Generate and publish websites.
For motivation and design choices, see Site Publisher.
This file is about how to use the generator and the syntax of each construct in each markup.
- Usage
- Summary
- Opinionated
- Markup
- Frontmatter
- Categories
- Page Names
- Entities
- Directories
- TEI dates
- TEI
gap - TEI
hand - TEI
choice - Facsimiles
- Blog Posts
- Paging
- SEO
- Graph
- Search
- Settings
- Apparatus
- External links
- Chunking
- Table of contents
- Tables
- Task Lists
- Footnotes
- Glossary
- Bibliography
- Code Blocks
- Callouts
- Admonitions
- Asides
- Quotes
- Strikethrough
- Figures
- PDF embeds
- Video
The source directory must contain _site_config.yml.
A key that is not a config field is an error, including a key nested under social, graph, or license.
Field names are kebab-case (named-windows, facsimiles-url).
Pass that directory as the only positional argument:
./gradlew run --args="/path/to/source-directory"
--serve generates and leaves the HTTP server running (same as IntelliJ @main def generate()).
--pretty-print rewrites authored TEI, DocBook, Markdown, and AsciiDoc in place and does not generate a site.
It skips a file whose bytes would not change.
It does not reformat HTML.
--width=N is the prose column (default 120).
--width=0 does not wrap.
--sentence-per-line defaults to true.
--sentence-per-line=false keeps a sentence on the same line and only wraps.
XML ignores both flags.
--pretty-print together with --serve is an error.
bin/pretty-print reflows Markdown and AsciiDoc paths and does not need site_config.yml.
It uses the extensions declared on those Markup instances.
Fences, tables, headings, front matter, and AsciiDoc attributes are left as written.
--log-level defaults to INFO.
--treat-errors-as-warnings, --include-drafts, --production, and --target-directory-name are the other flags.
SITE_PUBLISHER* environment variables are the same names with hyphens turned into underscores.
./gradlew build compiles and tests; ./gradlew test runs tests only.
Maven coordinates: org.podval.tools:org.podval.tools.publisher:0.9.0
A site with Gradle applies plugin id org.podval.tools.site-publisher (same version as the library).
The plugin adds a detached sitePublisher configuration and three JavaExec tasks that run
org.podval.tools.publish.site.Site.
Do not put the library on implementation: Scala version and Playwright stay off the project compile classpath, and off
the Gradle daemon.
plugins {
id 'org.podval.tools.site-publisher' version '0.9.0'
}
site {
treatErrorsAsWarnings = true
logLevel = 'INFO'
}
pluginManagement.repositories must include mavenCentral() (the plugin marker is published there) as well as
gradlePluginPortal() for other plugins such as Foojay.
./gradlew generateSite writes the target directory.
./gradlew serveSite is the same with --serve.
./gradlew prettyPrintSite passes --pretty-print, --width=N, and --sentence-per-line and does not publish _site.
generateSite and serveSite pass --pretty-print=false and do not pass the prose flags.
Do not make generateSite or prettyPrintSite a dependency of build; Pages CI calls generate explicitly.
OpenTorah :docs keeps tasks.named('generateSite') { dependsOn generateTables }.
site { } maps to CLI flags (defaults match SiteOptions):
| Property | Default | CLI |
|---|---|---|
| sourceDirectory | project directory | positional source path |
| targetDirectoryName | _site | --target-directory-name (absolute path used as-is) |
| treatErrorsAsWarnings | false | --treat-errors-as-warnings |
| logLevel | INFO | --log-level |
| includeDrafts | false | --include-drafts |
| production | false | --production |
| prettyPrintWidth | 120 | --width=N (prettyPrintSite only) |
| prettyPrintSentencePerLine | true | --sentence-per-line (prettyPrintSite only) |
The three tasks set --enable-native-access=ALL-UNNAMED, --sun-misc-unsafe-memory-access=allow,
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS=1, and PLAYWRIGHT_BROWSERS_PATH=$GRADLE_USER_HOME/ms-playwright.
generateSite launches Java 25.
Its inputs are the source tree minus the target directory and Gradle build/; its output is the target directory, so
Gradle up-to-date can skip a run when sources are unchanged.
The generator still deletes and rewrites the target when the task runs.
The plugin adds the library as a default dependency of the sitePublisher configuration, at the same version as the
plugin (org.podval.tools:org.podval.tools.publisher:0.9.0).
Gradle defaultDependencies run only when that configuration is still empty, so a consumer can pick another published
library version by declaring sitePublisher themselves (there is no site.publisherVersion).
Do not add a second coordinate alongside the default — a non-empty configuration replaces the default entirely:
dependencies {
sitePublisher "org.podval.tools:org.podval.tools.publisher:${providers.gradleProperty('sitePublisherVersion').get()}"
}
The supported pair is the same version as the plugin.
An older library may not accept flags the plugin always passes; a newer one is usually fine.
A local settings-body includeBuild of this repo substitutes the library module for whatever version string was
requested.
Site workflows: checkout, setup-java 25, setup-gradle, cache ~/.gradle/ms-playwright, ./gradlew generateSite (or
:docs:generateSite), upload _site / docs/_site, deploy Pages.
Pin the plugin version in build.gradle.
Known sites apply the plugin and call ./gradlew generateSite (or :docs:generateSite).
GitHub Actions can still call dubinsky/site-publisher/.github/actions/generate@v0.1.0 for repos that do not apply the
plugin (pin the action tag; optional version input overrides that action checkout’s gradle.properties).
Sites using this publisher:
This repo already includeBuild`s sibling `../xml and ../../OpenTorah/opentorah.org when those checkouts exist (-PxmlDir=
/ -PopentorahDir=).
A site that applies org.podval.tools.site-publisher substitutes a local checkout with two includeBuild`s of the
same directory: `pluginManagement (plugin id; pass a String, not a File) and the settings body (library child
org.podval.tools:org.podval.tools.publisher).
pluginManagement alone does not substitute the library now that it is not the included-build root.
Do not use mavenLocal().
pluginManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
final String publisherDir =
providers.gradleProperty('sitePublisherDir').getOrElse('../site-publisher')
if (file(publisherDir).isDirectory() && file("${publisherDir}/settings.gradle").isFile()) {
includeBuild publisherDir
}
}
// after plugins { }:
final File publisherDir = file(
providers.gradleProperty('sitePublisherDir').getOrElse('../site-publisher')
)
if (publisherDir.isDirectory() && new File(publisherDir, 'settings.gradle').isFile()) {
includeBuild(publisherDir)
}
Override with -PsitePublisherDir=.
OpenTorah / MathWorlds defaults stay ../../Podval/site-publisher.
When the directory is missing (CI), Gradle resolves the plugin and the library from Maven Central.
Without a site-side plugin, generate from this repo: ./gradlew run --args="/path/to/source-directory".
./gradlew nmcpZipAggregation ./gradlew publishAggregationToCentralPortal
The zip includes the library (org.podval.tools:org.podval.tools.publisher), the plugin implementation (org.podval.tools:site-publisher-plugin),
and the plugin marker (org.podval.tools.site-publisher:org.podval.tools.site-publisher.gradle.plugin).
Same version.
Site-header links are listed in _site_config.yml in display order (source path or site path, same resolution as home):
header-pages: - notes/index.md - posts - tags - graph
Omitted or empty means no header links (up/prev/next and format icons still appear). Unknown entries are recorded on the Errors page.
Optional facsimiles-url is the base URL of a facsimile JPEG site (trailing slash as in the collector
tei@facsimilesUrl).
Omitted means none.
See Facsimiles.
facsimiles-url: https://storage.googleapis.com/facsimiles.alter-rebbe.org/
Optional tei-default-calendar is julian or gregorian (default gregorian).
TEI date/@when numbers are in that calendar unless the element has calendar="#julian".
See TEI dates.
tei-default-calendar: julian
Optional named-windows (default off) reuses four collector window names so collection, names, transcription, and
facsimile can sit in separate tabs.
Internal links target the destination viewer; each page sets window.name.
Other sites should leave it off.
named-windows: true
license is one record.
name is required.
link, holder, and holder-link are optional.
A blank field is an error, and holder-link requires holder.
An omitted license key leaves out the head link and the footer line.
A scalar license or a top-level license-link is rejected.
license: name: CC BY 4.0 link: http://creativecommons.org/licenses/by/4.0/ holder: the Open Torah Project holder-link: http://www.opentorah.org/
description stays plain text because it is also the Atom subtitle and the SEO fallback.
chumashquestions.org replaces the scalar keys:
license: CC by-nc-nd license-link: http://creativecommons.org/licenses/by-nc-nd/4.0/
with:
license: name: CC by-nc-nd link: http://creativecommons.org/licenses/by-nc-nd/4.0/
This here is a static site generator. It:
-
is Opinionated;
-
recognizes various Markup languages;
-
interprets Frontmatter;
-
resolves various Page Names;
-
recognizes TEI Entities;
-
lists Directories;
-
supports Blog Posts;
-
supports Chunking;
-
supports Paging;
-
supports PDF generation;
-
supports TEI Facsimiles;
-
writes SEO tags in each page
<head>; -
can emit an interactive Graph of pages;
-
has reader Settings for color scheme, glossary, expansions, transclusions, facsimiles, and Apparatus;
-
checks External links when
check-linksis set;
Supported markup constructs:
Since I am writing this site generator for my own use, it is going to be opinionated:
-
everything is written in Scala
-
no plugins
-
no SCSS
-
no template languages
-
no layouts
TODO expound
Recognized by extension:
-
Markdown (
.md) -
AsciiDoc (
.adoc) -
HTML (
.html) -
TEI (
.xmlwith a TEI root:TEI,store,collection,person,place,org) -
DocBook (
.xmlwith a DocBook root:article,book,chapter,appendix,part,set,preface,refentry,topic)
Internal (YAML between --- lines in the markup file) or external (same basename, .yaml / .yml).
Do not use both.
Obsidian treats internal YAML as Properties; it stores extra keys but does not
interpret this publisher’s fields (chunk, bibliography, csl, …).
Other keys are kept.
modified_time sets the page modified time when its value is a date; any other value is ignored and the key stays.
Set title in front matter only when it differs from the file name.
Both the file name and the title are used for link resolution.
If the document also has a title (h1, AsciiDoc =, TEI/DocBook title), it must match the front matter title; a
mismatch is recorded on the Errors page.
Useful fields:
-
chunk/chunk-depth— see Chunking -
bibliography/csl/lang— see Bibliography -
toc-depth -
description,author,lang— page<head>(SEO); site values in_site_config.ymlare the fallback. Sitelangalso selects store and selector display names (built-in terms, optional_terms.xml, TEI<name lang>); omitted isen. Generated chrome follows thatlangin_site_config.yml, and English when it is omitted. That is the gear, the collection table, and the search page. -
tags,categories(Categories),aliases -
icon,permalink,post,date -
pdf- see PDF -
asset— copy the markup file as an asset (no dialect processing, no site chrome). Standalone sidecar only:name.yml/name.yamlnext toname.html(or.md,.adoc,.xml, …) withasset: true. The sidecar is not published. Internal---asset: trueis an error (the file stays markup). A directoryindexso marked stays a directory page (parent/children) butwritecopies the file (no listing, no chrome).
permalink (absolute, e.g. /short) and aliases (relative or absolute) each create a Refresh page at that URL.
A permalink that does not start with / is an error (/errors.html#permalink) and creates no alias.
When the page is a directory (an index or a TEI store / collection beside its folder), /permalink/child and wiki
[[permalink/child]] resolve to child under that directory — the short name is a path prefix, not only a leaf
bookmark.
A remainder under a non-directory alias does not resolve.
A last-dot suffix in a href that is all digits is part of the name (255.2), not a file extension.
categories is a list of links to authored pages.
A value is a wiki link ([[Books]] or [[Books|label]]) or a bare name (Books).
It resolves the same way as a wiki link in the body.
The target is a normal page: its prose stays, and the generator appends the pages that name it, sorted by title.
A value that resolves to nothing is an unresolved link on the Errors page.
No category page is created for it.
![[Note.base]] and ![[Note.base#View]] are Obsidian queries and are omitted, with no error.
The member page header lists each category beside its tags.
A wiki link in the body is a backlink.
It does not put the page in that list.
A page may name several categories.
Jekyll relies on page titles being given explicitly in the front matter and ignores the file names; I had to use a TODO
Obsidian plugin to set the titles from the file names to communicate them to Jekyll; as a result, pages where the title
is different from the file name (e.g., index pages) had to be marked with TODO in their front matter to disable this
plugin from setting the front matter title.
My plugin uses both the front matter title and the file name for link resolution, so I can remove this Obsidian plugin and redundant front matter titles which are the same as the file name, and set the title in the front matter only when it is different from the file name.
Obsidian uses file names exclusively and ignores the front matter title; for link resolution to use front matter titles also, TODO Obsidian plugin needs to be installed.
Wiki links ([[…]], including [[note|text]] and [[#id]]) are native Obsidian.
Block ids: ^id at the end of a paragraph, or on its own line after a list, table, quote, or code block (blank line
before the id).
Link with [[note#^id]].
Obsidian embeds of pages (![[note]], ![[note#heading]], ![[note#^block]]) copy that authored
region into the host (any markup on the target).
The copy is a boxed aside.transclusion whose header is a link to the source (Notes Title or Notes Title § heading).
The gear Settings checkbox Seamless transclusions hides the box and header (html.transclusion-clean).
A missing heading or block is an error (not a silent whole-page embed).
Footnotes inside a transcluded region are merged and numbered with the host.
Media embeds (![[file.pdf]], images, audio, video) are unchanged.
AsciiDoc include:: and TEI/DocBook xi:include are not this feature.
Wiki links in Obsidian:
-
are they case-sensitive?
-
do they take into account file name, title property, document title?
-
how do they look in the file if name reference is ambiguous?
-
can they be split between the lines?
-
do they support any kind of agglutination?
A TEI file whose root is person, place, or org is an entity; the kind is that root.
The entity id is the file name without .xml (name/alter-rebbe.xml → alter-rebbe).
The file may sit anywhere in the tree.
persName, placeName, and orgName with a non-empty @ref resolve to an entity when the kinds match and @ref is
exactly that id (not a path, not the displayed name, not a title).
A kind mismatch or unknown id is an unresolved internal link and is recorded on the Errors page.
Without @ref, the name is left as-is (not a link) and is recorded on the Errors page (name without @ref,
/errors.html#no-ref).
That check runs on the first parse of TEI documents and of store/collection title / abstract / body — not on the
name elements that define an entity file.
TEI unclear is /errors.html#unclear.
An entity file whose name is not the first name with spaces turned into underscores is /errors.html#misnamed-entity.
Each kind on the Errors page is a fragment (AmbiguousTitle → #ambiguous-title).
When more than one kind is present, a TOC links to those fragments.
<persName ref="alter-rebbe">Алтер Ребе</persName>
в <placeName ref="Вильна">Вильне</placeName>
<orgName ref="Виленский_кагал">виленский кагал</orgName>Wiki [[alter-rebbe]] still finds the entity page by file name.
Entity refs do not title-walk, so a markdown page titled alter-rebbe does not steal persName ref="alter-rebbe".
A TEI entityLists file is a catalog of buckets, not a store include list.
Beside a directory (name.xml next to name/) it is that directory’s index (/name/index.html).
Each listPerson, listPlace, or listOrg is a bucket:
-
the list kind is the element (
listPerson→personfiles); -
@nis the list id (/name/{n}.htmlwhen the catalog has two or more non-empty lists); -
@rolepresent: entities of that kind whose root@rolematches, anywhere in the site; omitted: entities of that kind with no role; -
<head>on the list is the list page title (<title>is still accepted). The heading is that title alone: nonameselector word, and no axis line under it. A direct child<title>ofentityListsis the catalog page title.refandplural="yes"onentityListsname that title from the term’s plural instead of<title>. The two attributes are a pair, and they are not combined with a<title>.
Empty lists are omitted.
Members are every matching entity on the site, sorted by file name; the link text is the first persName / placeName
/ orgName.
The entity page <h1> / <title> is that first name, not the file name.
Document backlinks on an entity page still come from @ref in other documents (grouped by TEI collection when the
source sits under one).
The group heading is that collection’s archive path, the same / spelling as the document breadcrumb, and the whole
path is one link to the collection.
The document row opens the first mention in the text.
A mention that exists only in teiHeader leaves that row on the document, because the published page does not keep
those anchors.
The catalog itself is not a backlink source.
A place with location/geo is a point on /map.html.
The page exists when any place has a parseable geo, including a site with no listPlace.
The text is the latitude, then the longitude, separated by a space.
idno with type="wikidata" may name the same place.
The body is the figure: the drawing, its caption, and a table of the places that have a coordinate.
A place without a coordinate is not on the page.
The area of a circle is the number of cases that mention the place.
A case is a collection whose parent groups by case or дело, and a case counts once.
A place with a coordinate and no such case is a ring.
A place too far from the others is listed in that table and is not drawn.
A province, a country, or a part of a city is left without a coordinate.
The place list does not draw the map, and the publisher does not link the list to the map.
An optional markup file at map is prose above the figure.
Its h1 or front-matter title replaces the page heading, Map or «Карта».
The SVG accessible name stays the map.title string.
The page is not a header link and not a directory-listing entry.
/map is a leaf alias of /map.html.
Rivers and the coast come from place-map-base.txt at the site root.
The publisher does not supply them, and it does not check that the file exists.
Without the file, the map is drawn with no such lines.
The name starts with , so the file is not published.
Do not add a _site_config.yml key for it.
A blank line separates polylines.
A line is a comment.
A block with no coordinates is ignored, so the attribution can stand in its own block.
A block whose first line is coast is coastline.
Any other block is a river.
Each remaining line is longitude, then latitude.
That order is the opposite of TEI geo.
To write the file for a site, give this prompt:
Write `_place-map-base.txt` at the root of this site-publisher site.
Read every place file.
A place with `location/geo` has a coordinate: latitude, then longitude, in decimal degrees.
Keep the cluster that fits in 15° of latitude and 15° of longitude.
Recompute the median after each drop.
Distance is the hypotenuse of the latitude difference and the longitude difference times the cosine of the median latitude.
Drop the farthest place until both spans are at most 15°.
That cluster is the region.
Leave the dropped places out of the box.
Rivers: Natural Earth 10m `ne_10m_rivers_lake_centerlines`.
Coast: Natural Earth 50m `ne_50m_coastline`.
Both are public domain.
The 50m rivers omit rivers the 10m set includes, so do not use them.
Clip to the cluster with about one degree of margin.
Simplify rivers to about 0.12° and the coast to about 0.15°.
Do not draw a line that is not in the extract.
A river or a shore that stops short in the extract stops short in the file.
Write `_place-map-base.txt` in this form.
A blank line separates polylines.
A line that starts with `#` is a comment.
A block with no coordinates is ignored, so name the source, the scale, and the public-domain status in a block of its own.
A block whose first line is `# coast` is coastline.
Any other block is a river.
Name the river on a `#` line when the extract names it.
Every coordinate line is longitude, then latitude, separated by a space.
That order is the opposite of TEI `geo`.
The leading `_` keeps the file out of the published site.
Do not add a key to `_site_config.yml`.When two or more lists are non-empty, the catalog page is a TOC of those lists (links to the list pages; members are not
inlined).
A markup file at {n} is prose above that name-list page, for every kind.
Its h1 or front-matter title replaces that page’s heading (<title> and l.store-current).
The catalog TOC and the store-tree name stay the list <head>.
Without that file, the <head> stays the heading.
A catalog with a single non-empty list is that list (no {n} subpage).
That same file is prose above the inlined list, and {n}.html is still not published.
The catalog <title> stays the catalog title.
Several entityLists files are independent catalogs.
The catalog directory is an identity prefix in collection-aliases.json (/name → /name/index.html, /name/{id} →
/name/{id}.html), the same Worker mechanism as collection aliases.
There is no synthetic /name.html dump.
<entityLists>
<title>Имена</title>
<listPerson n="jews" role="jew"><head>Жиды (они же Евреи)</head></listPerson>
<listPerson n="unknown"><head>Неизвестно кто</head></listPerson>
<listPlace n="places"><head>Места</head></listPlace>
</entityLists>The same catalog titled from the built-in name term.
On a Russian site the title is Имена.
Each list keeps its <head>.
<entityLists ref="name" plural="yes">
<listPerson n="jews" role="jew"><head>Жиды (они же Евреи)</head></listPerson>
</entityLists>Root / is index.md / index.adoc, or a synthesized listing of that folder.
To open at a specific page instead, set home in _site_config.yml to an absolute site path.
If the document is chunked, use the TOC (P/index.html):
home: /book/book/index.html
/index.html is then a meta Refresh to that page (not an HTTP 301).
Do not also author index.md / index.adoc.
Other root files stay on the site; they are just not listed at /.
Collection short names (collector site.xml <alias n="rgada" to="…"/>) are an optional alias on the TEI store /
collection root, not site config and not front-matter permalink:
<collection n="3140" directory="3140" alias="rgada">
alias is the URL prefix (/rgada, /rgada/003).
The publisher does not write a Refresh file at /rgada.html; Pages.find, emitted hrefs (including canonical/sitemap),
and serve() rewrite /rgada/003 to the written …/3140/003.html.
Inbound old collector facsimile URLs /rgada/facsimile/003 rewrite the same way (see Facsimiles).
Front-matter permalink / aliases remain for leaf Refresh pages.
Generate also writes collection-aliases.json (segment from → collection directory to) for a Cloudflare Worker.
The Worker is an internal rewrite (not a 301): longest slash-delimited prefix, then .html unless the request already
has an extension (keep .xml); inbound /alias/facsimile/P becomes {to}/P/facsimile.html.
Entity-lists catalogs are an identity prefix (/name → /name/index.html).
Prefix routes (hostname/rgada*, hostname/name*, …)
so assets skip the Worker.
Deploy with .github/actions/deploy-alias-worker.
Canonical entity hrefs stay the file path (/name/{id}.html when the file lives under name/).
A directory permalink (an index or TEI store/collection) is also a prefix: /short/child resolves under that
directory, and emitted hrefs use the short path.
A TEI store file in the source root (archive.xml next to archive/) also gets two synthetic indexes, named from the
file: {name}-collections.html (nested tree of child stores and collections) and {name}-index.html (flat list of
descendant collections).
The tree is titled Tree, or «Дерево» when lang is Russian.
The nested list still labels each level with that store’s by/@selector word from the term (archive is архив when
lang is ru, and archive otherwise).
A store with ref and plural="yes" is headed by the sentence-cased lexicon plural (Books, Книги).
The name stored for the URL is the lexicon plural (books, книги), not that heading.
The flat list is titled Collections, or «Коллекции» when lang is Russian.
A file at either path is rendered above the generated list.
A title in that file, an h1 or front matter title, replaces the page title.
The file is not listed in the root directory.
Point header-pages at {name}-collections and home at /{name}-index.html when those should be chrome / the
landing page.
The same root gets {name}-years.html when at least one original TEI document has that store as an ancestor.
Documents are grouped by the year on profileDesc/creation/date.
That year is the number written on the attribute, not a conversion to another calendar.
The page is a closed outline.
Each year is a collapsed line with the number of documents.
Opening a year shows its months the same way.
The month name follows the site lang.
A month with no documents is omitted.
A date with no month is a line after that year’s months: «без месяца» when lang is Russian, and "No month" otherwise.
Opening a month lists the rows.
When three or more documents share one exact day (@when with a day), that day is a line above those rows.
In Russian the line is the day, the month in the genitive, and the count.
In other languages it is the month name, the day, and the count.
A same-year range with a month on both ends is listed under each month from the first through the last.
A range inside one month is listed once, in that month.
It joins a day line only when both ends are that same day.
The row shows the date text when that text is not empty.
One value and no text shows that raw attribute.
Both ends in the same year and no text show the two raw values with an en dash between them.
Different years show the text, an em dash, and both raw ends, or the two raw ends when there is no text.
An unparsable value or a schematron conflict shows the text only, which may be empty.
An inverted range, the end year before the start year, uses that same different-years label: the text, an em dash, and
both raw ends when the text is not empty, otherwise the two raw ends joined by an en dash.
It is listed under undated.
A span of one year is listed under each year.
A longer span is listed under the start year only.
A document with no creation date, or a date with no temporal attribute, is listed under «Без даты» when site lang is
Russian and under Undated otherwise.
That line is closed and shows the count.
Inside it, documents are grouped by the archive directly under the root store, each archive a closed line with a count,
in the store’s include order.
The section is omitted when every listed document has a year.
A file named {base}-{xx} is not a row.
The title is «Годы» when site lang is Russian and Years otherwise.
A file at this path is rendered above the list.
A title in that file replaces «Годы».
The page is not added to header-pages.
Each root store with original documents gets its own year file.
A root with no original documents does not get a year page.
One root store is left off the path text of its descendants: the breadcrumb and the other archive-path strings.
There is no /collections or /years alias, with one root store or with several.
A second root store puts that crumb back.
A TEI store or collection file beside a directory (dir.xml next to dir/) is that directory’s index.
A collection index sets class="wide" on <html> so the content .wrapper is 1800px; header and footer stay the
default width.
Store indexes and documents under the collection do not.
xi:include/@href (relative to the store file) is an ordered child reference, not XInclude: the target is not inlined.
Store children are listed in include order (name: title).
A collection body is table.collection-index (Описание, Дата, Кто, Кому, Язык, Документ, Страницы, Расшифровка), not
a page list.
Optional <part from="000"> title rows split the table; pageType is manuscript (default: 000 / 000об) or book
(numeric).
A file named {base}-{xx} (two-letter language, dash at length 3) is a translation of {base}.
The two letters are the language code even when the catalog does not know them.
It is not a table row.
The Язык cell stays the code: he ru, with ru linking to the translation.
The site header links every reading to the others.
The link text is the autonym (русский), not [ru], and the link carries lang and hreflang.
A translation links back.
The same suffix works outside a collection.
Prev/next still skip translations.
A file named {base}+reg is a regularized reading of {base}, not a language.
doc-ru+reg reads doc-ru.
It keeps that file’s xml:lang.
It is not a collection row.
Prev/next skip it in every directory, and from the reading they are the original’s neighbors.
It shares the original’s facsimile.
The original’s header links [reg] after the language links.
The reading does not link back, and it is not in the hreflang set.
It is not in search.
A site-root directory whose name is a catalog code (en, not English and not zz) is another reading of the tail
with the same extension (en/notes/about.md and notes/about.md).
col/en/001.xml is not one: only the first segment counts.
A cluster needs the unsuffixed page that is not under a language prefix.
A lone 001-ru.xml or a lone en/about.md has no header link and no hreflang.
Two files with the same code are an error, and one link remains: the suffix file if there is one, otherwise the first
published path.
html lang is one stored code: text/@xml:lang, otherwise the filename suffix, otherwise that directory, otherwise
front matter lang, otherwise site lang, otherwise en.
Front matter, text/@xml:lang, text/@lang, the suffix, and the directory are signals.
The site language is not.
When two signals differ, the file is Wrong language.
On a TEI file, a missing text/@xml:lang is a signal when a suffix or a language directory is present (missing != ru).
Markdown and AsciiDoc have no such attribute, so en/about.md with no front matter is not an error.
When the cluster has another member, every member’s head has hreflang for each member, including itself, and
hreflang="x-default" pointing at the unsuffixed page.
The URL is absolute, the same string as <link rel="canonical">.
Canonical stays the page itself.
A page with no alternate has no hreflang.
There is no Accept-Language and no redirect.
A name the catalog knows (Russian, ru-RU) is published as the code (ru).
dir and data-interface-lang on <html> are the interface language, which is site lang, not html lang.
dir is rtl for he and yi, and ltr otherwise.
The root rules html[dir="ltr"] and html[dir="rtl"] set both direction and text-align, so a Hebrew document does
not right-align the Russian chrome.
An xml:lang on a content element is also an HTML lang on that element.
<code lang> is the highlight language (tei-lang), not HTML lang.
When the document language and the interface language differ, the site header and footer, the page or store header, the
facsimile aside, backlinks, embedded-in, category members, page breaks, and the date tip carry the interface lang and
dir, and div.page-body carries the document lang.
A note’s h1.page-title carries the document language.
On a collection document the value cells do, and l.store-current does not.
A store title and a store body are wrapped so the selector word stays in the interface language.
span.store-name keeps the language of the name.
The page header (.store-header) holds one ancestor path (nav.store-path) and this node’s selector + name + title as
an <l class="store-current">, then abstract/body and the by/@selector label.
The steps are separated by /.
The flat collection index, search hits, the year index, the citation, and sourceDesc use that same spelling.
When the site has exactly one root store, that store is left off the path of its descendants.
A second root store puts the crumb back.
The root page itself is unchanged.
A collection document with no title (no nonempty titleStmt/title, and no markup or front-matter title) does not get
that large line.
Its own selector and name (документ 071) are the last crumb, not a link, with aria-current="page".
A document that has a title keeps the large line.
A TEI document under a collection also gets a document-header table from teiHeader (Описание, Дата, Кто, Кому,
Расшифровка, Перевод).
A line is omitted when its value has no text.
The Кому cell is the first persName in a correspAction with type="received".
When no action has that type, it is the persName with role="addressee".
An endnote in teiHeader is not an article footnote.
A note in abstract, in the date, or in the addressee persName is a footnote on that header cell, and on the same
cell of the collection index.
The Дата cell (and in-text dates) get calendar hover tables; see TEI dates.
Selector words (document → документ when site lang is ru) come from the built-in terms.
A site adds axes in _terms.xml at the source root: a <Terms> file of <term id="…" selector="yes">.
by/@selector is that id.
An unknown id fails generation.
It is not an Errors-page warning.
A store or collection with ref and plural="yes" takes its name from that term’s plural.
Do not also write <name> or a root n.
<title>, <abstract>, and <body> stay.
The heading capitalizes that plural.
An authored <name> is shown as stored.
Documents under a store or collection use the same header.
Each TEI document is also written as XML beside the HTML (/rgia529/071.xml, and the same stem for a {base}-{xx}
translation).
The file is generated.
It is not the git file and it is not in the sitemap.
publicationStmt uses the site url and the nested license (name, link, holder).
sourceDesc is the archive path, plus facsimiles-url when that is set.
langUsage is that document’s xml:lang.
calendarDesc is tei-default-calendar when that is set.
revisionDesc says the published file is generated and the file in git is the source.
The publication date is the build’s HEAD commit time, omitted when the source directory is not in a git checkout.
The document header links to that XML file.
On the breadcrumb line, at the right, a TEI document also has the TEI link, the GitHub link, and a Cite button.
Each of those has a tooltip: "Published TEI", "Source file on GitHub", and "Copy citation".
The citation is not shown.
The button copies the archive path, the page’s canonical URL, the transcriber (Расшифровка), and the license name and
holder.
There is no access date.
When the build has a HEAD commit, the copied text then says Git hash and the full SHA.
When origin is a GitHub repository, it then gives the file URL, the same URL as the GitHub link.
The canonical URL is the same string as <link rel="canonical">: url from _site_config.yml plus the public path.
A url with no scheme is not given one.
Every authored page with a source file links to that file on GitHub (https://github.com/owner/name/blob/<branch>/…)
when origin is a GitHub remote.
The link is the branch tip, not the commit the citation quotes.
git@github.com:owner/repo.git, ssh://git@github.com/owner/repo.git, and
https://user:token@github.com/owner/repo.git all become https://github.com/owner/name.
A token in the remote is not written into the page.
A source directory below the work tree (docs/) is included in the path.
If origin is missing or is not GitHub, there is no link.
There is no repository key in _site_config.yml.
A scanned file under that directory that is not named in the includes is an error (translations are not).
Selector hops in the href (book/ in books/book/derzhavin.xml) are URL segments only — they are not pages, and up
skips them.
Above the table, one line gives the number of documents.
Missing photographs, split into empty leaves and leaves with text, and the scanned leaves of a document that has no
transcription, are on that line when the count is not zero.
Each of those counts opens one link per leaf.
Collection Страницы links to pb ids in the transcription (#p{n}); in-text pb links to the
facsimile viewer when facsimiles-url is set.
-
directory structure
-
navigation
-
header pages
-
icons
A TEI <date> with @when, @from, @to, @notBefore, or @notAfter gets a hover table.
.. in any of those attributes is InvalidDate, and the element is left as authored.
Each value is YYYY, YYYY-MM, or YYYY-MM-DD.
Month names follow the site lang.
The column headers stay English.
A full day on @when is one column, Date.
A year or a month on @when is both ends of that year or month.
A year or a month on @from or @notBefore expands to the first day of that end only.
A year or a month on @to or @notAfter expands to the last day of that end only.
@from equal to @to, or @notBefore equal to @notAfter, stays two columns, not Date.
| What you write | Hover columns |
|---|---|
| @when on a full day (YYYY-MM-DD) | Date |
| @when on a year or a month | From, To |
| @from and @to | From, To |
| @notBefore and @notAfter | Not before, Not after |
| @from only | From |
| @to only | To |
| @notBefore only | Not before |
| @notAfter only | Not after |
| @from and @notAfter | From, Not after |
| @notBefore and @to | Not before, To |
These combinations are InvalidDate, and the element is left as authored:
-
@whentogether with@notBefore,@notAfter,@from, or@to -
@fromtogether with@notBefore -
@totogether with@notAfter
@from with @notAfter, and @notBefore with @to, are allowed.
The numbers are Gregorian unless the element has calendar="#julian", or _site_config.yml has
tei-default-calendar: julian and @calendar is omitted.
Any other @calendar value is Gregorian.
This disagrees with the TEI Guidelines: the attributes are not rewritten to Gregorian; @calendar selects how the
numbers are read, and it still describes the prose.
Markdown uses the same element.
A <date> in a Markdown page with those attributes gets this hover.
The words on the page stay as written.
@calendar and tei-default-calendar apply as they do for TEI.
A <date> in code stays as written.
A hover tip inside a heading is left out of the section id and the table-of-contents title.
The collection index and document-header Дата cell show @when as the visible text when @when is present.
A note inside that date stays in the cell.
Otherwise the cell shows the element’s own text, still inside <date>, so the tip can attach.
In-text dates always keep their text.
A <date> with none of @when, @from, @to, @notBefore, and @notAfter is unchanged.
A TEI <gap reason="…"> with a non-empty @reason gets a hover tip with that text (same family as dates).
An empty element with a non-empty @reason shows one […] mark.
@content is not consulted.
Missing or blank @reason leaves the element as-is.
@hand="#id" points at a handNote in the same document.
@medium on that note is pencil or ink.
The note text is the hover tip.
A pointer that is not a single #id of a handNote in the document is an error (/errors.html#unresolved-hand), and
the element is left unwrapped.
<handNote xml:id="pencilHand" medium="pencil">записано карандашом</handNote>
<fw type="pageNum" place="right" hand="#pencilHand">6</fw>Author an abbreviation as abbr then expan:
<choice><abbr>Wilen<hi rend="superscript">ch</hi></abbr><expan>Wilenskich</expan></choice>The published tags are the same.
There is no new element.
orig and reg stay those names.
The existing CSS draws ( before the second child and ) after it, so the line reads Wilen with ch superscript,
then (Wilenskich).
The expansion is italic.
The parentheses stay roman.
supplied stays in square brackets, and corr keeps the editorial background.
The publisher does not reorder.
Reverse order would show the expansion as the main text.
Abbreviation expansions and corrections is on unless the reader turns it off.
Off hides expan, corr, and reg the same way: each leaves the line and comes back as a hover tip.
A dotted underline marks an abbreviation that has no titlo.
orig stays in the line, as abbr and sic do.
sic keeps its editorial underline.
It does not gain a dotted underline.
A titlo stays the overline.
Print and PDF show the expansion, the correction, and the regularization.
The checkbox appears when the site has a choice.
See Settings.
It is not the apparatus menu below.
A titlo:
<choice><abbr rend="titlo">мца</abbr><expan>месяца</expan></choice>The overline is abbr[rend~="titlo"].
A titlo with no expansion is abbr rend="titlo" and no choice, so there are no empty parentheses.
An abbreviation in the source is not supplied and not corr.
supplied stays an editor addition in brackets, and corr stays a correction inside choice with sic.
When _site_config.yml has facsimiles-url, each TEI document with at least one non-missing pb gets a viewer at
P/facsimile.html (published /alias/P/facsimile.html when the collection has alias).
Photos scroll in a pane under the header (the window is the size; there is no inner resize box).
Old collector bookmarks /alias/facsimile/P (and /alias/facsimile/P.html) still resolve to that viewer, including
translation /alias/facsimile/P-xx (shared original).
#p{n} fragments are unchanged.
New pages emit only the new URL.
The JPEG for pb@n is {facsimiles-url}{directory of the TEI file}/{n}.jpg.
A trailing slash on the config value is optional.
pb@facs overrides that URL.
Missing pb@missing pages are omitted from the scroller.
In the transcription, <pb n="000-1"/> is an images icon and the leaf name 000 (a.pb, id="p000-1") linking to the
viewer fragment, target="facsimile" (facsimileViewer when named-windows is on).
The name is smaller than the transcription.
The icon and the name stay on one line.
If they do not fit in the space left on the line, both move to the next line.
Hovering the mark shows the leaf name and «Факсимиле.».
The site-header format icon does the same without a fragment.
A missing photograph is its own line: the same icon faded, then the leaf name and «Нет фотографии.».
An empty leaf adds «Пустая.».
The line is not a link.
Hovering it shows that same sentence.
On a wide window the transcription shows the current scan to the right of the text. The text column stays the same width. The gear row "Show the facsimile beside the text" is present when the site has a companion pane. See Settings. It is on unless the reader turns it off. A page-break icon selects that scan while the pane is open. It opens the viewer when the pane is closed. The header facsimile icon always opens the viewer. A page with no transcribed text keeps its scan in that pane. While the pane is open, that page is a screen tall, so scrolling reaches it. A missing photo does not. Print and PDF do not include the pane.
Collection Страницы is page numbers on the transcription #p{n} only.
Each photo in the viewer links back to that pb in the transcription (target="text" / textViewer).
{base}-{xx} translations share the original’s viewer.
No new _site_config.yml key.
Omitted facsimiles-url still means no viewer and no pane; pb keeps its id (collection Страницы still works) and
has no href.
A page is a blog post in one of three ways:
-
A file in
_posts/or_drafts/namedYYYY-MM-DD-titleis published at/YYYY/MM/DD/title.html. -
Front matter
post: truekeeps the file path and adds a Refresh page at/YYYY/MM/DD/{file name}.html.dateis required.post-titlereplaces the file name in that path. -
Front matter
permalink: /YYYY/MM/DD/titledoes the same for any source file name. The Refresh page is that permalink.
/posts lists each real post once.
The Refresh file is not a second entry.
Front matter description is the post teaser: Atom <summary> and, when present, a p.post-excerpt on every /posts
batch.
Not auto-cut from the body.
Site description is not used here (SEO still falls back to it).
The /posts listing only.
In _site_config.yml:
paginate-posts: 10
Omitted or less than 1 is off.
Page 1 stays /posts.html; further batches are /posts/2.html, /posts/3.html.
A nav.pagination sits under the list.
No extra config.
Each HTML page <head> gets a document title (Page | Site title; home is just the site title),
meta name="generator" pointing at this publisher, description, author, canonical URL, Open Graph, a Twitter summary
card, and JSON-LD (WebSite / BlogPosting / WebPage).
Taken from existing fields: site title, description, url, author, lang, social.twitter; page title,
description, author, lang, date (and git/modified_time for modified).
No image, Facebook, or webmaster keys.
Off by default.
When _site_config.yml has graph.enabled: true, generate writes /graph.json and /graph.html.
/graph.html is a pan/zoom map of canonical authored pages and the resolved internal links between them (Cytoscape.js,
loaded only on that page).
Click a vertex to open the page (named-windows targets apply when that option is on).
Vertices are authored pages with a source file: notes, posts, TEI documents, entities, stores, and entity-list catalogs.
Aliases, chunks, facsimile viewers, PDFs, assets, and synthetics (/errors, /tags, /posts, …)
are not vertices; links to those views count as links to the owning page.
Edges come from existing backlinks (kind: link, including TEI @ref) and, unless turned off, transclusions (kind: transclude).
include-transclusions defaults to true; omit it or set it explicitly.
Unresolved links stay on the Errors page, not as graph nodes.
Do not name a collection @alias="graph": that path is reserved for the graph page.
Do not name a collection @alias="search" when search is enabled: that name is reserved through installPrefixAlias,
the same way @alias="graph" is reserved.
graph:
enabled: true
include-transclusions: true
exclude-path-prefixes:
- days
exclude-path-prefixes match the source path (days/2020-10-23.md, archive/lvia/…), not the published URL.
Daily notes publish as /YYYY/MM/DD/index.html; TEI collection aliases publish as /lvia1799-2/….
A notes vault typically enables the graph and excludes the Obsidian daily-notes folder (days when that is the
configured folder).
A large TEI archive should leave the graph off, or exclude archive to keep only names and notes.
Add graph to header-pages if you want a header link (there is no automatic icon).
Pipeline notes belong in the Site Publisher design note, not here.
Off by default.
Omitted search does not write /search.html or /search.json, and pages do not get a header form.
When _site_config.yml has search.enabled: true, generate writes /search.json and /search.html, and /search
rewrites to that page (no Refresh file; the Worker table includes it).
The alias does not rewrite /search.json.
That file is the index search.js fetches.
Every page then has a header form, immediately after the site title, that submits to /search.
That form does not load search.js or search.json.
Only /search.html loads the script.
The index is the diplomatic transcription: the spelling of the source, including ѣ, without stemming or modernized
spelling.
A sic reading is indexed.
A corr reading is not.
A line-break hyphen is kept, and the joined spelling is the next word.
Борухо-<lb break="no"/>вичъ is stored as Борухо-вичъ Боруховичъ.
Punctuation stays on the hyphenated word, so Борухо-вичъ, is followed by Боруховичъ and not by Боруховичъ,.
Consecutive break="no" hyphens are one joined word: на-хо-дилось and находилось.
A hyphen that is not immediately before such a break is not joined, including что-то and a hyphen before an ordinary
lb.
A maqaf (U+05BE) is not joined.
Notes, name pages, and a collection’s title and abstract are included.
Collection tables, non-collection stores, and entity-list catalogs are not.
An empty query shows no hits.
Year, archive, and language filters combine with the query.
A record with no language stays visible when a language is selected.
A record with no year or no archive is hidden when that filter is selected.
Do not author search.md or search.adoc when search is enabled.
SearchPage is added before the scan, so that file collides in claimPage, not in installPrefixAlias.
When search is off, search.md is an ordinary page.
Do not name a collection @alias="search" when search is enabled.
That name is reserved through installPrefixAlias, the same way @alias="graph" is reserved for the graph page.
exclude-path-prefixes use the same source-path test as the graph.
A document excluded there is still listed on the year page.
There is no 4 MB limit.
search: enabled: false exclude-path-prefixes: []
The gear menu on each page stores choices in the browser. A row is on every page of the site, or on none.
Color scheme is System, Light, or Dark. Nothing saved, and System, follow the operating system. Light and Dark stay as chosen. Print and PDF stay on the light palette. When the scheme resolves to dark, code blocks use the highlight.js dark theme and a mermaid diagram uses the same tokens as the page. The page graph uses the same colors.
In the text is the heading for term definitions and expansions. The heading is omitted when the site has neither.
Term definitions and citations inlines glossary and citation definitions.
Abbreviation expansions and corrections is on unless turned off.
See TEI choice.
Seamless transclusions hides the box and header around a transcluded region. See Page Names.
Show the facsimile beside the text hides the companion pane. See Facsimiles.
Transclusions and the facsimile row stay ungrouped. That section is omitted when the site has neither.
AsciiDoc roles mark additions, sources, and deletions. Each kind is independent and stored in the browser gear menu.
[.add]#added words#
[.see]#a source#
[.del]#removed words#Marked is the default: additions in brackets, sources in angle brackets at the small size, deletions struck through.
Plain shows the words with no chrome.
Hidden sets display: none.
The words stay in the HTML.
The gear lists Additions, then Deletions, then Sources.
Marked is labeled [Marked], a struck-through Marked, and <Marked>.
The Sources label stays at the small size.
[.add] is words the editor added, the same kind of words as a TEI supplied.
A TEI add is a scribal insertion and is not this role.
[.del] is words the editor takes out of the reading text.
A TEI del is the scribe’s own strikeout and is not this role.
[.see] is a source.
The role names and the TEI names both stay.
Each group appears only when that role is used anywhere in the site.
A site with [.add] and no [.del] or [.see] shows only Additions.
See Settings.
Nesting two kinds needs the inner span unconstrained:
[.add]#before [.see]##inner## after#A with spaces on both sides stays inside the span.
A against the previous word closes it.
# does not escape a hash.
Use ##, #, or # for a literal hash.
Off by default.
When _site_config.yml has check-links: true, generate requests each author-written http and https URL and lists
failures on the Errors page (#broken-link).
check-links: true
Checked: links (a@href) and media (img / video / audio / source @src, PDF object@data), including links
inside footnotes and bibliography entries.
An absolute URL whose host is the site url is a spurious self-link and is not requested.
mailto:, tel:, javascript:, and data: are not requested.
URLs inside code, and URLs only reached by transcluding another page, are checked on the page that authors them.
The same URL is requested once per generate.
Each page that contains it gets one error.
The request is HEAD.
On 403, 405, or 501 it is retried once with GET.
Redirects are followed.
A status of 400 or higher, or a timeout or connection failure, is a broken link.
The error text includes the status or the failure message.
Footer social links, the license link, the holder link, and facsimile JPEGs are not checked. Generation still finishes. The Errors page is the report, as for unresolved links. Timeouts are 5 seconds to connect and 10 seconds per request.
Split a document into per-section HTML pages (any markup, not only AsciiDoc).
chunk: true chunk-depth: 2
P.html is the unsplit document (wiki lands there).
The split view is P/index.html (P/ on a static host): preamble plus TOC, then P/Section.html for each chunk.
File hrefs to P/index.html stay there; they are not rewritten to P.html.
No directory listing is synthesized for P/.
When both views exist, the site header has icon links between them (list for chunked, file for one-page). Chunked TOC shows ancestors plus the current subtree; sibling branches are titles only.
A page with chunk: true or toc-depth gets a generated TOC if none is marked in the body.
When the window is at least 1100px wide, that TOC is a sticky rail beside the title and the text.
On a narrower window it stays at the top of the text.
A click lands with the heading below the site header.
A placeholder in the body is replaced with that TOC (the first one only) even without those keys; toc-depth still
controls how deep it goes (default 2).
- Markdown
-
Kramdown
{:toc}on a list. The list (seed item included) is replaced. Ordered or unordered:* this list is replaced with the TOC {:toc} ## Alpha
or
1. seed/{:toc}for a numbered outline.
A paragraph whose only text is [TOC] (any case) is the same placeholder (Typora, GitLab).
It must be a top-level paragraph: not a link ([TOC]: /url then [TOC]), not in a list, quote, or code span.
- HTML
-
<div class="toc-placeholder"></div>.
AsciiDoc, TEI, and DocBook have no in-body placeholder; use chunk / toc-depth.
Front matter pdf: true prints P.html to P.pdf (Chromium).
The site header on P.html (and on chunked pages, if any) gets a PDF icon linking to it.
The site header is hidden in print, so the PDF itself does not include those icons.
Markdown tables via FlexMark Tables Extension (GFM; Obsidian supports them natively).
- Markdown
-
| A | B | |---|---| | 1 | 2 |
AsciiDoc tables via Asciidoctor.
- AsciiDoc
-
|=== | A | B | 1 | 2 |===
- TEI
-
<table> <row role="label"><cell>A</cell><cell>B</cell></row> <row><cell>1</cell><cell>2</cell></row> </table>
- DocBook
-
CALS
row/entry(optionaltgroup);morerowsbecomesrowspan.<table> <tgroup cols="2"> <thead><row><entry>A</entry><entry>B</entry></row></thead> <tbody><row><entry>1</entry><entry>2</entry></row></tbody> </tgroup> </table>
- HTML
-
<table> <thead><tr><th>A</th><th>B</th></tr></thead> <tbody><tr><td>1</td><td>2</td></tr></tbody> </table>
Unchecked [ ] and checked [x] items in a list.
The static site shows disabled checkboxes.
- Markdown
-
GFM / FlexMark Task List Extension (Obsidian supports them natively):
- [ ] open - [x] done
- AsciiDoc
-
native checklists:
* [ ] open * [x] done
DocBook has no native task-list markup.
- HTML
-
the IR shape (already a task list; no conversion):
<ul class="task-list"> <li class="task-list-item"> <input type="checkbox" class="task-list-item-checkbox" disabled> open </li> <li class="task-list-item"> <input type="checkbox" class="task-list-item-checkbox" disabled checked> done </li> </ul>
Hovering a footnote number shows the note; the numbered list is at the end of the (chunk of the) page. A note written with no space after the preceding word or element stays adjacent in the HTML.
A marker in a table cell is published as a lowercase Latin letter (a, b, …, z, aa) with the list immediately
under that table (div.table-with-notes).
Markdown still defines those bodies at the end of the source file; the published page moves them.
AsciiDoc [1] is written at the call site (including in a cell); the published page still lists those notes
under the table.
Collector collection-index and document-header tables stay in the page arabic series.
A footnote inside another footnote is one level of letters a, b at the end of the parent note.
There is no nested hover tip: the outer tooltip shows inner letter markers, not a nested list.
Markdown and AsciiDoc get nested notes only if the processor emits an inner stub.
TEI note place="end" inside another place="end" does.
place="foot" nests the same way.
A note of either place inside the other is a nested letter of its own series.
TEI converts note place="foot" and note place="end".
place="foot" is the source-footnote series (div.footnotes.source-footnotes), listed before the endnotes.
A source marker is the text color and roman.
place="end" is the encoder endnote series.
An encoder marker is italic, in the violet note color.
Danger red stays for a missing name, an unresolved citation, and a deletion.
An unplaced note is unchanged.
@n, when non-blank, is the marker at the call site and in the list.
Order and fragment ids stay on the generated series.
The same marker may appear on more than one note.
Obsidian inline ^[…] footnotes are unsupported.
- AsciiDoc
-
inline note, or a named definition reused with an empty macro: A
[2]in a table cell is written at the call site; the published page lists it under the table.See this footnote:[A note.] and again footnote:fn[Same note.] later footnote:fn[]. - Markdown
-
FlexMark: use
[^id]in the text; define[^id]:later. Obsidian supports that form natively (Help: Footnotes). Obsidian also has inline^[…]footnotes; this publisher does not. A[^id]in a table cell uses the same[^id]:body at the file end; the published page moves it under the table.See this [^note] and again [^note]. [^note]: A note.
- TEI
-
definition and use are the same element at the point of reference (
@n, when non-blank, is the marker):<p>See this<note place="end">A note.</note>.</p>
A non-blank
@nis that marker. The next note with no@nis still numbered in series.<p>See this<note place="end" n="*">A note.</note> and <note place="end">the next.</note>.</p>
A
place="end"inside anotherplace="end"is a nested letter on the parent note:<p>See this<note place="end">Outer<note place="end">Inner.</note></note>.</p>
- DocBook
-
<footnote>at the call site (xml:idoptional; non-blanklabelis the same marker). Reuse with<footnoteref linkend="id"/>. A<footnote>inside another footnote’s<para>is nested the same way if the converter emits an inner stub.<para>See this<footnote><para>A note.</para></footnote>.</para>
- HTML
-
IR stubs (same
footnote-correlation-idon the call site and the body):<p>See this<span class="footnote-link" footnote-correlation-id="note"></span>.</p> <span class="footnote" footnote-correlation-id="note">A note.</span>
A glossary is a description list marked as such. Term links get hover definitions (print inlines them in parentheses).
- AsciiDoc
-
[glossary]on the description list; term ids from. Reference a term with[id].See <<posuk>>. [glossary] [[posuk]]posuk:: verse - Markdown
-
PHP Extra definition lists (https://michelf.ca/projects/php-markdown/extra/#def-list) via FlexMark Definition List Extension. Mark the list with
{:.glossary}after it (Kramdown IAL, same family as Table of contents). Without that marker the list is not a glossary (no term ids, no tooltips). The marker also separates adjacent lists.
Obsidian does not render PHP Extra definition lists natively (Markdown Guide: Obsidian).
Community plugin Pandoc Extended Markdown renders Pandoc/PHP
Extra term / : definition lists.
{:.glossary} is Kramdown IAL, not Obsidian; it shows as a paragraph in the note.
[[#id]] heading links are native Obsidian.
+
See [[#posuk]].
posuk
: verse
{:.glossary}Term id is the id on <dt> if present, otherwise the term text with spaces turned into hyphens (Alter Rebbe →
Alter-Rebbe).
Reference a term with [[#id]] or [text](#id).
A lone : after a Markdown term is not a reliable empty definition: FlexMark can take the next term as that definition.
Write a real definition line, or omit the term.
- TEI
-
<list type="gloss">(ortype="glossary") of<label>/<item>. Term id fromxml:idon the label, else on the item, else the label text with spaces turned into hyphens. Reference a term with<ref target="#id">or<term ref="#id">.<p>See <ref target="#posuk">posuk</ref>.</p> <list type="gloss"> <label xml:id="posuk">posuk</label> <item>verse</item> </list>
- DocBook
-
<glosslist>(or<glossary>) of<glossentry>/<glossterm>/<glossdef>. Term id fromxml:idon the entry, else the term, else the term text with spaces turned into hyphens. Reference a term with<link linkend="id">.<variablelist>is a description list, not a glossary.<para>See <link linkend="posuk">posuk</link>.</para> <glosslist> <glossentry xml:id="posuk"> <glossterm>posuk</glossterm> <glossdef><para>verse</para></glossdef> </glossentry> </glosslist>
- HTML
-
the IR shape (
class="glossary"; each term isclass="glossary-item"with anid):<p>See <a href="#posuk">posuk</a>.</p> <div class="glossary"> <div class="glossary-item" id="posuk"> <dt>posuk</dt> <dd>verse</dd> </div> </div>
Two kinds, per document (no site-wide file or style). They can appear in the same document.
Front matter:
-
bibliography— path to a.bibfile, relative to this source file (.bibfiles are not copied to the site; un-ignore with_site_ignoreif you want one published) -
csl— CSL style name from the bundled styles (for exampleapa,ieee) -
lang— CSL locale; falls back to the sitelang, thenen-US
Both bibliography and csl are required.
Only works cited on the page appear in the list.
In-text cites become links to #bibl-{key} on the matching list entry (first key if a cite names several).
Unknown keys are highlighted and recorded as a page error.
- AsciiDoc
-
no
asciidoctor-bibliographygem. Alsocite:key[]/cite:key[p. 12](standard inline-macro form).- `cite:[knuth79]`, `cite:[knuth79, p. 12]`, `cite:[knuth79, lamport94]` — parenthetical - `cite:knuth79[]`, `cite:knuth79[p. 12]` — same, standard inline-macro form - `citenp:[lamport94]` — narrative (author in the sentence) - `bibliography::[]` — list placeholder (otherwise the list is appended)
- Markdown
-
Pandoc citation syntax (no extra FlexMark extension).
me@host.comis left as an email.@keyinside inline code or a fenced block stays as written. Obsidian does not format[@key]natively. Community plugin Pandoc Reference List recognizes Pandoc citekeys in the note (sidebar; optional inline). Citations inserts[@key]from a.bibfile.- `[@knuth79]`, `[@knuth79, p. 12]`, `[@knuth79; @lamport94]` — parenthetical - `@knuth79` — narrative (`me@host.com` is left as an email) - `@hand` inside inline code, or the same in a fenced block, is not a citation - `[-@lamport94]` — suppress author - `:::bibliography` — list placeholder See [@knuth79] and [@knuth79, p. 12] and [@knuth79; @lamport94]
- DocBook
-
<citation>key</citation>(optional locator after a comma).biblioref@linkendthat is not a native entry id is the same. Empty<bibliography/>is the list placeholder (otherwise the list is appended).<para>See <citation>knuth79</citation> and <citation>knuth79, p. 12</citation>.</para> <bibliography/>
- HTML
-
the IR shape:
<p>See <span class="citation" data-mode="parenthetical"><span class="citation-item" data-key="knuth79"></span></span> and <span class="citation" data-mode="parenthetical"><span class="citation-item" data-key="knuth79" data-locator="p. 12"></span></span>.</p> <div class="bibliography"></div>
- TEI
-
@cRefonref/ptris the citeproc key.@nis an optional locator. Empty<div type="bibliography"/>is the list placeholder (otherwise the list is appended).<p>See <ref cRef="knuth79"/> and <ptr cRef="lamport94" n="p. 12"/>.</p> <div type="bibliography"/>
Authored entries stay on the page (not cited-only).
Links to an entry id get a hover tip.
Entry ids are as written (#knuth79); they do not use the #bibl- prefix of the CSL list, so the same key can be cited
both ways in one document.
- AsciiDoc
-
[bibliography]list with[[[id]]]/[[[id,xreftext]]]anchors; cite with[id].See <<knuth79>> and <<lamport94>>. [bibliography] * [[[knuth79]]] Knuth, Donald E. _TeX and Metafont_. 1979. * [[[lamport94,Lamport 94]]] Lamport, Leslie. _LaTeX_. 1994.
- TEI
-
ref/ptr@targetthexml:idof abibl/biblStructinlistBibl(not inteiHeader). Emptyptrshows@nor the id.citwith aquoteis a quote, not this. A bare@targetthat matches alistBiblid is treated as#id.<p>See <ref target="#knuth79">Knuth 1979</ref> and <ptr target="#lamport94"/>.</p> <listBibl> <bibl xml:id="knuth79">Knuth, Donald E. <title>TeX and Metafont</title>. 1979.</bibl> <biblStruct xml:id="lamport94"> <monogr><author>Lamport, Leslie</author><title>LaTeX</title></monogr> </biblStruct> </listBibl>
- DocBook
-
<bibliography>withbiblioentry/bibliomixed; cite with<link linkend="id">or<biblioref linkend="id"/>(emptybibliorefshows@xreflabelor the id).<para>See <link linkend="knuth79">Knuth 1979</link> and <biblioref linkend="lamport94"/>.</para> <bibliography> <biblioentry xml:id="knuth79">Knuth, Donald E. TeX and Metafont. 1979.</biblioentry> <bibliomixed xml:id="lamport94">Lamport, Leslie. LaTeX. 1994.</bibliomixed> </bibliography>
Markdown has no native in-document bibliography list.
Inline code and fenced blocks.
A language class on <code> (language-scala, …)
loads highlight.js; language-mermaid loads Pele instead.
- Markdown
-
CommonMark inline
and fenced blocks. Obsidian supports both natively, including a language identifier (Help: Code). Fencedcode```mermaiddiagrams are native in Obsidian too (Help: Diagram); this publisher loads Pele forlanguage-mermaid.Use `map` in ```scala xs.map(f) ```
- AsciiDoc
-
inline
;code[source]listing (optional language).Use `map` in [source,scala] ---- xs.map(f) ----
- TEI
-
<code>(tagdocs module) with@langfor the formal language (notxml:lang). A newline in the content is treated as a block and wrapped in<pre>.<eg>/<egXML>are not converted.<p>Use <code lang="scala">map</code> in</p> <code lang="scala">xs.map(f) </code>
- DocBook
-
<code>/<literal>inline;<programlisting>/<screen>/<literallayout>as a block.@language(notxml:lang) is the highlight.js tag.<para>Use <code language="scala">map</code> in</para> <programlisting language="scala">xs.map(f) </programlisting>
- HTML
-
<p>Use <code>map</code> in</p> <pre><code class="language-scala">xs.map(f) </code></pre>
Numbered annotations on lines of verbatim (listing, literal, or source), plus a list of those annotations. Not a general footnote mechanism.
- AsciiDoc
-
<1>(or<.>for automatic numbering) at the end of a line; repeat the numbers in a callout list below. Hide a marker in the raw source behind a line comment (// <1>,# <1>,<!--1-→).[source,ruby] ---- require 'sinatra' (1) ---- <1> Library import
- DocBook
-
<co>in a listing;<calloutlist>/<callout arearefs>below.@labeloncois the marker number.<programlisting>require 'sinatra'<co xml:id="co1" label="1"/></programlisting> <calloutlist> <callout arearefs="co1"><para>Library import</para></callout> </calloutlist>
- HTML
-
the IR shape:
<pre><code>require 'sinatra' <span class="callout" data-value="1">1</span></code></pre> <ol class="callout-list"> <li>Library import</li> </ol>
Typed notices (note, tip, warning, …). Not listing Callouts and not asides/sidebars.
- Markdown
-
Obsidian core callouts (
> [!type]; optional title;+/-to fold). No plugin.> [!tip] Save time > Use the shortcut. > [!faq]- Hidden > Secret.
- AsciiDoc
-
NOTE:/TIP:/IMPORTANT:/CAUTION:/WARNING:(paragraph or[NOTE]====block; optional.Title).NOTE: Auxiliary information. .Watch out [WARNING] ==== Multi-paragraph body. ====
- DocBook
-
note,tip,warning,caution,important(optional childtitle).<note><title>Save time</title><para>Use the shortcut.</para></note>
- HTML
-
the IR shape (
detailswhen folded):<div class="admonition" data-type="tip"> <div class="admonition-title">Save time</div> <p>Use the shortcut.</p> </div>
Untyped auxiliary content next to the main flow. Not an admonition.
- AsciiDoc
-
[sidebar]or a**block (optional.Title)..Optional Title **** Auxiliary content. ****
- Markdown
-
no native syntax; Obsidian has none. Write an HTML
block.<aside> <p>From Markdown.</p> </aside>
- DocBook
-
<sidebar>(optional childtitle).<sidebar> <title>Optional Title</title> <para>Auxiliary content.</para> </sidebar>
- HTML
-
<aside>(IR addsclass="aside"if missing); optional title:<aside class="aside"> <div class="aside-title">Optional Title</div> <p>Auxiliary content.</p> </aside>
Block quotations.
Not Admonitions (> [!tip]) and not Asides.
- AsciiDoc
-
[quote](or a__block); optional.Title; attribution and citation title as[quote, Author, Source]..A title [quote, Jefferson, Papers] ____ A little rebellion now and then is a good thing. ____
- Markdown
-
a CommonMark blockquote (
>). No attribution syntax (an em-dash line is still quote body). Obsidian> [!type]is an admonition, not a quote.> A Markdown quotation. - DocBook
-
<blockquote>or<epigraph>; optionaltitleandattribution. Inline<quote>stays<q>.<blockquote> <title>A title</title> <para>A little rebellion now and then is a good thing.</para> <attribution>Jefferson</attribution> </blockquote>
- HTML
-
<blockquote>(IR addsclass="quote"if missing); optional title and<footer>attribution (citekept as-is):<blockquote class="quote"> <div class="quote-title">A title</div> <p>A little rebellion now and then is a good thing.</p> <footer class="quote-attribution">— Jefferson<br/><cite>Papers</cite></footer> </blockquote>
- TEI
-
<quote>;<cit>groups a quote with<bibl>(attribution). A<bibl>directly in<quote>is attribution too.<q>stays inline.<cit> <quote>A TEI quotation.</quote> <bibl>Jefferson, <title>Papers</title></bibl> </cit>
Deleted or superseded text.
HTML <del> (browser line-through).
- Markdown
-
GFM
~text~(Obsidian native; FlexMark strikethrough extension).See ~~struck out~~ here.
- AsciiDoc
-
text.See [line-through]#obsolete phrase# here. - TEI
-
<del>.<p>See <del>TEI struck</del>.</p>
- DocBook
-
<emphasis role="strikethrough">(orline-through). Default<emphasis>is<em>;role="bold"is<strong>.<para>See <emphasis role="strikethrough">DocBook struck</emphasis>.</para>
- HTML
-
<del>(IR);<s>andspan/markwithclass="line-through"become<del>:<p>See <del>HTML struck</del>.</p>
A block image (or other graphic) with an optional caption.
Inline images stay <img>.
Local src values are resolved to a site path (/pixel.svg); a missing file is an error (missing asset) and the
<img> gets unresolved-asset.
Wiki ![[image]] also finds a unique file of that name anywhere in the source tree.
- Markdown
-
a
on its own line; optional"title"is the caption. FlexMark does not turn Obsidian|WIDTH/|WIDTHxHEIGHTin the alt intoimgwidth/height(the size stays inalt). Obsidian![[image]]embeds stay<img>(resolved later);|WIDTHor|WIDTHxHEIGHTon that wiki embed do becomewidth/height. ![[pixel.svg|320]] ![[pixel.svg|320x240]]
- AsciiDoc
-
block
image::(optional.Title). Inlineis not a figure. Width and height (image::file.png[alt, 320, 240]orwidth=320, height=240) are kept on the<img>..A figure caption image::pixel.svg[A square]
- TEI
-
<figure>with<graphic url>and optional<head>(caption).<graphic>becomes<img>.<figure> <head>A TEI figure</head> <graphic url="pixel.svg"/> </figure>
- DocBook
-
<figure>(or<informalfigure>) with<imagedata fileref>and optional<title>(caption).<figure> <title>A DocBook figure</title> <mediaobject> <imageobject> <imagedata fileref="pixel.svg"/> </imageobject> </mediaobject> </figure>
- HTML
-
<figure>(IR addsclass="figure"if missing); optional<figcaption>:<figure class="figure"> <img src="pixel.svg" alt="A square"/> <figcaption class="figure-caption">HTML figure</figcaption> </figure>
A PDF file shown on the page. Not PDF generation of a page.
- Markdown
-
Obsidian
![[file.pdf]]; optional#page=Nand&height=H(pixels if H is digits). Alias![[file.pdf|Title]]is the link label.![[sample.pdf]]
- AsciiDoc
-
no native block; write the HTML IR (passthrough
).
DocBook has no native PDF-embed markup.
- HTML
-
<object type="application/pdf">(IR wraps it) or the IR:<div class="pdf-embed"> <object data="sample.pdf" type="application/pdf" aria-label="sample.pdf"> <a href="sample.pdf">Open PDF: sample.pdf</a> </object> <p class="pdf-embed-link"><a href="sample.pdf">Open PDF: sample.pdf</a></p> </div>
The sibling link is always visible and is what remains in print (the inner viewer is hidden).
data (and the fallback href) are resolved like other local assets; #page=N is kept.
A local video file, or a YouTube/Vimeo player.
Local files use HTML <video> (like <audio>).
Local src is resolved like images; YouTube/Vimeo <iframe> URLs are left alone.
- Markdown
-
Obsidian
![[file.mp4]](alsowebm,ogv,m4v). Alias![[file.mp4|Title]]is the fallback link label.![[clip.mp4]]
- AsciiDoc
-
video::file.mp4[](optional.Titlebecomes a figure caption). YouTube/Vimeo:video::id[youtube]/video::id[vimeo].video::clip.mp4[] - DocBook
-
<videodata fileref>. A YouTube/Vimeo URL becomes an iframe.<mediaobject> <videoobject> <videodata fileref="clip.mp4"/> </videoobject> </mediaobject>
- HTML
-
<video src>(IR addsclass="video"andcontrols); a YouTube/Vimeo<iframe>getsclass="video-embed":<video class="video" src="clip.mp4" controls="controls"> <a href="clip.mp4">Open video: clip.mp4</a> </video>