Commit 69e0d84e authored by hyeryung's avatar hyeryung

init

parent 01afa4f0
Pipeline #18789 failed with stages
in 1 minute and 56 seconds
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
{
"extends": "standard",
"rules": {
"arrow-parens": ["error", "always"],
"comma-dangle": ["error", {
"arrays": "always-multiline",
"objects": "always-multiline",
"imports": "always-multiline",
"exports": "always-multiline"
}],
"no-restricted-properties": ["error", {
"property": "substr",
"message": "Use String#slice instead."
}],
"max-len": [1, 120, 2],
"spaced-comment": "off",
"radix": ["error", "always"]
}
}
/build/
/node_modules/
/public/
image: node:10.14.2-stretch
stages: [setup, verify, deploy]
install:
stage: setup
cache:
paths:
- .cache/npm
script:
- &npm_install
npm install --quiet --no-progress --cache=.cache/npm
lint:
stage: verify
cache: &pull_cache
policy: pull
paths:
- .cache/npm
script:
- *npm_install
- node_modules/.bin/gulp lint
bundle-stable:
stage: deploy
only:
- master@antora/antora-ui-default
cache: *pull_cache
script:
- *npm_install
- node_modules/.bin/gulp bundle
artifacts:
paths:
- build/ui-bundle.zip
bundle-dev:
stage: deploy
except:
- master
cache: *pull_cache
script:
- *npm_install
- node_modules/.bin/gulp bundle
artifacts:
expire_in: 1 day # unless marked as keep from job page
paths:
- build/ui-bundle.zip
pages:
stage: deploy
only:
- master@antora/antora-ui-default
cache: *pull_cache
script:
- *npm_install
- node_modules/.bin/gulp preview:build
# FIXME figure out a way to avoid copying these files to preview site
- rm -rf public/_/{helpers,layouts,partials}
artifacts:
paths:
- public
{
"description": "Build tasks for the Antora default UI project",
"flags.tasksDepth": 1
}
{
"extends": "stylelint-config-standard",
"rules": {
"comment-empty-line-before": null,
"no-descending-specificity": null,
}
}
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
This Source Code Form is subject to the terms of the Mozilla Public
License, v. 2.0. If a copy of the MPL was not distributed with this
file, You can obtain one at http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
= Antora Default UI
// Settings:
:experimental:
:hide-uri-scheme:
// Project URLs:
:url-project: https://gitlab.com/antora/antora-ui-default
:url-preview: https://antora.gitlab.io/antora-ui-default
:url-ci-pipelines: {url-project}/pipelines
:img-ci-status: {url-project}/badges/master/pipeline.svg
// External URLs:
:url-antora: https://antora.org
:url-antora-docs: https://docs.antora.org
:url-git: https://git-scm.com
:url-git-dl: {url-git}/downloads
:url-gulp: http://gulpjs.com
:url-opendevise: https://opendevise.com
:url-nodejs: https://nodejs.org
:url-nvm: https://github.com/creationix/nvm
:url-nvm-install: {url-nvm}#installation
:url-source-maps: https://developer.mozilla.org/en-US/docs/Tools/Debugger/How_to/Use_a_source_map
image:{img-ci-status}[CI Status (GitLab CI), link={url-ci-pipelines}]
This project is an archetype that demonstrates how to produce a UI bundle that can be used by {url-antora}[Antora] to generated a documentation site.
You can see a preview of the default UI at {url-preview}.
While the default UI is ready to be used with Antora, the intent is that you'll fork it and customize it for your own needs.
It's intentionally minimalistic so as to give you a good starting point without requiring too much effort to customize.
== Code of Conduct
The Antora project and its project spaces are governed by our https://gitlab.com/antora/antora/-/blob/HEAD/CODE-OF-CONDUCT.adoc[Code of Conduct].
By participating, you're agreeing to honor this code.
Let's work together to make this a welcoming, professional, inclusive, and safe environment for everyone.
== Use the Default UI
If you want to simply use the default UI for your Antora-generated site, add the following UI configuration to your playbook:
[source,yaml]
----
ui:
bundle:
url: https://gitlab.com/antora/antora-ui-default/-/jobs/artifacts/HEAD/raw/build/ui-bundle.zip?job=bundle-stable
snapshot: true
----
NOTE: The `snapshot` flag tells Antora to fetch the UI when the `--fetch` command-line flag is present.
This setting is required because updates to the UI bundle are pushed to the same URL.
If the URL were to be unique, this setting would not be required.
Read on to learn how to customize the default UI for your own documentation.
== Development Quickstart
This section offers a basic tutorial to teach you how to set up the default UI project, preview it locally, and bundle it for use with Antora.
A more comprehensive tutorial can be found in the documentation at {url-antora-docs}.
=== Prerequisites
To preview and bundle the default UI, you need the following software on your computer:
* {url-git}[git] (command: `git`)
* {url-nodejs}[Node.js] (commands: `node` and `npm`)
* {url-gulp}[Gulp CLI] (command: `gulp`)
==== git
First, make sure you have git installed.
$ git --version
If not, {url-git-dl}[download and install] the git package for your system.
==== Node.js
Next, make sure that you have Node.js installed (which also provides npm).
$ node --version
If this command fails with an error, you don't have Node.js installed.
If the command doesn't report an LTS version of Node.js (e.g., v10.15.3), it means you don't have a suitable version of Node.js installed.
In this guide, we'll be installing Node.js 10.
While you can install Node.js from the official packages, we strongly recommend that you use {url-nvm}[nvm] (Node Version Manager) to manage your Node.js installation(s).
Follow the {url-nvm-install}[nvm installation instructions] to set up nvm on your machine.
Once you've installed nvm, open a new terminal and install Node.js 10 using the following command:
$ nvm install 10
You can switch to this version of Node.js at any time using the following command:
$ nvm use 10
To make Node.js 10 the default in new terminals, type:
$ nvm alias default 10
Now that you have Node.js installed, you can proceed with installing the Gulp CLI.
==== Gulp CLI
You'll need the Gulp command-line interface (CLI) to run the build.
The Gulp CLI package provides the `gulp` command which, in turn, executes the version of Gulp declared by the project.
You can install the Gulp CLI globally (which resolves to a location in your user directory if you're using nvm) using the following command:
$ npm install -g gulp-cli
Verify the Gulp CLI is installed and on your PATH by running:
$ gulp --version
If you prefer to install global packages using Yarn, run this command instead:
$ yarn global add gulp-cli
Alternately, you can use the `gulp` command that is installed by the project's dependencies.
$ npx --offline gulp --version
Now that you have the prerequisites installed, you can fetch and build the UI project.
=== Clone and Initialize the UI Project
Clone the default UI project using git:
[subs=attributes+]
$ git clone {url-project} &&
cd "`basename $_`"
The example above clones Antora's default UI project and then switches to the project folder on your filesystem.
Stay in this project folder when executing all subsequent commands.
Use npm to install the project's dependencies inside the project.
In your terminal, execute the following command:
$ npm install
This command installs the dependencies listed in [.path]_package.json_ into the [.path]_node_modules/_ folder inside the project.
This folder does not get included in the UI bundle and should _not_ be committed to the source control repository.
[TIP]
====
If you prefer to install packages using Yarn, run this command instead:
$ yarn
====
=== Preview the UI
The default UI project is configured to preview offline.
The files in the [.path]_preview-src/_ folder provide the sample content that allow you to see the UI in action.
In this folder, you'll primarily find pages written in AsciiDoc.
These pages provide a representative sample and kitchen sink of content from the real site.
To build the UI and preview it in a local web server, run the `preview` command:
$ gulp preview
You'll see a URL listed in the output of this command:
....
[12:00:00] Starting server...
[12:00:00] Server started http://localhost:5252
[12:00:00] Running server
....
Navigate to this URL to preview the site locally.
While this command is running, any changes you make to the source files will be instantly reflected in the browser.
This works by monitoring the project for changes, running the `preview:build` task if a change is detected, and sending the updates to the browser.
Press kbd:[Ctrl+C] to stop the preview server and end the continuous build.
=== Package for Use with Antora
If you need to package the UI so you can use it to generate the documentation site locally, run the following command:
$ gulp bundle
If any errors are reported by lint, you'll need to fix them.
When the command completes successfully, the UI bundle will be available at [.path]_build/ui-bundle.zip_.
You can point Antora at this bundle using the `--ui-bundle-url` command-line option.
If you have the preview running, and you want to bundle without causing the preview to be clobbered, use:
$ gulp bundle:pack
The UI bundle will again be available at [.path]_build/ui-bundle.zip_.
==== Source Maps
The build consolidates all the CSS and client-side JavaScript into combined files, [.path]_site.css_ and [.path]_site.js_, respectively, in order to reduce the size of the bundle.
{url-source-maps}[Source maps] correlate these combined files with their original sources.
This "`source mapping`" is accomplished by generating additional map files that make this association.
These map files sit adjacent to the combined files in the build folder.
The mapping they provide allows the debugger to present the original source rather than the obfuscated file, an essential tool for debugging.
In preview mode, source maps are enabled automatically, so there's nothing you have to do to make use of them.
If you need to include source maps in the bundle, you can do so by setting the `SOURCEMAPS` environment variable to `true` when you run the bundle command:
$ SOURCEMAPS=true gulp bundle
In this case, the bundle will include the source maps, which can be used for debugging your production site.
== Copyright and License
Copyright (C) 2017-present OpenDevise Inc. and the Antora Project.
Use of this software is granted under the terms of the https://www.mozilla.org/en-US/MPL/2.0/[Mozilla Public License Version 2.0] (MPL-2.0).
See link:LICENSE[] to find the full license text.
== Authors
Development of Antora is led and sponsored by {url-opendevise}[OpenDevise Inc].
name: antora-ui-default
title: Antora Default UI
version: ~
nav:
- modules/ROOT/nav.adoc
'use strict'
module.exports = (numOfItems, { data }) => {
const { contentCatalog, site } = data.root
if (!contentCatalog) return
const rawPages = getDatedReleaseNotesRawPages(contentCatalog)
const pageUiModels = turnRawPagesIntoPageUiModels(site, rawPages, contentCatalog)
return getMostRecentlyUpdatedPages(pageUiModels, numOfItems)
}
let buildPageUiModel
function getDatedReleaseNotesRawPages (contentCatalog) {
return contentCatalog.getPages(({ asciidoc, out }) => {
if (!asciidoc || !out) return
return getReleaseNotesWithRevdate(asciidoc)
})
}
function getReleaseNotesWithRevdate (asciidoc) {
const attributes = asciidoc.attributes
return asciidoc.attributes && isReleaseNotes(attributes) && hasRevDate(attributes)
}
function isReleaseNotes (attributes) {
return attributes['page-component-name'] === 'release-notes'
}
function hasRevDate (attributes) {
return 'page-revdate' in attributes
}
function turnRawPagesIntoPageUiModels (site, pages, contentCatalog) {
buildPageUiModel ??= module.parent.require('@antora/page-composer/build-ui-model').buildPageUiModel
return pages
.map((page) => buildPageUiModel(site, page, contentCatalog))
.filter((page) => isValidDate(page.attributes?.revdate))
.sort(sortByRevDate)
}
function isValidDate (dateStr) {
return !isNaN(Date.parse(dateStr))
}
function sortByRevDate (a, b) {
return new Date(b.attributes.revdate) - new Date(a.attributes.revdate)
}
function getMostRecentlyUpdatedPages (pageUiModels, numOfItems) {
return getResultList(pageUiModels, Math.min(pageUiModels.length, numOfItems))
}
function getResultList (pageUiModels, maxNumberOfPages) {
const resultList = []
for (let i = 0; i < maxNumberOfPages; i++) {
const page = pageUiModels[i]
if (page.attributes?.revdate) resultList.push(getSelectedAttributes(page))
}
return resultList
}
function getSelectedAttributes (page) {
const latestVersion = getLatestVersion(page.contents.toString())
return {
latestVersionAnchor: latestVersion?.anchor,
latestVersionName: latestVersion?.innerText,
revdateWithoutYear: removeYear(page.attributes?.revdate),
title: cleanTitle(page.title),
url: page.url,
}
}
function getLatestVersion (contentsStr) {
const firstVersion = contentsStr.match(/<h2 id="([^"]+)">(.+?)<\/h2>/)
if (!firstVersion) return
const result = { anchor: firstVersion[1] }
if (isVersion(firstVersion[2])) result.innerText = firstVersion[2]
return result
}
function isVersion (versionText) {
return /^[0-9]+\.[0-9]+(?:\.[0-9]+)?/.test(versionText)
}
function removeYear (dateStr) {
if (!isValidDate(dateStr)) return
const dateObj = new Date(dateStr)
return `${dateObj.toLocaleString('default', { month: 'short' })} ${dateObj.getDate()}`
}
function cleanTitle (title) {
return title.split('Release Notes')[0].trim()
}
* xref:prerequisites.adoc[]
* xref:set-up-project.adoc[]
* xref:build-preview-ui.adoc[]
* xref:development-workflow.adoc[]
* xref:templates.adoc[]
** xref:template-customization.adoc[]
** xref:create-helper.adoc[]
* xref:add-static-files.adoc[]
* xref:stylesheets.adoc[]
** xref:add-fonts.adoc[]
* xref:style-guide.adoc[]
** xref:inline-text-styles.adoc[]
** xref:image-styles.adoc[]
** xref:admonition-styles.adoc[]
** xref:code-blocks.adoc[]
** xref:list-styles.adoc[]
** xref:sidebar-styles.adoc[]
** xref:ui-macro-styles.adoc[]
= Add Fonts
This page explains how to add new fonts to your UI.
These instructions assume you've forked the default UI and are able to customize it.
There are three steps involved:
. Add the font to your UI project
. Add a font-face declaration to your stylesheet
. Use the new font in your stylesheet
How you reference the font file in the font-face declaration depends on how you decide to manage it.
You can manage the font with npm or download it manually and add it directly to your UI project.
The following sections describe each approach in turn.
== npm managed
You can use npm (or Yarn) to manage the font.
This approach saves you from having to store the font file directly in your UI project.
Here are the steps involved.
. Use npm (or Yarn) to install the font files from a package (e.g., https://www.npmjs.com/package/typeface-open-sans[typeface-open-sans])
$ npm i --save typeface-open-sans
. In [.path]_src/css_, add a corresponding CSS file (e.g., [.path]_typeface-open-sans.css_)
. In that file, declare the font face:
+
[source,css]
----
@font-face {
font-family: "Open Sans";
font-style: normal;
font-weight: 400;
src:
local("Open Sans"),
local("Open Sans-Regular"),
url(~typeface-open-sans/files/open-sans-latin-400.woff) format("woff")
}
----
+
The Gulp build recognizes the `~` URL prefix and copies the font from the npm package to the build folder (and hence bundle).
+
You must define one @font-face for each font weight and style combination (e.g., `font-weight: 500` + `font-style: italic`) from the font that you want to use in your stylesheet.
. Import the typeface CSS file you just created into the main stylesheet, [.path]_src/css/site.css_ (adjacent to the other typeface imports):
+
[source,css]
----
@import "typeface-open-sans.css";
----
. Repeat the previous steps for each font style and weight you want to use from that package.
. Change the CSS to use your newly imported font:
+
[source,css]
----
html {
font-family: "Open Sans", sans;
}
----
+
TIP: If you're building on the default UI, you may instead want to define or update the font family using a variable defined in [.path]_src/css/vars.css_.
. Test the new font by previewing your UI:
$ npx gulp preview
If you see the new font, you've now successfully added it to your UI.
If you aren't sure, look for the https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Edit_fonts[All fonts on page] section in your browser's developer tools to see whether the font was loaded.
== Manual
A simpler approach to adding fonts is to store them directly in your project.
Here are the steps involved.
. Download the font files and add them to the [.path]_src/font_ folder.
Create this folder if it does not exist.
. In [.path]_src/css_, add a corresponding CSS file (e.g., [.path]_typeface-open-sans.css_)
. In that file, declare the font face:
+
[source,css]
----
@font-face {
font-family: "Open Sans";
font-style: normal;
font-weight: 400;
src:
local("Open Sans"),
local("Open Sans-Regular"),
url(../font/open-sans-latin-400.woff) format("woff")
}
----
+
Note that the path is a relative path starting from the [.path]_src/css_ folder to the [.path]_src/font_ folder.
+
You must define one @font-face for each font weight and style combination (e.g., `font-weight: 500` + `font-style: italic`) from the font that you want to use in your stylesheet.
. Import the typeface CSS file you just created into the main stylesheet, [.path]_src/css/site.css_ (adjacent to the other typeface imports):
+
[source,css]
----
@import "typeface-open-sans.css";
----
. Repeat the previous steps for each font style and weight you want to use.
. Change the CSS to use your newly imported font:
+
[source,css]
----
html {
font-family: "Open Sans", sans;
}
----
+
TIP: If you're building on the default UI, you may instead want to define or update the font family using a variable defined in [.path]_src/css/vars.css_.
. Test the new font by previewing your UI:
$ npx gulp preview
If you see the new font, you've now successfully added it to your UI.
If you aren't sure, look for the https://developer.mozilla.org/en-US/docs/Tools/Page_Inspector/How_to/Edit_fonts[All fonts on page] section in your browser's developer tools to see whether the font was loaded.
= Add Static Files
A static UI file is any file provided by the UI that's copied directly to your site.
A common example of a static file is a favicon image.
One way to add additional static files is by using the xref:antora:playbook:ui-supplemental-files.adoc[supplemental UI], which is defined in your Antora playbook.
This document explains how to add static files from the UI bundle instead.
A key benefit of putting static files in the UI bundle is that they can be shared across multiple projects instead of having to add them per project using the supplemental UI.
== How Antora identifies static UI files
Antora recognizes a number of non-publishable files and folders at the top level of a UI bundle.
This list consists of the folders [.path]_helpers_, [.path]_layouts_, and [.path]_partials_ and the optional [.path]_ui.yml_ file.
Although these files and folders are present in the UI bundle, they are not copied into the site's output directory.
In other words, they are _not_ static files.
Instead, they're used to compose the HTML pages that are published or for configuration.
Any files or folders from the UI bundle that Antora does not recognize are classified as static.
Static files and folders are automatically copied from the UI bundle to the site's output directory underneath the UI output directory (e.g., [.path]_++_++_), preserving the relative path of the file from the UI bundle.
Any files or folders that begin with a dot (i.e., hidden) are ignored unless explicitly listed as static files.
Antora's default UI already has several intrinsic static folders.
This list includes the [.path]_css_, [.path]_font_ (generated), [.path]_img_, and [.path]_js_ folders.
Although not used in the default UI, the UI build also recognizes another static folder named [.path]_src/static_.
If the UI build finds any files inside this folder, including any files that are nested, it copies them directly to the root of the UI bundle, preserving any subfolders.
Let's take advantage of this feature to add several new static files to the UI bundle.
First, create a folder under [.path]_src_ named [.path]_static_:
$ mkdir src/static
Let's now add several files, including one that is hidden, to the [.path]_src/static_ folder.
Here's a file tree that shows the location of these new files:
....
src/
static/
about/
notice.txt
.DS_Store
favicon.ico
favicon.png
....
Let's now consider how static files flow from the UI project to the published site.
We'll start with where these files reside within the UI project (non-static files not shown here):
.UI project
....
src/
...
css/
...
img/
back.svg
...
js/
...
static/
about/
notice.txt
.DS_Store
favicon.ico
favicon.png
....
The UI build will assemble the files into the UI bundle as follows:
.Contents of UI bundle
....
...
css/
site.css
font/
roboto-latin-400-italic.woff
...
img/
back.svg
...
js/
site.js
about/
notice.txt
.DS_Store
favicon.ico
favicon.png
....
NOTE: The [.path]_font_ folder is introduced by the UI build.
When using this UI bundle in an Antora build, the contents of the output directory of your site will look like this:
.Contents of site output directory
....
_/
css/
site.css
font/
roboto-latin-400-italic.woff
...
img/
back.svg
...
js/
site.js
about/
notice.txt
favicon.ico
favicon.png
component-a/
...
sitemap.xml
...
....
As stated earlier, static files are copied to the UI output directory (e.g., [.path]_++_++_) in the published site by default.
In this directory, we see the [.path]_css_, [.path]_font_, [.path]_img_ and [.path]_js_ folders, all static folders provided by the default UI.
We also see the [.path]_about_ folder and the favicon files we added to the UI project in the first step.
We do not see the [.path]_helpers_, [.path]_partials_, and [.path]_layouts_ folders since they're not static files.
We also don't see the [.path]_.DS_Store_ file since it's a hidden file.
If the UI output directory is where you want static files to end up, there's nothing else you have to do.
If you want all or some of the static files to be moved to the site root, you need to add additional configuration to promote them.
== Promote static files
If you want to promote certain static files to the root of your site, you need to identify them.
You identify them using the [.path]_ui.yml_ file.
This file, called the UI descriptor, is used to configure the behavior of the UI.
The UI descriptor must be located at the root of the UI bundle.
The UI descriptor has one required key, `static_files`, which is the one we're interested in.
The `static_files` key identifies files that should be promoted from the UI output directory to the site root (i.e., the site output directory).
The files you want to promote must be specified as an array, where each entry is either an exact relative path or a glob of relative paths in the UI bundle.
The exact path or path glob must match files, not folders.
Unlike implicit static files, promoted static files can begin with a dot (and are thus not ignored).
To configure static files to promote, start by creating the file [.path]_src/ui.yml_ in your UI project.
If this file exists, the UI build will copy the file from there to the root of the UI bundle.
Next, add the `static_files` key to this file and add an entry for the [.path]_favicon.ico_ file.
This entry will configure the UI to promote the favicon to the site root.
.src/ui.yml
[,yaml]
----
static_files:
- favicon.ico
----
If you have multiple favicon files with different suffixes or file extensions, you can match all of them using a wildcard (aka glob).
.src/ui.yml
[,yaml]
----
static_files:
- favicon*
----
With this configuration in place, Antora will read the favicon images from the UI bundle and copy them to the root of the site.
Static files that are not identified are still copied to UI output directory.
The result of the above [.path]_ui.yml_ would be the following:
.Contents of site output directory
....
_/
css/
site.css
font/
roboto-latin-400-italic.woff
...
img/
back.svg
...
js/
site.js
about/
notice.txt
component-a/
favicon.ico
favicon.png
...
sitemap.xml
...
....
Notice that the promoted favicon files are now at the site root rather than inside the UI output directory.
However, the [.path]_about_ folder is still inside the UI output directory.
Let's promote that one as well.
You can identify all files in a folder using the wildcard `+*+` in the last path segment, such as `+folder/*+`.
You can identify all files in a folder at any depth using the wildcard `+**+` in the last path segment, such as `+folder/**+`.
Matching a folder has no effect (e.g., `folder`).
Wildcards never match hidden files.
Hidden files must always be written using an exact path match.
Let's also promote all files in the [.path]_about_ folder by adding the wildcard match the `static_files` key in the [.path]_ui.yml_ file.
.src/ui.yml
....
static_files:
- favicon*
- about/*
....
Using this configuration, the [.path]_about_ folder will end up at the site root, adjacent to the favicon files, instead of inside the UI output directory.
Notice that the [.path]_about_ folder is copied too, not just its contents.
Now that the static files are where you want them, let's look at how to reference them from the HTML pages.
== Use the static files
Often when you add static files to your site, you need to reference them somewhere.
In the case of a favicon image, it must be referenced in the `<head>` of the HTML page.
If you are referencing a promoted static file, you'll use the prefix `+{{{siteRootPath}}}+`.
Otherwise, you'll use the prefix `+{{{uiRootPath}}}+`.
Let's update the [.path]_src/partials/head-icons.hbs_ partial to reference a promoted favicon image at the root of the site.
.src/partials/head-icons.hbs
[,yaml,indent=4]
----
<link rel="icon" href="{{{siteRootPath}}}/favicon.ico" type="image/x-icon">
----
Rebuild the UI with `gulp bundle`.
You should now see that your site has a favicon image that's provided by the UI bundle.
= Admonition Styles
:navtitle: Admonitions
An xref:antora:asciidoc:admonitions.adoc[admonition], also known as a notice, helps draw attention to content with a special label or icon.
== Admonition blocks
An admonition block is a table.
The table title element is specified by the block class: tip, note, important, warning, or caution.
Here's an AsciiDoc source example that produces an admonition with the table title warning:
[source,asciidoc]
----
WARNING: Watch out!
----
If font-based icons are enabled (`icons=font`), the table title text is replaced by the associated icon.
[source,html]
----
<div class="admonitionblock warning">
<table>
<tr>
<td class="icon">
<i class="fa icon-warning" title="Warning"></i>
</td>
<td class="content">
<div class="paragraph">
<p>Watch out!</p>
</div>
</td>
</tr>
</table>
</div>
----
Here's how it might appear when the title is displayed as text:
WARNING: Watch out!
= Build a UI Project for Local Preview
:navtitle: Build and Preview the UI
== Build Preview Site
Once you've modified the site UI, the first thing you'll want to do is check out how it looks.
That's what the files in the [.path]_preview-src/_ folder are for.
This folder contains HTML file fragments that provide a representative sample of content from the site.
The preview saves you from having to generate the whole site just to test the UI.
These files should give you an idea of how the UI will look when applied to the actual site.
The pages in the preview site are assembled using the Handlebars templates and link to the pre-compiled asset files (emulating the behavior of the site generator).
Thus, to look at them, you need to run them through the UI build.
There are two preview modes available.
You can run the build once and examine the result or you can run the build continuously so you can see changes as you make them.
The next two sections explain how to use these modes.
=== Build Once
To build the UI once for preview, then stop, execute the following command:
$ npx gulp preview:build
This task pre-compiles the UI files into the [.path]_public_ directory.
To view the preview pages, navigate to the HTML pages in the [.path]_public_ directory using your browser (e.g., [.path]_public/index.html_).
=== Build Continuously
To avoid the need to run the `preview:build` task over and over while developing, you can use the `preview` command instead to have it run continuously.
This task also launches a local HTTP server so updates get synchronized with the browser (i.e., "`live reload`").
To launch the preview server, execute the following command:
$ npx gulp preview
You'll see a URL listed in the output of this command:
....
[12:59:28] Starting 'preview:serve'...
[12:59:28] Starting server...
[12:59:28] Server started http://localhost:5252
[12:59:28] Running server
....
Navigate to the URL to view the preview site.
While this command is running, any changes you make to the source files will be instantly reflected in the browser.
This works by monitoring the project for changes, running the `preview:build` task if a change is detected, and sending the updates to the browser.
Press kbd:[Ctrl+C] to stop the preview server and end the continuous build.
== Package for Previewing
If you need to bundle the UI in order to preview the UI on the real site in local development, run the following command:
$ npx gulp bundle
The `bundle` command also invokes the `lint` command to check that the CSS and JavaScript follow the coding standards.
The UI bundle will be available at [.path]_build/ui-bundle.zip_.
You can then point Antora at this bundle using the `--ui-bundle-url` command-line option.
If you have the preview running, and you want to bundle without causing the preview to be clobbered, use:
$ npx gulp bundle:pack
The UI bundle will again be available at [.path]_build/ui-bundle.zip_.
= Code Blocks
:page-aliases: copy-to-clipboard.adoc
This page describes some of the styles and behaviors of code blocks and how to support them in a custom UI.
In AsciiDoc, code blocks a referred to as source and listing blocks.
A source block is a listing block that has a source language defined on it (e.g., javascript).
Refer to xref:antora:asciidoc:source.adoc[source blocks] to learn more about source blocks in AsciiDoc and how they are used in Antora.
Readers typically expect that a source block will be presented with syntax highlighting, so we'll start there.
== Syntax highlighting
Antora uses highlight.js as syntax highlighter in the AsciiDoc processor by default.
If you want to turn off syntax highlighting, you can do so by unsetting the `source-highlighter` attribute in the Antora playbook.
[,yaml]
----
asciidoc:
attributes:
source-highlighter: ~
----
If you soft unset the value instead (i.e., set the value to `false`), it allows individual pages to selectively turn syntax highlighting back on.
By default, Antora highlights a restricted set of languages in order to minimize the size of the supporting asset files.
However, you can add or remove support for languages in your own UI bundle.
To do so, follow these steps:
. Switch to your UI project.
. Open the file [.path]_src/js/vendor/highlight.bundle.js_.
. Add or remove one of the `registerLanguage` statements.
For example, to add AsciiDoc syntax highlighting, add the following line:
[,js]
----
hljs.registerLanguage('asciidoc', require('highlight.js/lib/languages/asciidoc'))
----
Keep in mind, the language must be supported by highlight.js.
To find out what languages are supported and how to abbreviate them, set the https://github.com/highlightjs/highlight.js/tree/9-18-stable/src/languages[languages] folder in the project on GitHub.
== Copy to clipboard
This page describes the copy to clipboard feature added to source blocks when using the default UI.
=== Source blocks
The default UI automatically adds a clipboard button to all source blocks and provides the necesssary CSS and JavaScript to support that behavior.
The clipboard button shows up next to the language label when the mouse is hovered over the block.
When the user clicks the clipboard button, the contents of the source block will be copied to the user's clipboard and a notification overlay will be briefly shown.
You can try this behavior below:
[,ruby]
----
puts 'Take me to your clipboard!'
----
IMPORTANT: Copy to clipboard only works for a local site or if the site is hosted over https (SSL).
The copy to clipboard does not work on an insecure site (http) since the clipboard API is not available in that environment.
In that case, the behavior gracefully degrades so the user will not see the clipboard button or an error.
=== Console blocks
The default UI also adds a clipboard button to all console blocks.
A console block is either a literal paragraph that begins with a `$` or a source block with the language `console`.
The script provided by the default UI will automatically strip the `$` prompt at the beginning of each line and join the lines with `&&`.
In <<ex-console-copy-paste>>, since the language is `console` and each line begins with a `$`, the console copy-paste logic is triggered.
.Copy to clipboard for a multi-line console command
[#ex-console-copy-paste]
------
[,console]
----
$ mkdir example
$ cd example
----
------
When a user uses the copy-to-clipboard button, they will copy the combined command `mkdir example && cd example` instead of the literal text shown.
This can be useful for tutorial examples that a user is expected to copy-paste to run.
You can try this behavior below:
[,console]
----
$ mkdir example
$ cd example
----
= Create a UI Helper
This page explains how to create a UI helper for use in a page template (layout or partial).
A helper is a JavaScript function that's invoked by Handlebars when it comes across a helper call in a template.
== Helper anatomy
A helper must be defined as a JavaScript file in the [.path]_helpers_ directory of the UI bundle.
The basename of the file without the file extension will be used as the function name.
For example, if the helper is located at [.path]_helpers/join.js_, the name of the function will be `join`.
You don't have to register the helper as Antora does that for you automatically.
This automatic behavior replaces this Handlebars API call (which you *don't* have to do):
[,js]
----
Handlebars.registerHelper('join', function () { ... })
----
The helper file should export exactly one default function.
The name of the function in the file does not matter.
Here's a template of a helper function you can use as a starting point:
.new-helper.js
[,js]
----
'use strict'
module.exports = () => {
return true
}
----
The return value of the function will be used in the logic in the template.
If the helper is used in a conditional, it should return a boolean value (as in the previous example).
If the helper is used to create output, it should return a string.
If the helper is used in an iteration loop, it should return a collection.
We can now use our conditional helper in a template as follows:
[,hbs]
----
{{#if (new-helper)}}
always true!
{{/if}}
----
The round brackets are always required around a helper function call (except in cases when they're implied by Handlebars).
The helper can access top-level variables in the template by accepting the template context as the final parameter.
The top-level variables are stored in in the `data.root` property of this object.
.new-helper.js
[,js]
----
'use strict'
module.exports = ({ data: { root } }) => {
return root.site.url === 'https://docs.example.org'
}
----
Now our condition will change:
[,hbs]
----
{{#if (new-helper)}}
Only true if the site URL is https://docs.example.org.
{{/if}}
----
A helper can also accept input parameters.
These parameters get inserted in the parameter list before the context object.
Handlebars only calls the function with the input parameters passed by the template, so it's important to use a fixed number of them.
Otherwise, the position of the context object will jump around.
.new-helper.js
[,js]
----
'use strict'
module.exports = (urlToCheck, { data: { root } }) => {
return root.site.url === urlToCheck
}
----
Now we can accept the URL to check as an input parameter:
[,hbs]
----
{{#if (new-helper 'https://docs.example.org')}}
Only true if the site URL matches the one specified.
{{/if}}
----
You can consult the https://handlebarsjs.com/guide/[Handlebars language guide] for more information about creating helpers.
== Use the content catalog in a helper
You can work directly with Antora's content catalog in a helper to work with other pages and resources.
Let's define a helper that assembles a collection of pages that have a given tag defined in the `page-tags` attribute.
The helper call will look something like this:
[,hbs]
----
{{#each (pages-with-tag 'tutorial')}}
----
We'll start by defining the helper in a file named [.path]_pages-with-tag.js_.
In this first iteration, we'll have it return a collection of raw virtual file objects from Antora's content catalog.
Populate the file with the following contents:
.pages-with-tag.js
[,js]
----
'use strict'
module.exports = (tag, { data }) => {
const { contentCatalog } = data.root
return contentCatalog.getPages(({ asciidoc, out }) => {
if (!out || !asciidoc) return
const pageTags = asciidoc.attributes['page-tags']
return pageTags && pageTags.split(', ').includes(tag)
})
}
----
Here we're obtaining a reference to the content catalog, then filtering the pages by our criteria using the `getPage()` method.
It's always good to check for the presence of the `out` property to ensure the page is publishable.
Here's how this helper is used in the template:
[,hbs]
----
{{#each (pages-with-tag 'tutorial')}}
<a href="{{{relativize ./pub.url}}}">{{{./asciidoc.doctitle}}}</a>
{{/each}}
----
You'll notice that the page objects in the collection differ from the typical page UI model.
We can convert each page to a page UI model before returning the collection.
Let's write the extension again, this time running each page through Antora's `buildPageUiModel` function:
.pages-with-tag.js
[,js]
----
'use strict'
module.exports = (tag, { data }) => {
const { contentCatalog, site } = data.root
const pages = contentCatalog.getPages(({ asciidoc, out }) => {
if (!out || !asciidoc) return
const pageTags = asciidoc.attributes['page-tags']
return pageTags && pageTags.split(', ').includes(tag)
})
const { buildPageUiModel } = require.main.require('@antora/page-composer/build-ui-model')
return pages.map((page) => buildPageUiModel(site, page, contentCatalog))
}
----
In this case, the usage of the item object is simpler and more familiar:
[,hbs]
----
{{#each (pages-with-tag 'tutorial')}}
<a href="{{{relativize ./url}}}">{{{./doctitle}}}</a>
{{/each}}
----
Using this helper as a foundation, you can implement a variety of customizations and custom collections.
CAUTION: Keep in mind that any helper you will use will be called for each page that uses the template.
This can impact performance.
If it's called on every page in your site, be sure that the operation is efficient to avoid slowing down site generation.
As an alternative to using a helper, you may want to consider whether writing an Antora extension is a better option.
== Find latest release notes
Here's another example of a helper that finds the latest release notes in a component named `release-notes`.
[,js]
----
include::example$latest-release-notes.js[]
----
Here's how might use it to create a list of release notes.
[,hbs]
----
<ul>
{{#each (latest-release-notes 10)}}
<li><a href="{{relativize ./url}}#{{./latestVersionAnchor}}">{{./title}} ({{./revdateWithoutYear}})</a></li>
{{/each}}
</ul>
----
= UI Development Workflow
// This section provides information about some of the UI files you'll be modifying and how to prepare and submit those changes.
All changes pushed to a UI project's default branch can trigger a new release (not described here).
Therefore, you want to make your changes to a development branch and submit it as a pull request (PR) to be approved.
(Even better would be to issue the PR from a fork).
Only when the PR is approved and merged will the new release be triggered.
== git steps
Use the following command to create a local development branch named `name-me`:
$ git checkout -b name-me -t origin/HEAD
You'll then apply your changes to the UI files.
Once you're done making changes, commit those changes to the local branch:
$ git commit -a -m "describe your change"
Then, push your branch to the remote repository:
$ git push origin name-me
Finally, navigate to your UI project in your browser and create a new pull request from this branch.
The maintainer of the UI should review the changes.
If the changes are acceptable, the maintainer will merge the pull request.
As soon as the pull request is merged into the default branch, an automated process will take over to publish a new release for the site generator to use.
Now that you've got the process down, let's review some of the files you'll be working with in more detail.
= Image Styles
:navtitle: Images
This page describes how images in the content are styled and how to customize these styles.
It covers both block and inline images, which are styled differently.
[#size]
== Image size
If a width is not specified on the image macro, the image will be sized to match its intrinsic width.
However, if that width exceeds the available width (i.e., the width of the content area), the image's width will be capped to fit the available space (`max-width: 100%`).
If the image is an SVG, and a width is not specified on the image macro or on the root tag in the SVG, the image will use the maximum width available (i.e., the width of the content area).
The image's height is not used when sizing the image.
However, the aspect ratio of the image is preserved.
[#block-position]
== Block image position
By default, a block image is centered within the content area of the page.
If the block has a caption, that caption will also be centered under the image, but the text will be left-aligned.
The caption may exceed the width of the image.
If you want the image and its caption to be aligned to the left side of the content, add the `text-left` role to the image block.
[,asciidoc]
----
[.text-left]
image::my-image.png[]
----
If you want the image and its caption to be aligned to the right side of the content, add the `text-right` role to the image block.
[,asciidoc]
----
[.text-left]
image::my-image.png[]
----
Applying the `text-right` role also flips the text alignment of the caption to right-aligned.
== Float an image
You can float either a block or inline image to the left or right using the `float` attribute.
When an image is configured to float, the content that follows it will wrap around it (on the opposing side) until that content clears the bottom of the image.
Typically, you use the `float` property with an inline image since you can control when the floating starts relative to the surrounding content.
[,asciidoc]
----
image:subject.png[Subject,250,float=right]
This paragraph can refer to the image on the right.
----
If you use `float` on a block image, it overrides its default positioning (it will be aligned in the direction of the float).
Using float implies that the image occupies less than the width of the content area.
If, on the other hand, it extends from margin to margin, than it ceases to function as a float.
= Antora Default UI
// Settings:
:hide-uri-scheme:
// URLs:
:url-antora: https://antora.org
:url-repo: https://gitlab.com/antora/antora-ui-default
:url-preview: https://antora.gitlab.io/antora-ui-default
:url-hbs: https://handlebarsjs.com
:url-gulp: https://gulpjs.com
:url-npm: https://npmjs.com
:url-node: https://nodejs.org
:url-nvm: https://github.com/creationix/nvm
:url-nvm-install: {url-nvm}#installation
:url-git: https://git-scm.com
:url-git-dl: {url-git}/downloads
This project produces the {url-preview}[default UI bundle] for documentation sites generated with {url-antora}[Antora].
It contains the UI assets (page templates, CSS, JavaScript, images, etc.) and a Gulp build script.
The build can be used to preview the UI locally (featuring live updates), or to package it for consumption by the Antora site generator.
This documentation explains how to use this project to set up, customize and manage a UI for a documentation site generated with Antora.
After reading it, you'll be able to:
* [x] Understand how an Antora UI project is structured.
* [x] Set up your environment to work on the UI project.
* [x] Launch a preview server to visually inspect the UI.
* [x] Adopt a development workflow to share and accept changes to the UI.
* [x] Package a UI for your documentation site that Antora can use.
== File type and technology overview
The Antora UI consists of the following file types that are used to structure and style the documentation site pages generated by Antora.
* Handlebars "`page`" templates (layouts, partials, and helpers)
* CSS (enhanced using PostCSS)
* JavaScript (UI scripts)
* Images / Graphics (specific to the UI)
* Fonts
* Sample content for previewing the UI (HTML and UI model)
To understand how the UI works, let's begin by surveying the primary technologies used by the UI.
Handlebars (file extension: `.hbs`)::
{url-hbs}[Handlebars] is a "`logic-less`" templating engine used to create HTML from template files.
Templates contain placeholders (i.e., mustache expressions like `+{{{page.title}}}+`) into which content is injected from a model.
They also accommodate simple logic expressions for repeating content or including it conditionally (e.g., `+{{#each navigation}}+`) as well as partials (e.g., `+{{> header}}+`).
Gulp (script file: [.path]_gulpfile.js/index.js_)::
{url-gulp}[Gulp] is a build tool for JavaScript projects.
It configures a collection of tasks that can be used to perform automated tasks such as compiling files, running a preview server, or publishing a release.
npm (command: `npm`)::
npm manages software packages (i.e., software dependencies) that it downloads from {url-npm}.
Software this project uses includes libraries that handle compilation as well as shared assets such as font files that are distributed as npm packages.
npm is part of Node.js.
npx (command: `npx`)::
npx runs bin scripts that are either installed in [.path]_node_modules/.bin_ or that it manages itself (if there's no package.json).
npx is the preferred way to run any command.
npx is part of Node.js.
package.json:::
This file keeps track of the dependencies (described using fuzzy versions) that npm (or Yarn) should fetch.
package-lock.json:::
This file contains a report of which dependencies npm resolved.
This information ensures that dependency resolution is reproducible.
node_modules/:::
A local cache of resolved dependencies that npm (or Yarn) fetches.
PostCSS::
This project does not use a CSS preprocessor such as Sass or LESS.
Instead, it relies on normal CSS which is enhanced by a series of postprocessors.
The most common postprocessor backports newer CSS features to older browsers by adding properties with vendor prefixes.
== UI project versus UI bundle
The [.term]*UI project*, which is comprised of the source files in the git repository, provides the recipe and raw materials for creating an Antora UI bundle.
It includes a build, source files, project files, and dependency information.
This is your development workspace.
The [.term]*UI bundle*, a distributable archive, provides pre-compiled (interpreted, consolidated, and/or minimized) files that are ready to be used by Antora.
=== UI project repository structure (default branch)
You should think of the UI project's default branch as your UI workspace.
It contains the recipe and raw materials for creating a UI, including a build, source files, project files, and dependency information.
Here's how the files are structured in the UI project:
[.output]
....
README.adoc
gulpfile.js/
index.js
lib/
tasks/
package.json
package-lock.json
src/
css/
base.css
breadcrumbs.css
...
helpers/
and.js
eq.js
...
img/
back.svg
caret.svg
...
layouts/
404.hbs
default.hbs
partials/
article.hbs
breadcrumbs.hbs
...
js/
01-navigation.js
02-fragment-jumper.js
...
vendor/
highlight.js
preview-src/
index.html
ui-model.yml
....
A Gulp build is used to compile and assemble the UI project files into a UI bundle.
=== UI bundle structure (releases)
The UI bundle--a distributable archive--provides files which are ready to be used by Antora.
When the UI project files are built by Gulp, they are assembled under the [.path]_public_ directory.
Since the [.path]_public_ directory is generated, it's safe to remove.
The contents of the UI bundle resembles the UI project's default branch contents, except the bundle doesn't contain any files other than the ones that make up the UI.
This is the content that is used by Antora.
[.output]
....
css/
site.css
font/
...
helpers/
and.js
eq.js
...
img/
back.svg
caret.svg
...
layouts/
404.hbs
default.hbs
partials/
article.hbs
breadcrumbs.hbs
...
js/
site.js
vendor/
highlight.js
....
Some of these files have been compiled or aggregated, such as the stylesheets and JavaScript.
The benefit of building the UI files is that the files can be optimized for static inclusion in the site without that optimization getting in the way of UI development.
For example, the UI build can optimize SVGs or add vendor prefixes to the CSS.
Since these optimizations are only applied to the pre-compiled files, they don't interfere with the web developer's workflow.
== UI compilation and generator consumption overview
The purpose of an Antora UI project is to assemble the UI files into a reusable distribution that Antora can use to compose the HTML pages and the assets they require.
The only required file in the UI bundle is the default Handlebars layout for pages (i.e., [.path]_layouts/default.hbs_).
If the 404 page is enabled, the Handlebars layout for the 404 page is also required (i.e., [.path]_layouts/404.hbs_).
The layout files must be located in the [.path]_layouts_ folder in the UI bundle.
The name of the layout is the stem of the file, which is the file's basename with a file extension (e.g., [.path]_layouts/default.hbs_ becomes `default`).
[.output]
....
layouts/
404.hbs
default.hbs
....
There are no other required files in a UI bundle.
Any additional files are only required because they are referenced by a layout, either when generating the HTML (partial or helper) or assets referenced by the HTML (CSS or JavaScript) that are served to the browser.
Antora does not copy layouts, partials, or helpers to the generated site.
If the layout looks for a partial, that partial must be located in the [.path]_partials_ directory.
The name of the partial is the stem of the file (e.g,. [.path]_partials/body.hbs_] becomes `body` and used as `> body`).
If the partial is inside a folder, the name of that folder is not used in the partial's name.
Additionally, any JavaScript files found in the [.path]_helpers_ directory are automatically registered as template helpers.
The name of the helper function matches the stem of the file (e.g., [.path]_helpers/concat.js_ becomes `concat`).
Here's how a UI would be structured if it had layouts, partials, and helpers:
[.output]
....
helpers/
concat.js
layouts/
404.hbs
default.hbs
partials/
body.hbs
....
The UI is served statically in a production site, but the UI's assets live in a source form in a UI project to accommodate development and simplify maintenance.
When handed off to the Antora pipeline, the UI is in an interim, pre-compiled state.
Specifically, the default branch of the git repository contains the files in source form while releases are used to distribute the files in pre-compiled form.
The responsibility of compiling the UI is shared between a UI project and Antora.
The UI project uses a local build to pre-compile (i.e., interpret, consolidate, and/or minimize) the files.
The pre-compiled files are agnostic to Antora's content model, relieving the generator from having to deal with this part.
It also allows the UI to be reused.
The UI project build then packages the UI into a bundle, which the UI Loader in Antora consumes.
Antora grabs the bundle, extracts it into a UI catalog, and takes compilation to completion by weaving the Antora's content model into the Handlebars templates to make the pages and auxiliary data files.
Antora then copies the remaining UI assets to the site output.
Now that you have an overview of the files that make up the UI and how it gets assembled, let's go over how to set up the project, build the UI, and preview it.
= Inline Text Styles
:navtitle: Inline Text
:example-caption!:
////
When creating a UI theme for Antora, there are certain elements in the UI that require support from the CSS to work correctly.
This list includes elements in the shell (i.e., frame) and in the document content.
This document identifies these UI elements.
////
This page describes how to style text in the contents of the page which is visually emphasized.
[#bold]
== Bold text (<strong>)
How xref:antora:asciidoc:bold.adoc[text marked as bold] appears on your site depends on the fonts loaded by the UI and the CSS styles the UI applies to the `<strong>` HTML tag.
[source,html]
----
A bold <strong>word</strong>, or a bold <strong>phrase of text</strong>.
----
Since `<strong>` is a semantic HTML element, it automatically inherits default styling (`font-weight: bold`) from the browser.
If you want to override the browser styles, you'll need to define properties on the `strong` selector in the stylesheet for your UI.
In the default UI, the `<strong>` element is styled in the 500 font weight of the current typeface (Roboto).
For example:
[example]
A bold *word*, or a bold *phrase of text*.
[#italic]
== Italic text (<em>)
How xref:antora:asciidoc:italic.adoc[italicized text] appears on your site depends on the fonts loaded by the UI and the CSS styles the UI applies to the `<em>` HTML tag.
[source,html]
----
An italic <em>word</em>, or an italic <em>phrase of text</em>.
----
Since `<em>` is a semantic HTML element, it automatically inherits default styling (`font-style: italic`) from the browser.
If you want to override the browser styles, you'll need to define properties on the `em` selector in the stylesheet for your UI.
In the default UI, the `em` element is styled in the italic font variant of the current typeface (Roboto).
For example:
[example]
An italic _word_, or an italic _phrase of text_.
[#monospace]
== Monospace text (<code>)
How xref:antora:asciidoc:monospace.adoc[inline monospace text] is displayed depends on the fixed-width font loaded by your UI and the CSS styles it applies to the `<code>` HTML tag.
[source,html]
----
A monospace <code>word</code>, or a monospace <code>phrase of text</code>.
----
Since `<code>` is a semantic HTML element, it automatically inherits default styling (`font-family: monospace`) from the browser.
If you want to override the browser styles, you'll need to define properties on the `code` selector in the stylesheet for your UI.
In the default UI, the `code` element is styled using the fixed-width font loaded by the stylesheet (Roboto Mono).
For example:
[example]
A monospace `word`, or a monospace `phrase of text`.
[#highlight]
== Highlighted text (<mark>)
How xref:antora:asciidoc:highlight.adoc[highlighted (or marked) text] appears on your site depends on the CSS styles the UI applies to the `<mark>` HTML tag.
[source,html]
----
Let&#8217;s add some <mark>highlight</mark> to it.
----
Since `<mark>` is a semantic HTML element, it automatically inherits default styling (`background-color: yellow`) from the browser.
Here's an example:
[example]
Let's add some #highlight# to it.
If you want to override the browser styles, you'll need to define properties on the `mark` selector in the stylesheet for your UI.
= List Styles
:navtitle: Lists
== Ordered list numeration
The browser automatically numbers xref:antora:asciidoc:ordered-and-unordered-lists.adoc[ordered lists] and selects a numeration style by list depth in the following order: decimal, lower-alpha, lower-roman, upper-alpha, upper-roman.
AsciiDoc allows the author to override the numeration style for an ordered list.
Here's an example of that output:
[source,html]
----
<div class="olist loweralpha">
<ol class="loweralpha" type="a">
<li><p>a</p></li>
<li><p>b</p></li>
<li><p>c</p></li>
</ol>
</div>
----
In order to support this customization, you must assign the list-style-type property to the following classes on the `<ol>` element in your stylesheet.
|===
|<ol> class |list-style-type property value
|arabic
|decimal
|decimal
|decimal-leading-zero
|loweralpha
|lower-alpha
|lowergreek
|lower-greek
|lowerroman
|lower-roman
|upperalpha
|upper-alpha
|upperroman
|upper-roman
|===
== Checklist
A xref:antora:asciidoc:checklists.adoc[checklist] is an unordered list with items that are prefixed with a checkbox marker (checked or unchecked).
Here's an AsciiDoc source example that produces a checklist:
[source,asciidoc]
----
* [ ] todo
* [x] done!
----
If font-based icons are enabled (`icons=font`), the checkbox gets inserted as the first element of the paragraph element that contains the list item text.
[source,html]
----
<div class="ulist checklist">
<ul class="checklist">
<li>
<p><i class="fa fa-square-o"> todo</p>
</li>
<li>
<p><i class="fa fa-check-square-o"> done!</p>
</li>
</ul>
</div>
----
The recommended approach is to hide the list markers (`list-style: none`), then add a checkbox glyph on the icon element using either a background image or a `before` pseudo element.
Here's how it might appear:
* [ ] todo
* [*] done!
= UI Development Prerequisites
// URLs:
:url-nvm: https://github.com/creationix/nvm
:url-node: https://nodejs.org
:url-gulp: http://gulpjs.com
:url-git: https://git-scm.com
:url-git-dl: {url-git}/downloads
:url-node-releases: https://nodejs.org/en/about/releases/
// These prerequisite instructions are less detailed than Antora's prerequisite instructions, I don't know if this is a concern or not.
An Antora UI project is based on tools built atop Node.js, namely:
* {url-node}[Node.js] (commands: `node`, `npm`, and `npx`)
** {url-nvm}[nvm] (optional, but strongly recommended)
* {url-gulp}[Gulp CLI] (command: `gulp`) (can be accessed via `npx gulp`)
You also need {url-git}[git] (command: `git`) to pull down the project and push updates to it.
== git
First, make sure you have git installed.
$ git --version
If not, {url-git-dl}[download and install] the git package for your system.
== Node.js
You need Node.js installed on your machine to use Antora, including the default UI.
Antora follows the Node.js release schedule, so we advise that you choose an active long term support (LTS) release of Node.js.
We recommend using the latest active Node.js LTS version.
While you can use other versions of Node.js, Antora is only tested against LTS releases.
To check whether you have Node.js installed, and which version, open a terminal and type:
$ node --version
You should see a version string, such as:
v10.15.3
If the command fails with an error, it means you don't have Node.js installed.
The best way to install Node.js is to use nvm (Node Version Manager).
Refer to xref:antora:install:linux-requirements.adoc#install-nvm[Install nvm and Node.js (Linux)], xref:antora:install:macos-requirements.adoc#install-nvm[Install nvm and Node.js (macOS)], or xref:antora:install:windows-requirements.adoc#install-nvm[Install nvm and Node.js (Windows)] for instructions.
Once you have Node.js installed, you can proceed with installing the Gulp CLI.
== Gulp CLI
Next, you may choose to install the Gulp CLI globally.
This package provides the `gulp` command which executes the version of Gulp declared by the project.
You can install the Gulp CLI globally (which resolves to a location in your user directory if you're using nvm) using the following command:
$ npm i -g gulp-cli
Alternately, you can run the `gulp` command using `npx` once you've installed the project dependencies, thus waiving the requirement to install it globally.
$ npx gulp
Using `npx gulp` is the preferred way to invoke the `gulp` command.
Now that you have Node.js and Gulp installed, you're ready to set up the project.
= Set up a UI Project
:url-project: https://gitlab.com/antora/antora-ui-default.git
Before you can start working on the UI, you need to grab the sources and initialize the project.
The sources can be {url-project}[Antora's default UI] or an existing UI project structured to work with Antora.
== Fetch the Default UI project
To start, clone the default UI project using git:
[subs=attributes+]
$ git clone {url-project}
$ cd "`basename ${_%.git}`"
The example above clones Antora's default UI project and then switches to the project folder on your filesystem.
Stay in this project folder in order to initialize the project using npm.
== Install dependencies
Next, you'll need to initialize the project.
Initializing the project essentially means downloading and installing the dependencies into the project.
That's the job of npm.
In your terminal, execute the following command (while inside the project folder):
$ npm i
The `npm i` command, short for `npm install`, installs the dependencies listed in [.path]_package.json_ into the [.path]_node_modules/_ folder inside the project.
This folder does not get included in the UI bundle.
The folder is safe to delete, though npm does a great job of managing it.
You'll notice another file which seems to be relevant here, [.path]_package-lock.json_.
npm uses this file to determine which concrete version of a dependency to use, since versions in [.path]_package.json_ are typically just a range.
The information in this file makes the build reproducible across different machines and runs.
Installing the dependencies makes the `npx gulp` command available.
You can verify this by querying the Gulp version:
$ npx gulp -v
If a new dependency must be resolved that isn't yet listed in [.path]_package-lock.json_, npm will update this file with the new information when you run `npm i`.
Therefore, you're advised to commit the [.path]_package-lock.json_ file into the repository whenever it changes.
== Supported build tasks
Now that the dependencies are installed, you should be able to run the `gulp` command to find out what tasks the build supports:
$ npx gulp --tasks-simple
You should see:
[.output]
....
default
clean
lint
format
build
bundle
bundle:pack
preview
preview:build
....
We'll explain what each of these tasks are for and when to use them.
= Sidebar Styles
:navtitle: Sidebars
This page describes the in-page sidebar block styles, not the styles for the navigation sidebar.
== Sidebar blocks
xref:antora:asciidoc:sidebar.adoc[Sidebars] can contain any type of content.
The sidebar title is specified by the block class title.
Here's an AsciiDoc source example that produces a sidebar with a title:
[source,asciidoc]
----
.Optional Title
****
This is a paragraph in a sidebar.
****
----
[source,html]
----
<div class="sidebarblock">
<div class="content">
<div class="title">Optional Title</div>
<div class="paragraph">
<p>This is a paragraph in a sidebar.</p>
</div>
</div>
</div>
----
= UI Element Style and Behavior Guide
:navtitle: UI Element Styles and Behaviors
When creating a UI theme for Antora, there are certain elements in the UI that require support from the CSS--as well as JavaScript in some cases--to work correctly.
This list includes elements in the shell (i.e., frame) and in the document content.
This document identifies these UI elements.
//== UI Shell
// TODO
== Document Content
The HTML in the main content area is generated from AsciiDoc using Asciidoctor.
AsciiDoc has numerous content elements that require assistance from CSS to render properly.
These elements include:
* xref:inline-text-styles.adoc[Inline text emphasis]
* xref:admonition-styles.adoc[Admonitions]
* xref:list-styles.adoc[Lists]
* xref:sidebar-styles.adoc[Sidebars]
* xref:ui-macro-styles.adoc[Button, keybinding, and menu UI macros]
* xref:code-blocks.adoc[Code blocks]
= Work with the CSS Stylesheets
The stylesheets are written in CSS.
These stylesheets utilize CSS variables to keep the CSS DRY and easy to customize.
== Stylesheet organization and processing
Within the default UI project, the stylesheet files are separated into modules to help organize the rules and make them easier to find.
The UI build combines and minifies these files into a single file named [.path]_site.css_.
During the build, the CSS is enhanced using PostCSS in much the same way as a CSS preprocessor works, only the modifications are made to the CSS directly.
The modifications mostly revolve around injecting vendor prefixes for compatibility or backporting new features to more broadly supported syntax.
NOTE: An Antora UI provides its own styles.
While these styles are expected to support any roles defined in the AsciiDoc Language documentation, it does not provide all the selectors found in the Asciidoctor default stylesheet.
If there's a selector that the Asciidoctor default stylesheet provides that you need in your Antora site, you'll need to add it to the CSS for your own UI.
== Add a new CSS rule
Let's consider the case when you want to modify the font size of a section title.
First, make sure you have set up the project and created a development branch.
Next, open the file [.path]_src/css/doc.css_ and modify the rule for the section title.
[source,css]
----
.doc h1 {
font-size: 2.5rem;
margin-bottom: 1rem;
margin-top: 2rem
}
----
Save the file, commit it to git, push the branch, and allow the approval workflow to play out.
= Template Customization
The default UI bundle can be customized using AsciiDoc attributes.
xref:templates.adoc[] explains how to access such attributes.
But what are the attributes that actually used by the templates.
If you're going to use the default UI bundle for your project, you'll want to know.
== page-role attribute
The `page-role` attribute is typically defined per-page.
This attribute is used to add one or more space-separated classes to the `<body>` tag of that page.
A common use of the `page-role` attribute is to label the home page.
[,asciidoc]
----
= Home
:page-role: home
This is the home page.
----
////
Alternately, the role can be set on the document itself.
[,asciidoc]
----
[.home]
= Home
This is the home page.
----
////
The resulting HTML will include the following `<body>` start tag:
[,html]
----
<body class="article -toc">
----
The stylesheet can now take advantage of this identity to assign styles to pages that have a given role.
For example, the home page often requires a different appearance.
Being able to target that page with CSS allows UI developers to apply that customization.
Note that the UI templates could make use of the page role in other ways.
The default UI currently only appends the value to the `class` attribute on the `<body>` tag.
=== Hide the TOC sidebar
The one reserved page role that the default UI recognizes is `-toc`.
This role instructs the site script to remove (i.e., hide) the TOC sidebar.
Here's how to set it.
[,asciidoc]
----
= Page Title
:page-role: -toc
The TOC sidebar is not displayed even though this page has sections.
== First Section
== Second Section
----
The AsciiDoc `toc` attribute controls whether the TOC is rendered in the *body* of the article.
Since the default UI provides an alternate TOC, you most likely don't want to activate the built-in TOC functionality in AsciiDoc when using Antora.
== page-pagination attribute
The `page-pagination` attribute is set in your xref:antora:playbook:asciidoc-attributes.adoc[playbook] in order to enable pagination, if your UI bundle supports it.
The default UI bundle supports this and you'll get the links to previous and next pages at the bottom of every page, based your navigation.
.Enable pagination
[,yaml]
----
asciidoc:
attributes:
page-pagination: ''
----
Antora automatically calculates the appropriate URLs and inserts the correct links.
Since you most likely want this enabled for every page in your site, there's no point setting this attribute per page.
However, if you do want to be able to override it per page, such as to turn it off, then you need to soft set the value in the playbook.
.Enable pagination, but make it overriddable
[,yaml]
----
asciidoc:
attributes:
page-pagination: '@'
----
You can now turn page pagination off by unsetting the `page-pagination` attribute in the document header.
= Work with the Handlebars Templates
Antora combines the Handlebars templates with the converted AsciiDoc content and other UI model data to make the pages in the site.
These "`logic-less`" templates are mostly HTML with some special mustache tags sprinkled in where content should be inserted.
== What do the templates do?
The layout templates, which are stored in [.path]_src/layouts/_, provide the main page structure.
The partial templates, in [.path]_src/partials/_, fill in different regions of a page, such as the navigation and footer.
The templates read from a model that's populated by Antora.
The model can be accessed by enclosing path expressions in mustaches, which are `{{` and `}}` or `{{{` and `}}}` (e.g., `+{{{page.title}}}+`).
The double mustaches escape the value for HTML, whereas triple mustaches insert the value as is.
WARNING: If the mustaches are preceded by a backslash (e.g, `\{{`), the expression will be disabled.
This often comes up when constructing URLs.
To avoid this problem, you should use forward slashes in URLs instead of backslashes.
When `{{` is immediately followed by `>`, that invokes a partial (from the partials directory) and inserts the result (e.g., `+{{> head}}+`.
In other words, that's not a model reference like the other mustache expressions.
=== Template variables
CAUTION: This model is not final.
Variable names and purposes may change.
Here's an overview of the available UI model:
.Variables available to the Handlebars templates (top-level variables in bold)
[#template-variables-ref,cols="1m,2"]
|===
| Name | Description
s| [[site]]site
| Information about the site.
| site.url
| The base URL of the site, if specified in the playbook.
If a site start page is defined, the visitor will be redirected from this location to the start page.
Includes the value of `site.path`.
Can be prepended to `page.url` to create an absolute URL for a page (e.g., `+{{{site.url}}}{{{page.url}}}+`).
| site.path
| The pathname (i.e., subpath) of the site.url under which the site is hosted (e.g., /docs).
This value is empty if site.url is not defined, has no path segment, or matches /.
Can be dropped from the site.url value using a helper (e.g., `deleteSuffix site.url site.path`)
Can be prepended to `page.url` to create a root-relative URL for a page (e.g., `+{{{site.path}}}{{{page.url}}}+`).
(since Antora 2.1)
| site.title
| The title of the site.
| site.homeUrl
| The URL that points directly to the start (aka home) page of the site.
| site.components
| A map of all the components in the site, keyed by component name.
Properties of each component include name, title, url, latest, and versions.
Properties of each version include name (since 2.3), version, displayVersion, prerelease (if set), title, url, asciidoc (since 2.3), and navigation.
The navigation property on each version provides access to the navigation menu for that component version.
| site.ui
| Information about the site UI.
| site.ui.defaultLayout
| The default page layout used for this site.
| site.ui.url
| The absolute base URL of the UI.
s| [[page]]page
| Information about the current page.
| page.title
| The page title in HTML format (often used as the primary heading).
This value may include inline HTML elements and XML character references.
| page.contents
| The main article content in HTML format.
Sourced from AsciiDoc and converted to HTML by the Asciidoctor processor.
| page.attributes
| Any AsciiDoc document attribute prefixed with `page-`.
The `page-` prefix is dropped from the name used in this model.
For example, the value of the document attribute named `page-support-phone` can be accessed via the UI model using `page.attributes.support-phone`.
Page attributes can be defined per page in the AsciiDoc document header (e.g., `:page-support-phone: +1 212-555-1234`) or globally in the playbook under the key `asciidoc.attributes`.
The `page-` prefix is used to isolate page-related attributes from the numerous other document attributes in AsciiDoc.
| page.author
| The first author of the document, if one or more authors are specified in the AsciiDoc header.
| page.description
| The text of the description attribute in the AsciiDoc header, if specified.
| page.keywords
| A comma-separated list of keywords defined in the AsciiDoc header, if specified.
| page.component
| Information about the component for the current page.
Properties include name, title, url, latest, and versions.
| page.componentVersion
| Information about the component version for the current page.
Properties include name (since 2.3), version, displayVersion, prerelease (if set), title, url, and asciidoc (since 2.3).
| page.module
| The name of the module for the current page.
| page.relativeSrcPath
| The path of the current page relative to the pages directory in the current module (since 2.3).
| page.version
| The name of the version for the current page.
| page.displayVersion
| The name of the display version for the current page.
| page.versions
| All versions of the current page, including the current page.
Each entry has the properties url, string, and missing.
| page.latest
| The entry from `page.versions` that corresponds to the latest version available of this page.
The latest page may not come from the latest version of the component (if the page is missing in that version).
This property is not set if this page is in a versionless component.
| page.breadcrumbs
| An array of breadcrumb items that represent the current selection in the navigation tree.
Includes text-only and external items.
| page.navigation
| The hierarchical navigation menu for the component version of the current page.
Each navigation item contains the property `content` as well as the optional properties `url` and (child) `items`.
| page.url
| The URL for the current page.
This URL is a root-relative path (i.e., it begins with `/`), where root refers to where Antora published the files.
The value is most often used by the `relativize` helper to generate relative URLs from this page.
| page.canonicalUrl
| The canonical URL for the current page.
The canonicalUrl is only set if site.url is set to an absolute URL and the page's component has at least one non-prerelease version.
If there are multiple versions of the component, the canonical URL is the qualified URL of the most recent, non-prerelease version of the page.
If there's only a single version of the component, the canonical URL is the qualified URL of the current page.
| page.editUrl
| The URL to edit the current page (i.e., activates the web-based editor on the git host).
This value is derived automatically for the hosts github.com, gitlab.com, pagure.io, and bitbucket.org, even if the repository is private.
If the host is not recognized, or you want to customize the value, you can use the `edit_url` key on the content source in the playbook.
The default UI shows an "Edit this Page" link that points to this URL unless the repository is private (i.e., `page.origin.private` is truthy) or `page.fileUri` is set.
You can force this link to be shown by setting the environment variable `FORCE_SHOW_EDIT_PAGE_LINK` (e.g., `FORCE_SHOW_EDIT_PAGE_LINK=true`) or by customizing the logic in the UI template.
| page.fileUri
| The local file:// URI to edit the current page if the page originates from the local filesystem (i.e., the worktree).
If this property is set, the default UI shows an "Edit this Page" link that points to this URI (instead of the `page.editUrl` value) unless the `CI` environment variable is set (e.g., `CI=true`).
When the `CI` environment variable is set, the default UI ignores this property (since linking to a local file:// URI in a published site doesn't make any sense).
| page.origin
| Information about the content source from which the current page was taken.
Properties include url, reftype (since 3.1), refname, branch, tag, refhash (since 2.3), startPath, worktree, webUrl, fileUriPattern, editUrlPattern, private.
| page.origin.private
| This value will be `auth-required` if the git server requests authentication credentials, otherwise `auth-embedded` if credentials are embedded in the content source URL in the playbook, otherwise unset.
In the default UI, if this value is truthy, the "Edit this Page" link is not shown.
A quick way to force this property to be truthy (even if the repository is public) is to begin the content source URL in the playbook with empty credentials, as in `\https://@`.
Then, the "Edit the Page" link will not appear.
| page.home
| Indicates whether the current page is the start (aka home) page of the site.
| page.layout
| The page layout for the current page.
| page.next
| The next reachable page in the navigation tree (skips past text-only and external items).
| page.previous
| The previous reachable page in the navigation tree (skips past text-only and external items).
| page.parent
| The parent page in the navigation tree (skips past text-only and external items).
s| env
| The map of environment variables (sourced from `process.env`).
s| siteRootPath
| The relative path to the root of the published site.
If a site start page is defined, the visitor will be redirected from this location to the start page.
Can be used as a fallback when a site URL is not set.
This value is _root-relative_ in the 404 page template, which is required for the 404 page to work correctly when served by the web server.
s| uiRootPath
| The relative path to the root directory of the UI.
This value is _root-relative_ in the 404 page template, which is required for the 404 page to work correctly when served by the web server.
s| antoraVersion
| The version of Antora used to build the site (specifically the version of the @antora/page-composer package).
s| contentCatalog
| A proxy object around Antora's virtual content catalog, which provides access to components, component versions, pages, and resource files.
Exposes the following methods: `findBy`, `getById`, `getComponent`, `getComponentVersion`, `getComponents`, `getComponentsSortedBy`, `getFiles`, `getPages`, `getSiteStartPage`, `resolvePage`, and `resolveResource`.
*This object should only be used from a UI helper.*
|===
This model is likely to grow over time.
== Modify a template
Let's consider the case when you want to add a new meta tag inside the HTML head.
First, make sure you have set up the project and created a development branch.
Next, open the file [.path]_templates/partials/head.hbs_ and add your tag.
[source,html]
----
<meta class="swiftype" name="title" data-type="string" content="{{page.title}}">
----
Each template file has access to the template model, which exposes information about the current page through variable names.
The variables currently available are listed in <<template-variables-ref>>.
Save the file, commit it to git, push the branch, and allow the approval workflow to play out.
= UI Macro Styles
:navtitle: UI Macros
Asciidoctor supports xref:antora:asciidoc:ui-macros.adoc[three UI element representations] out of the box, which are made from corresponding inline UI macros.
* button (btn macro)
* keybinding (kbd macro)
* menu (menu macro)
The UI elements are output using semantic HTML elements, so they inherit some default styling from the browser.
However, to look proper, they require some additional styling.
== Button
A xref:antora:asciidoc:ui-macros.adoc#button[button] is meant to represent an on-screen button (`+btn:[Save]+`).
However, it should not appear like an actual button as that could confuse the reader into thinking it's interactive.
Therefore, a button is rendered as a bold text by default:
[source,html]
----
<b class="button">Save</b>
----
Traditionally, a button reference is styled by surrounding the text with square brackets, as shown here:
btn:[Save]
== Keybinding
A xref:antora:asciidoc:ui-macros.adoc#keybinding[keybinding] can be a single key (`+kbd:[F11]+`) or a sequence of keys (`+kbd:[Ctrl+F]`).
Here's the HTML that's generated for these two forms.
[source,html]
----
<kbd>F11</kbd>
<span class="keyseq"><kbd>Ctrl</kbd>+<kbd>F</kbd></span>
----
Here's how these might appear:
[%hardbreaks]
kbd:[F11]
kbd:[Ctrl+F]
== Menu
A xref:antora:asciidoc:ui-macros.adoc#menu[menu] can be a top-level menu reference (`+menu:File[]+`) or a nested selection (`+menu:File[Save]+`).
Here's the HTML that's generated for these two forms.
[source,html]
----
<b class="menuref">File</b>
<span class="menuseq"><b class="menu">File</b>&#160;<b class="caret">&#8250;</b> <b class="menuitem">Save</b></span>
----
This might be rendered as:
menu:File[]
menu:File[Save]
The default styling applied to a menu reference is usually sufficient.
'use strict'
const metadata = require('undertaker/lib/helpers/metadata')
const { watch } = require('gulp')
module.exports = ({ name, desc, opts, call: fn, loop }) => {
if (name) {
const displayName = fn.displayName
if (displayName === '<series>' || displayName === '<parallel>') {
metadata.get(fn).tree.label = `${displayName} ${name}`
}
fn.displayName = name
}
if (loop) {
const delegate = fn
name = delegate.displayName
delegate.displayName = `${name}:loop`
fn = () => watch(loop, { ignoreInitial: false }, delegate)
fn.displayName = name
}
if (desc) fn.description = desc
if (opts) fn.flags = opts
return fn
}
'use strict'
module.exports = (...tasks) => {
const seed = {}
if (tasks.length) {
if (tasks.lastIndexOf(tasks[0]) > 0) {
const task1 = tasks.shift()
seed.default = Object.assign(task1.bind(null), { description: `=> ${task1.displayName}`, displayName: 'default' })
}
return tasks.reduce((acc, it) => (acc[it.displayName || it.name] = it) && acc, seed)
} else {
return seed
}
}
'use strict'
const log = require('fancy-log')
const PluginError = require('plugin-error')
const prettierEslint = require('prettier-eslint')
const { Transform } = require('stream')
const map = (transform) => new Transform({ objectMode: true, transform })
module.exports = () => {
const report = { changed: 0, unchanged: 0 }
return map(format).on('finish', () => {
if (report.changed > 0) {
const changed = 'formatted '
.concat(report.changed)
.concat(' file')
.concat(report.changed === 1 ? '' : 's')
const unchanged = 'left '
.concat(report.unchanged)
.concat(' file')
.concat(report.unchanged === 1 ? '' : 's')
.concat(' unchanged')
log(`prettier-eslint: ${changed}; ${unchanged}`)
} else {
log(`prettier-eslint: left ${report.unchanged} file${report.unchanged === 1 ? '' : 's'} unchanged`)
}
})
function format (file, enc, next) {
if (file.isNull()) return next()
if (file.isStream()) return next(new PluginError('gulp-prettier-eslint', 'Streaming not supported'))
const input = file.contents.toString()
const output = prettierEslint({ text: input, filePath: file.path })
if (input === output) {
report.unchanged += 1
} else {
report.changed += 1
file.contents = Buffer.from(output)
}
next(null, file)
}
}
'use strict'
const Asciidoctor = require('@asciidoctor/core')()
const fs = require('fs-extra')
const handlebars = require('handlebars')
const merge = require('merge-stream')
const ospath = require('path')
const path = ospath.posix
const requireFromString = require('require-from-string')
const { Transform } = require('stream')
const map = (transform = () => {}, flush = undefined) => new Transform({ objectMode: true, transform, flush })
const vfs = require('vinyl-fs')
const yaml = require('js-yaml')
const ASCIIDOC_ATTRIBUTES = { experimental: '', icons: 'font', sectanchors: '', 'source-highlighter': 'highlight.js' }
module.exports = (src, previewSrc, previewDest, sink = () => map()) => (done) =>
Promise.all([
loadSampleUiModel(previewSrc),
toPromise(
merge(compileLayouts(src), registerPartials(src), registerHelpers(src), copyImages(previewSrc, previewDest))
),
])
.then(([baseUiModel, { layouts }]) => {
const extensions = ((baseUiModel.asciidoc || {}).extensions || []).map((request) => {
ASCIIDOC_ATTRIBUTES[request.replace(/^@|\.js$/, '').replace(/[/]/g, '-') + '-loaded'] = ''
const extension = require(request)
extension.register.call(Asciidoctor.Extensions)
return extension
})
const asciidoc = { extensions }
for (const component of baseUiModel.site.components) {
for (const version of component.versions || []) version.asciidoc = asciidoc
}
baseUiModel = { ...baseUiModel, env: process.env }
delete baseUiModel.asciidoc
return [baseUiModel, layouts]
})
.then(([baseUiModel, layouts]) =>
vfs
.src('**/*.adoc', { base: previewSrc, cwd: previewSrc })
.pipe(
map((file, enc, next) => {
const siteRootPath = path.relative(ospath.dirname(file.path), ospath.resolve(previewSrc))
const uiModel = { ...baseUiModel }
uiModel.page = { ...uiModel.page }
uiModel.siteRootPath = siteRootPath
uiModel.uiRootPath = path.join(siteRootPath, '_')
if (file.stem === '404') {
uiModel.page = { layout: '404', title: 'Page Not Found' }
} else {
const doc = Asciidoctor.load(file.contents, { safe: 'safe', attributes: ASCIIDOC_ATTRIBUTES })
uiModel.page.attributes = Object.entries(doc.getAttributes())
.filter(([name, val]) => name.startsWith('page-'))
.reduce((accum, [name, val]) => {
accum[name.slice(5)] = val
return accum
}, {})
uiModel.page.layout = doc.getAttribute('page-layout', 'default')
uiModel.page.title = doc.getDocumentTitle()
uiModel.page.contents = Buffer.from(doc.convert())
}
file.extname = '.html'
try {
file.contents = Buffer.from(layouts.get(uiModel.page.layout)(uiModel))
next(null, file)
} catch (e) {
next(transformHandlebarsError(e, uiModel.page.layout))
}
})
)
.pipe(vfs.dest(previewDest))
.on('error', done)
.pipe(sink())
)
function loadSampleUiModel (src) {
return fs.readFile(ospath.join(src, 'ui-model.yml'), 'utf8').then((contents) => yaml.safeLoad(contents))
}
function registerPartials (src) {
return vfs.src('partials/*.hbs', { base: src, cwd: src }).pipe(
map((file, enc, next) => {
handlebars.registerPartial(file.stem, file.contents.toString())
next()
})
)
}
function registerHelpers (src) {
handlebars.registerHelper('resolvePage', resolvePage)
handlebars.registerHelper('resolvePageURL', resolvePageURL)
return vfs.src('helpers/*.js', { base: src, cwd: src }).pipe(
map((file, enc, next) => {
handlebars.registerHelper(file.stem, requireFromString(file.contents.toString()))
next()
})
)
}
function compileLayouts (src) {
const layouts = new Map()
return vfs.src('layouts/*.hbs', { base: src, cwd: src }).pipe(
map(
(file, enc, next) => {
const srcName = path.join(src, file.relative)
layouts.set(file.stem, handlebars.compile(file.contents.toString(), { preventIndent: true, srcName }))
next()
},
function (done) {
this.push({ layouts })
done()
}
)
)
}
function copyImages (src, dest) {
return vfs
.src('**/*.{png,svg}', { base: src, cwd: src })
.pipe(vfs.dest(dest))
.pipe(map((file, enc, next) => next()))
}
function resolvePage (spec, context = {}) {
if (spec) return { pub: { url: resolvePageURL(spec) } }
}
function resolvePageURL (spec, context = {}) {
if (spec) return '/' + (spec = spec.split(':').pop()).slice(0, spec.lastIndexOf('.')) + '.html'
}
function transformHandlebarsError ({ message, stack }, layout) {
const m = stack.match(/^ *at Object\.ret \[as (.+?)\]/m)
const templatePath = `src/${m ? 'partials/' + m[1] : 'layouts/' + layout}.hbs`
const err = new Error(`${message}${~message.indexOf('\n') ? '\n^ ' : ' '}in UI template ${templatePath}`)
err.stack = [err.toString()].concat(stack.slice(message.length + 8)).join('\n')
return err
}
function toPromise (stream) {
return new Promise((resolve, reject, data = {}) =>
stream
.on('error', reject)
.on('data', (chunk) => chunk.constructor === Object && Object.assign(data, chunk))
.on('finish', () => resolve(data))
)
}
'use strict'
const autoprefixer = require('autoprefixer')
const browserify = require('browserify')
const concat = require('gulp-concat')
const cssnano = require('cssnano')
const fs = require('fs-extra')
const imagemin = require('gulp-imagemin')
const merge = require('merge-stream')
const ospath = require('path')
const path = ospath.posix
const postcss = require('gulp-postcss')
const postcssCalc = require('postcss-calc')
const postcssImport = require('postcss-import')
const postcssUrl = require('postcss-url')
const postcssVar = require('postcss-custom-properties')
const { Transform } = require('stream')
const map = (transform) => new Transform({ objectMode: true, transform })
const through = () => map((file, enc, next) => next(null, file))
const uglify = require('gulp-uglify')
const vfs = require('vinyl-fs')
module.exports = (src, dest, preview) => () => {
const opts = { base: src, cwd: src }
const sourcemaps = preview || process.env.SOURCEMAPS === 'true'
const postcssPlugins = [
postcssImport,
(css, { messages, opts: { file } }) =>
Promise.all(
messages
.reduce((accum, { file: depPath, type }) => (type === 'dependency' ? accum.concat(depPath) : accum), [])
.map((importedPath) => fs.stat(importedPath).then(({ mtime }) => mtime))
).then((mtimes) => {
const newestMtime = mtimes.reduce((max, curr) => (!max || curr > max ? curr : max), file.stat.mtime)
if (newestMtime > file.stat.mtime) file.stat.mtimeMs = +(file.stat.mtime = newestMtime)
}),
postcssUrl([
{
filter: (asset) => new RegExp('^[~][^/]*(?:font|typeface)[^/]*/.*/files/.+[.](?:ttf|woff2?)$').test(asset.url),
url: (asset) => {
const relpath = asset.pathname.slice(1)
const abspath = require.resolve(relpath)
const basename = ospath.basename(abspath)
const destpath = ospath.join(dest, 'font', basename)
if (!fs.pathExistsSync(destpath)) fs.copySync(abspath, destpath)
return path.join('..', 'font', basename)
},
},
]),
postcssVar({ preserve: preview }),
// NOTE to make vars.css available to all top-level stylesheets, use the next line in place of the previous one
//postcssVar({ importFrom: path.join(src, 'css', 'vars.css'), preserve: preview }),
preview ? postcssCalc : () => {}, // cssnano already applies postcssCalc
autoprefixer,
preview
? () => {}
: (css, result) => cssnano({ preset: 'default' })(css, result).then(() => postcssPseudoElementFixer(css, result)),
]
return merge(
vfs.src('ui.yml', { ...opts, allowEmpty: true }),
vfs
.src('js/+([0-9])-*.js', { ...opts, read: false, sourcemaps })
.pipe(bundle(opts))
.pipe(uglify({ output: { comments: /^! / } }))
// NOTE concat already uses stat from newest combined file
.pipe(concat('js/site.js')),
vfs
.src('js/vendor/*([^.])?(.bundle).js', { ...opts, read: false })
.pipe(bundle(opts))
.pipe(uglify({ output: { comments: /^! / } })),
vfs
.src('js/vendor/*.min.js', opts)
.pipe(map((file, enc, next) => next(null, Object.assign(file, { extname: '' }, { extname: '.js' })))),
// NOTE use the next line to bundle a JavaScript library that cannot be browserified, like jQuery
//vfs.src(require.resolve('<package-name-or-require-path>'), opts).pipe(concat('js/vendor/<library-name>.js')),
vfs
.src(['css/site.css', 'css/vendor/*.css'], { ...opts, sourcemaps })
.pipe(postcss((file) => ({ plugins: postcssPlugins, options: { file } }))),
vfs.src('font/*.{ttf,woff*(2)}', opts),
vfs.src('img/**/*.{gif,ico,jpg,png,svg}', opts).pipe(
preview
? through()
: imagemin(
[
imagemin.gifsicle(),
imagemin.jpegtran(),
imagemin.optipng(),
imagemin.svgo({
plugins: [
{ cleanupIDs: { preservePrefixes: ['icon-', 'view-'] } },
{ removeViewBox: false },
{ removeDesc: false },
],
}),
].reduce((accum, it) => (it ? accum.concat(it) : accum), [])
)
),
vfs.src('helpers/*.js', opts),
vfs.src('layouts/*.hbs', opts),
vfs.src('partials/*.hbs', opts),
vfs.src('static/**/*[!~]', { ...opts, base: ospath.join(src, 'static'), dot: true })
).pipe(vfs.dest(dest, { sourcemaps: sourcemaps && '.' }))
}
function bundle ({ base: basedir, ext: bundleExt = '.bundle.js' }) {
return map((file, enc, next) => {
if (bundleExt && file.relative.endsWith(bundleExt)) {
const mtimePromises = []
const bundlePath = file.path
browserify(file.relative, { basedir, detectGlobals: false })
.plugin('browser-pack-flat/plugin')
.on('file', (bundledPath) => {
if (bundledPath !== bundlePath) mtimePromises.push(fs.stat(bundledPath).then(({ mtime }) => mtime))
})
.bundle((bundleError, bundleBuffer) =>
Promise.all(mtimePromises).then((mtimes) => {
const newestMtime = mtimes.reduce((max, curr) => (curr > max ? curr : max), file.stat.mtime)
if (newestMtime > file.stat.mtime) file.stat.mtimeMs = +(file.stat.mtime = newestMtime)
if (bundleBuffer !== undefined) file.contents = bundleBuffer
next(bundleError, Object.assign(file, { path: file.path.slice(0, file.path.length - 10) + '.js' }))
})
)
return
}
fs.readFile(file.path, 'UTF-8').then((contents) => {
next(null, Object.assign(file, { contents: Buffer.from(contents) }))
})
})
}
function postcssPseudoElementFixer (css, result) {
css.walkRules(/(?:^|[^:]):(?:before|after)/, (rule) => {
rule.selector = rule.selectors.map((it) => it.replace(/(^|[^:]):(before|after)$/, '$1::$2')).join(',')
})
}
'use strict'
const prettier = require('../lib/gulp-prettier-eslint')
const vfs = require('vinyl-fs')
module.exports = (files) => () =>
vfs
.src(files)
.pipe(prettier())
.pipe(vfs.dest((file) => file.base))
'use strict'
const camelCase = (name) => name.replace(/[-]./g, (m) => m.slice(1).toUpperCase())
module.exports = require('require-directory')(module, __dirname, { recurse: false, rename: camelCase })
'use strict'
const stylelint = require('gulp-stylelint')
const vfs = require('vinyl-fs')
module.exports = (files) => (done) =>
vfs
.src(files)
.pipe(stylelint({ reporters: [{ formatter: 'string', console: true }], failAfterError: true }))
.on('error', done)
'use strict'
const eslint = require('gulp-eslint')
const vfs = require('vinyl-fs')
module.exports = (files) => (done) =>
vfs
.src(files)
.pipe(eslint())
.pipe(eslint.format())
.pipe(eslint.failAfterError())
.on('error', done)
'use strict'
const ospath = require('path')
const vfs = require('vinyl-fs')
const zip = (() => {
try {
return require('@vscode/gulp-vinyl-zip')
} catch {
return require('gulp-vinyl-zip')
}
})()
module.exports = (src, dest, bundleName, onFinish) => () =>
vfs
.src('**/*', { base: src, cwd: src, dot: true })
.pipe(zip.dest(ospath.join(dest, `${bundleName}-bundle.zip`)))
.on('finish', () => onFinish && onFinish(ospath.resolve(dest, `${bundleName}-bundle.zip`)))
'use strict'
const fs = require('fs-extra')
const { Transform } = require('stream')
const map = (transform) => new Transform({ objectMode: true, transform })
const vfs = require('vinyl-fs')
module.exports = (files) => () =>
vfs.src(files, { allowEmpty: true }).pipe(map((file, enc, next) => fs.remove(file.path, next)))
'use strict'
const connect = require('gulp-connect')
const os = require('os')
const ANY_HOST = '0.0.0.0'
const URL_RX = /(https?):\/\/(?:[^/: ]+)(:\d+)?/
module.exports = (root, opts = {}, watch = undefined) => (done) => {
connect.server({ ...opts, middleware: opts.host === ANY_HOST ? decorateLog : undefined, root }, function () {
this.server.on('close', done)
if (watch) watch()
})
}
function decorateLog (_, app) {
const _log = app.log
app.log = (msg) => {
if (msg.startsWith('Server started ')) {
const localIp = getLocalIp()
const replacement = '$1://localhost$2' + (localIp ? ` and $1://${localIp}$2` : '')
msg = msg.replace(URL_RX, replacement)
}
_log(msg)
}
return []
}
function getLocalIp () {
for (const records of Object.values(os.networkInterfaces())) {
for (const record of records) {
if (!record.internal && record.family === 'IPv4') return record.address
}
}
return 'localhost'
}
'use strict'
const { parallel, series, watch } = require('gulp')
const createTask = require('./gulp.d/lib/create-task')
const exportTasks = require('./gulp.d/lib/export-tasks')
const log = require('fancy-log')
const bundleName = 'ui'
const buildDir = 'build'
const previewSrcDir = 'preview-src'
const previewDestDir = 'public'
const srcDir = 'src'
const destDir = `${previewDestDir}/_`
const { reload: livereload } = process.env.LIVERELOAD === 'true' ? require('gulp-connect') : {}
const serverConfig = { host: '0.0.0.0', port: 5252, livereload }
const task = require('./gulp.d/tasks')
const glob = {
all: [srcDir, previewSrcDir],
css: `${srcDir}/css/**/*.css`,
js: ['gulpfile.js', 'gulp.d/**/*.js', `${srcDir}/helpers/*.js`, `${srcDir}/js/**/+([^.])?(.bundle).js`],
}
const cleanTask = createTask({
name: 'clean',
desc: 'Clean files and folders generated by build',
call: task.remove(['build', 'public']),
})
const lintCssTask = createTask({
name: 'lint:css',
desc: 'Lint the CSS source files using stylelint (standard config)',
call: task.lintCss(glob.css),
})
const lintJsTask = createTask({
name: 'lint:js',
desc: 'Lint the JavaScript source files using eslint (JavaScript Standard Style)',
call: task.lintJs(glob.js),
})
const lintTask = createTask({
name: 'lint',
desc: 'Lint the CSS and JavaScript source files',
call: parallel(lintCssTask, lintJsTask),
})
const formatTask = createTask({
name: 'format',
desc: 'Format the JavaScript source files using prettify (JavaScript Standard Style)',
call: task.format(glob.js),
})
const buildTask = createTask({
name: 'build',
desc: 'Build and stage the UI assets for bundling',
call: task.build(
srcDir,
destDir,
process.argv.slice(2).some((name) => name.startsWith('preview'))
),
})
const bundleBuildTask = createTask({
name: 'bundle:build',
call: series(cleanTask, lintTask, buildTask),
})
const bundlePackTask = createTask({
name: 'bundle:pack',
desc: 'Create a bundle of the staged UI assets for publishing',
call: task.pack(
destDir,
buildDir,
bundleName,
(bundlePath) => !process.env.CI && log(`Antora option: --ui-bundle-url=${bundlePath}`)
),
})
const bundleTask = createTask({
name: 'bundle',
desc: 'Clean, lint, build, and bundle the UI for publishing',
call: series(bundleBuildTask, bundlePackTask),
})
const packTask = createTask({
name: 'pack',
desc: '(deprecated; use bundle instead)',
call: series(bundleTask),
})
const buildPreviewPagesTask = createTask({
name: 'preview:build-pages',
call: task.buildPreviewPages(srcDir, previewSrcDir, previewDestDir, livereload),
})
const previewBuildTask = createTask({
name: 'preview:build',
desc: 'Process and stage the UI assets and generate pages for the preview',
call: parallel(buildTask, buildPreviewPagesTask),
})
const previewServeTask = createTask({
name: 'preview:serve',
call: task.serve(previewDestDir, serverConfig, () => watch(glob.all, previewBuildTask)),
})
const previewTask = createTask({
name: 'preview',
desc: 'Generate a preview site and launch a server to view it',
call: series(previewBuildTask, previewServeTask),
})
module.exports = exportTasks(
bundleTask,
cleanTask,
lintTask,
formatTask,
buildTask,
bundleTask,
bundlePackTask,
previewTask,
previewBuildTask,
packTask
)
'use strict'
// This placeholder script allows this package to be discovered using require.resolve.
// It may be used in the future to export information about the files in this UI.
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "@antora/ui-default",
"description": "An archetype project that produces a UI for creating documentation sites with Antora",
"homepage": "https://gitlab.com/antora/antora-ui-default",
"license": "MPL-2.0",
"repository": {
"type": "git",
"url": "https://gitlab.com/antora/antora-ui-default.git"
},
"engines": {
"node": ">= 8.0.0"
},
"browserslist": [
"last 2 versions"
],
"devDependencies": {
"@asciidoctor/core": "~2.2",
"@fontsource/roboto": "~4.5",
"@fontsource/roboto-mono": "~4.5",
"@vscode/gulp-vinyl-zip": "~2.5",
"autoprefixer": "~9.7",
"browser-pack-flat": "~3.4",
"browserify": "~16.5",
"cssnano": "~4.1",
"eslint": "~6.8",
"eslint-config-standard": "~14.1",
"eslint-plugin-import": "~2.20",
"eslint-plugin-node": "~11.1",
"eslint-plugin-promise": "~4.2",
"eslint-plugin-standard": "~4.0",
"fancy-log": "~1.3",
"fs-extra": "~8.1",
"gulp": "~4.0",
"gulp-concat": "~2.6",
"gulp-connect": "~5.7",
"gulp-eslint": "~6.0",
"gulp-imagemin": "~6.2",
"gulp-postcss": "~8.0",
"gulp-stylelint": "~13.0",
"gulp-uglify": "~3.0",
"handlebars": "~4.7",
"highlight.js": "9.18.3",
"js-yaml": "~3.13",
"merge-stream": "~2.0",
"postcss-calc": "~7.0",
"postcss-custom-properties": "~9.1",
"postcss-import": "~12.0",
"postcss-url": "~8.0",
"prettier-eslint": "~9.0",
"require-directory": "~2.1",
"require-from-string": "~2.0",
"stylelint": "~13.3",
"stylelint-config-standard": "~20.0",
"vinyl-fs": "~3.0"
}
}
= Hardware and Software Requirements
Author Name
:idprefix:
:idseparator: -
:!example-caption:
:!table-caption:
//:page-role: -toc
:page-pagination:
[.float-group]
--
image:multirepo-ssg.svg[Multirepo SSG,180,135,float=right,role=float-gap]
Platonem complectitur mediocritatem ea eos.
Ei nonumy deseruisse ius.
Mel id omnes verear.
Vis no velit audiam, sonet <<dependencies,praesent>> eum ne.
*Prompta eripuit* nec ad.
Integer diam enim, dignissim eget eros et, ultricies mattis odio.
--
Vestibulum consectetur nec urna a luctus.
Quisque pharetra tristique arcu fringilla dapibus.
https://example.org[Curabitur,role=unresolved] ut massa aliquam, cursus enim et, accumsan lectus.
Mauris eget leo nunc, nec tempus mi? Curabitur id nisl mi, ut vulputate urna.
== Cu solet
Nominavi luptatum eos, an vim hinc philosophia intellegebat.
Lorem pertinacia `expetenda` et nec, [.underline]#wisi# illud [.line-through]#sonet# qui ea.
H~2~0.
E = mc^2^.
*Alphabet* *алфавит* _алфавит_ *_алфавит_*.
Eum an doctus <<liber-recusabo,maiestatis efficiantur>>.
Eu mea inani iriure.footnote:[Quisque porta facilisis tortor, vitae bibendum velit fringilla vitae! Lorem ipsum dolor sit amet, consectetur adipiscing elit.]
[,json]
----
{
"name": "module-name",
"version": "10.0.1",
"description": "An example module to illustrate the usage of package.json",
"author": "Author Name <author@example.com>",
"scripts": {
"test": "mocha",
"lint": "eslint"
}
}
----
.Example paragraph syntax
[,asciidoc]
----
.Optional title
[example]
This is an example paragraph.
----
.Optional title
[example]
This is an example paragraph.
.Summary *Spoiler Alert!*
[%collapsible]
====
Details.
Loads of details.
====
[,asciidoc]
----
Voila!
----
.Result
[%collapsible.result]
====
Voila!
====
=== Some Code
How about some code?
[,js]
----
vfs
.src('js/vendor/*.js', { cwd: 'src', cwdbase: true, read: false })
.pipe(tap((file) => { // <.>
file.contents = browserify(file.relative, { basedir: 'src', detectGlobals: false }).bundle()
}))
.pipe(buffer()) // <.>
.pipe(uglify())
.pipe(gulp.dest('build'))
----
<.> The `tap` function is used to wiretap the data in the pipe.
<.> Wrap each streaming file in a buffer so the files can be processed by uglify.
Uglify can only work with buffers, not streams.
Execute these commands to validate and build your site:
$ podman run -v $PWD:/antora:Z --rm -t antora/antora \
version
3.0.0
$ podman run -v $PWD:/antora:Z --rm -it antora/antora \
--clean \
antora-playbook.yml
Cum dicat #putant# ne.
Est in <<inline,reque>> homero principes, meis deleniti mediocrem ad has.
Altera atomorum his ex, has cu elitr melius propriae.
Eos suscipit scaevola at.
....
pom.xml
src/
main/
java/
HelloWorld.java
test/
java/
HelloWorldTest.java
....
Eu mea munere vituperata constituam.
[%autowidth]
|===
|Input | Output | Example
m|"foo\nbar"
l|foo
bar
a|
[,ruby]
----
puts "foo\nbar"
----
|===
Here we just have some plain text.
[source]
----
plain text
----
[.rolename]
=== Liber recusabo
Select menu:File[Open Project] to open the project in your IDE.
Per ea btn:[Cancel] inimicus.
Ferri kbd:[F11] tacimates constituam sed ex, eu mea munere vituperata kbd:[Ctrl,T] constituam.
.Sidebar Title
****
Platonem complectitur mediocritatem ea eos.
Ei nonumy deseruisse ius.
Mel id omnes verear.
Altera atomorum his ex, has cu elitr melius propriae.
Eos suscipit scaevola at.
****
No sea, at invenire voluptaria mnesarchum has.
Ex nam suas nemore dignissim, vel apeirian democritum et.
At ornatus splendide sed, phaedrum omittantur usu an, vix an noster voluptatibus.
---
.Ordered list with customized numeration
[upperalpha]
. potenti donec cubilia tincidunt
. etiam pulvinar inceptos velit quisque aptent himenaeos
. lacus volutpat semper porttitor aliquet ornare primis nulla enim
Natum facilisis theophrastus an duo.
No sea, at invenire voluptaria mnesarchum has.
.Unordered list with customized marker
[square]
* ultricies sociosqu tristique integer
* lacus volutpat semper porttitor aliquet ornare primis nulla enim
* etiam pulvinar inceptos velit quisque aptent himenaeos
Eu sed antiopam gloriatur.
Ea mea agam graeci philosophia.
[circle]
* circles
** circles
*** and more circles!
At ornatus splendide sed, phaedrum omittantur usu an, vix an noster voluptatibus.
* [ ] todo
* [x] done!
Vis veri graeci legimus ad.
sed::
splendide sed
mea::
tad::
agam graeci
Let's look at that another way.
[horizontal]
sed::
splendide sed
mea::
agam graeci
At ornatus splendide sed.
.Library dependencies
[#dependencies%autowidth%footer,stripes=hover]
|===
|Library |Version
|eslint
|^1.7.3
|eslint-config-gulp
|^2.0.0
|expect
|^1.20.2
|istanbul
|^0.4.3
|istanbul-coveralls
|^1.0.3
|jscs
|^2.3.5
h|Total
|6
|===
Cum dicat putant ne.
Est in reque homero principes, meis deleniti mediocrem ad has.
Altera atomorum his ex, has cu elitr melius propriae.
Eos suscipit scaevola at.
[TIP]
This oughta do it!
Cum dicat putant ne.
Est in reque homero principes, meis deleniti mediocrem ad has.
Altera atomorum his ex, has cu elitr melius propriae.
Eos suscipit scaevola at.
[NOTE]
====
You've been down _this_ road before.
====
Cum dicat putant ne.
Est in reque homero principes, meis deleniti mediocrem ad has.
Altera atomorum his ex, has cu elitr melius propriae.
Eos suscipit scaevola at.
[WARNING]
====
Watch out!
====
[CAUTION]
====
[#inline]#I wouldn't try that if I were you.#
====
[IMPORTANT]
====
Don't forget this step!
====
.Key Points to Remember
[TIP]
====
If you installed the CLI and the default site generator globally, you can upgrade both of them with the same command.
$ npm i -g @antora/cli @antora/site-generator
Or you can install the metapackage to upgrade both packages at once.
$ npm i -g antora
====
Nominavi luptatum eos, an vim hinc philosophia intellegebat.
Eu mea inani iriure.
[discrete]
== Voluptua singulis
[discrete]
=== Nominavi luptatum
Cum dicat putant ne.
Est in reque homero principes, meis deleniti mediocrem ad has.
Ex nam suas nemore dignissim, vel apeirian democritum et.
.Antora is a multi-repo documentation site generator
image::multirepo-ssg.svg[Multirepo SSG,3000,opts=interactive]
.Let's see that again, but a little smaller
image::multirepo-ssg.svg[Multirepo SSG,300,role=text-left]
Make the switch today!
.Full Circle with Jake Blauvelt
video::300817511[vimeo,640,360,align=left]
[#english+中文]
== English + 中文
Altera atomorum his ex, has cu elitr melius propriae.
Eos suscipit scaevola at.
[,'Famous Person. Cum dicat putant ne.','Cum dicat putant ne. https://example.com[Famous Person Website]']
____
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris eget leo nunc, nec tempus mi? Curabitur id nisl mi, ut vulputate urna.
Quisque porta facilisis tortor, vitae bibendum velit fringilla vitae!
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Mauris eget leo nunc, nec tempus mi? Curabitur id nisl mi, ut vulputate urna.
Quisque porta facilisis tortor, vitae bibendum velit fringilla vitae!
____
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
[verse]
____
The fog comes
on little cat feet.
____
== Fin
That's all, folks!
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 30">
<defs>
<filter id="b" width="1.041" height="1.058" x="-.021" y="-.029" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation=".172"/>
</filter>
<filter id="a" width="1.021" height="1.029" x="-.01" y="-.014" color-interpolation-filters="sRGB">
<feGaussianBlur stdDeviation=".086"/>
</filter>
</defs>
<rect width="20.063" height="14.331" x="9.968" y="14.969" fill="#3d3d3d" filter="url(#a)" rx="1.66" ry="1.66"/>
<path fill="#ff6f00" stroke-width="1.113" d="M27.364 4.53a3.8 3.8 0 0 0-1.201 7.406c.19.035.26-.082.26-.183l-.006-.707c-1.057.23-1.28-.449-1.28-.449-.173-.439-.422-.556-.422-.556-.345-.236.026-.23.026-.23.382.026.583.39.583.39.338.582.889.414 1.105.317.035-.246.133-.413.242-.508-.844-.096-1.732-.422-1.732-1.878 0-.415.149-.754.392-1.02-.04-.096-.17-.483.037-1.006 0 0 .319-.102 1.045.39.303-.085.628-.127.951-.128.323.001.648.043.952.128.725-.492 1.044-.39 1.044-.39.207.523.077.91.037 1.006.244.266.391.605.391 1.02 0 1.46-.889 1.78-1.735 1.875.137.118.258.349.258.703 0 .509-.004.918-.004 1.043 0 .101.068.22.26.183a3.8 3.8 0 0 0-1.203-7.406zm-2.377 5.413c-.008.02-.038.025-.065.012-.027-.013-.043-.038-.034-.057.008-.02.038-.025.065-.012.028.012.044.038.034.057zm.187.167c-.018.017-.053.01-.077-.017-.025-.027-.03-.062-.012-.08.02-.016.053-.008.078.018.025.027.03.062.011.08zm.128.213c-.023.017-.061.002-.085-.032-.023-.034-.023-.075.001-.09.024-.017.061-.002.085.031.023.034.023.075 0 .091zm.217.248c-.02.023-.065.016-.097-.015-.034-.03-.043-.074-.022-.097.021-.023.066-.017.098.015.033.03.043.074.021.097zm.28.083c-.009.03-.051.043-.094.03-.043-.012-.072-.047-.063-.077.01-.03.052-.044.095-.03.043.012.071.047.063.077zm.32.035c0 .032-.036.058-.081.058-.046.001-.082-.024-.083-.055 0-.031.036-.057.081-.058.046 0 .083.024.083.055zm.313-.012c.005.031-.026.062-.071.07-.044.009-.085-.01-.09-.04-.006-.032.026-.063.07-.071.045-.008.085.01.09.041z"/>
<path fill="#ff6f00" stroke-width=".902" d="M23.85 3.643L20.355.151a.515.515 0 0 0-.728 0l-.726.725.92.92a.612.612 0 0 1 .775.78l.887.887a.612.612 0 1 1-.367.345l-.827-.827v2.177a.613.613 0 1 1-.504-.018V2.943a.613.613 0 0 1-.333-.804l-.907-.907-2.395 2.395a.515.515 0 0 0 0 .729l3.493 3.493a.515.515 0 0 0 .728 0l3.477-3.477a.516.516 0 0 0 0-.729"/>
<g fill-rule="evenodd">
<path fill="#fc6d26" d="M16.363 7.948l-.425-1.309-.843-2.594a.145.145 0 0 0-.275 0l-.843 2.594h-2.799l-.843-2.594a.145.145 0 0 0-.275 0l-.843 2.594-.425 1.309a.29.29 0 0 0 .105.324l3.68 2.674 3.68-2.674a.29.29 0 0 0 .106-.324"/>
<path fill="#c43e00" d="M12.577 10.942l1.4-4.307h-2.8z"/>
<path fill="#ff6f00" d="M12.577 10.942l-1.399-4.307H9.217z"/>
<path fill="#ffa040" d="M9.217 6.631L8.792 7.94a.29.29 0 0 0 .105.324l3.68 2.674z"/>
<path fill="#c43e00" d="M9.217 6.632h1.961l-.843-2.594a.145.145 0 0 0-.275 0z"/>
<path fill="#ff6f00" d="M12.578 10.942l1.4-4.307h1.96z"/>
<path fill="#ffa040" d="M15.938 6.631l.426 1.309a.29.29 0 0 1-.106.324l-3.68 2.674z"/>
<path fill="#c43e00" d="M15.938 6.632h-1.961l.843-2.594a.145.145 0 0 1 .275 0z"/>
</g>
<g fill="none" stroke="#ff6f00" stroke-linecap="round" stroke-linejoin="round" stroke-width=".38">
<path d="M27.364 12.94v3.1M20 9v8.1M12.578 11.94v4.1"/>
</g>
<rect width="20.063" height="14.331" x="9.968" y="14.969" fill="#505050" rx="1.66" ry="1.66"/>
<rect width="20.063" height="14.331" x="9.968" y="14.969" fill="#484848" fill-opacity=".953" filter="url(#b)" rx="1.66" ry="1.66"/>
<rect width="18.339" height="12.762" x="10.83" y="15.754" fill="#3d3d3d" rx=".42" ry=".42"/>
<path fill="none" stroke="#ff6f00" stroke-linecap="round" stroke-linejoin="round" stroke-width=".24" d="M18.222 17.496h-6.35"/>
<rect width="7.365" height="5.758" x="20.885" y="16.897" fill="#a1a1a1" rx=".4" ry=".4"/>
<path fill="#ff6f00" d="M25.399 18.297c.367 0 2.242 3.657 2.058 3.975-.183.318-5.797.318-5.98 0-.184-.318 3.555-3.975 3.922-3.975z"/>
<g fill="none" stroke="#ff6f00" stroke-linecap="round" stroke-linejoin="round" stroke-width=".24">
<path d="M17.163 19.437h-5.292M17.163 20.437h-5.292M15.047 21.437h-3.175"/>
</g>
<path fill="#a1a1a1" d="M11.848 25.486a.099.099 0 0 0-.098.1v2.929h.183V25.68c0-.014.012-.025.025-.025h16.083c.014 0 .025.011.025.025v2.834h.183v-2.928c0-.056-.043-.1-.097-.1z"/>
<path fill="#505050" d="M11.958 25.656a.025.025 0 0 0-.025.025v2.839h16.133V25.68a.025.025 0 0 0-.024-.025z"/>
<g fill="none" stroke="#ff6f00" stroke-linecap="round" stroke-linejoin="round">
<g stroke-width=".2">
<path d="M15.124 26.486h-2.117M17.77 26.486h-1.587M16.912 28.074h-2.117M15.653 27.245h-1.587"/>
</g>
<path stroke-width=".24" d="M17.163 24.671h-5.292"/>
<path stroke-width=".2" d="M20.416 26.486h-1.587"/>
</g>
</svg>
antoraVersion: '1.0.0'
site:
url: http://localhost:5252
title: Brand Docs
homeUrl: &home_url /xyz/5.2/index.html
components:
- name: abc
title: Project ABC
url: '#'
versions:
- &latest_version_abc
url: '#'
version: '1.1'
displayVersion: '1.1'
- url: '#'
version: '1.0'
displayVersion: '1.0'
latestVersion: *latest_version_abc
- &component
name: xyz
title: &component_title Project XYZ
url: /xyz/6.0/index.html
versions:
- &latest_version_xyz
url: /xyz/6.0/index.html
version: '6.0'
displayVersion: '6.0'
- &component_version
title: *component_title
url: '#'
version: '5.2'
displayVersion: '5.2'
- url: '#'
version: '5.1'
displayVersion: '5.1'
- url: '#'
version: '5.0'
displayVersion: '5.0'
latestVersion: *latest_version_xyz
- name: '123'
title: Project 123
url: '#'
versions:
- &latest_version_123
url: '#'
version: '2.2'
displayVersion: '2.2'
- url: '#'
version: '2.1'
displayVersion: '2.1'
- url: '#'
version: '2.0'
displayVersion: '2.0'
latestVersion: *latest_version_123
page:
url: *home_url
home: true
title: Brand&#8217;s Hardware &amp; Software Requirements
component: *component
componentVersion: *component_version
version: '5.2'
displayVersion: '5.2'
module: ROOT
relativeSrcPath: index.adoc
editUrl: http://example.com/project-xyz/blob/main/index.adoc
origin:
private: false
previous:
content: Quickstart
url: '#'
urlType: 'internal'
next:
content: Liber Recusabo
url: '#'
urlType: 'internal'
breadcrumbs:
- content: Quickstart
url: '#'
urlType: fragment
- content: Brand&#8217;s Hardware &amp; Software Requirements
url: /xyz/5.2/index.html
urlType: internal
versions:
- version: '6.0'
displayVersion: '6.0'
url: '#'
- version: '5.2'
displayVersion: '5.2'
url: '#'
- version: '5.1'
displayVersion: '5.1'
url: '#'
- version: '5.0'
displayVersion: '5.0'
missing: true
url: '#'
navigation:
- root: true
items:
- content: Quickstart
url: '#'
urlType: fragment
items:
- content: Brand&#8217;s Hardware &amp; Software Requirements
url: /xyz/5.2/index.html
urlType: internal
- content: Cu Solet
url: '/xyz/5.2/index.html#cu-solet'
urlType: internal
- content: English + 中文
url: '/xyz/5.2/index.html#english+中文'
urlType: internal
- content: Liber Recusabo
url: '#liber-recusabo'
urlType: fragment
- content: Reference
items:
- content: Keyboard Shortcuts
url: '#'
urlType: fragment
- content: Importing and Exporting
url: '#'
urlType: fragment
- content: Some Code
url: '/xyz/5.2/index.html#some-code'
urlType: internal
*,
*::before,
*::after {
box-sizing: inherit;
}
html {
box-sizing: border-box;
font-size: var(--body-font-size);
height: 100%;
scroll-behavior: smooth;
}
@media screen and (min-width: 1024px) {
html {
font-size: var(--body-font-size--desktop);
}
}
body {
background: var(--body-background);
color: var(--body-font-color);
font-family: var(--body-font-family);
line-height: var(--body-line-height);
margin: 0;
tab-size: 4;
word-wrap: anywhere; /* aka overflow-wrap; used when hyphens are disabled or aren't sufficient */
}
a {
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:active {
background-color: none;
}
code,
kbd,
pre {
font-family: var(--monospace-font-family);
}
b,
dt,
strong,
th {
font-weight: var(--body-font-weight-bold);
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
em em { /* stylelint-disable-line */
font-style: normal;
}
strong strong { /* stylelint-disable-line */
font-weight: normal;
}
button {
cursor: pointer;
font-family: inherit;
font-size: 1em;
line-height: var(--body-line-height);
margin: 0;
}
button::-moz-focus-inner {
border: none;
padding: 0;
}
summary {
cursor: pointer;
-webkit-tap-highlight-color: transparent;
outline: none;
}
table {
border-collapse: collapse;
word-wrap: normal; /* table widths aren't computed as expected when word-wrap is enabled */
}
object[type="image/svg+xml"]:not([width]) {
width: fit-content;
}
::placeholder {
opacity: 0.5;
}
@media (pointer: fine) {
@supports (scrollbar-width: thin) {
html {
scrollbar-color: var(--scrollbar-thumb-color) var(--scrollbar-track-color);
}
body * {
scrollbar-width: thin;
scrollbar-color: var(--scrollbar-thumb-color) transparent;
}
}
html::-webkit-scrollbar {
background-color: var(--scrollbar-track-color);
height: 12px;
width: 12px;
}
body ::-webkit-scrollbar {
height: 6px;
width: 6px;
}
::-webkit-scrollbar-thumb {
background-clip: padding-box;
background-color: var(--scrollbar-thumb-color);
border: 3px solid transparent;
border-radius: 12px;
}
body ::-webkit-scrollbar-thumb {
border-width: 1.75px;
border-radius: 6px;
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--scrollbar_hover-thumb-color);
}
}
@media screen and (min-width: 1024px) {
.body {
display: flex;
}
}
.breadcrumbs {
display: none;
flex: 1 1;
padding: 0 0.5rem 0 0.75rem;
line-height: var(--nav-line-height);
}
@media screen and (min-width: 1024px) {
.breadcrumbs {
display: block;
}
}
a + .breadcrumbs {
padding-left: 0.05rem;
}
.breadcrumbs ul {
display: flex;
flex-wrap: wrap;
margin: 0;
padding: 0;
list-style: none;
}
.breadcrumbs li {
display: inline;
margin: 0;
}
.breadcrumbs li::after {
content: "/";
padding: 0 0.5rem;
}
.breadcrumbs li:last-of-type::after {
content: none;
}
.doc {
color: var(--doc-font-color);
font-size: var(--doc-font-size);
hyphens: auto;
line-height: var(--doc-line-height);
margin: var(--doc-margin);
max-width: var(--doc-max-width);
padding: 0 1rem 4rem;
}
@media screen and (min-width: 1024px) {
.doc {
flex: auto;
font-size: var(--doc-font-size--desktop);
margin: var(--doc-margin--desktop);
max-width: var(--doc-max-width--desktop);
min-width: 0;
}
}
.doc h1,
.doc h2,
.doc h3,
.doc h4,
.doc h5,
.doc h6 {
color: var(--heading-font-color);
font-weight: var(--heading-font-weight);
hyphens: none;
line-height: 1.3;
margin: 1rem 0 0;
}
.doc > h1.page:first-child {
font-size: calc(36 / var(--rem-base) * 1rem);
margin: 1.5rem 0;
}
@media screen and (min-width: 769px) {
.doc > h1.page:first-child {
margin-top: 2.5rem;
}
}
.doc > h1.page:first-child + aside.toc.embedded {
margin-top: -0.5rem;
}
.doc > h2#name + .sectionbody {
margin-top: 1rem;
}
#preamble + .sect1,
.doc .sect1 + .sect1 {
margin-top: 2rem;
}
.doc h1.sect0 {
background: var(--abstract-background);
font-size: 1.8em;
margin: 1.5rem -1rem 0;
padding: 0.5rem 1rem;
}
.doc h2:not(.discrete) {
border-bottom: 1px solid var(--section-divider-color);
margin-left: -1rem;
margin-right: -1rem;
padding: 0.4rem 1rem 0.1rem;
}
.doc h3:not(.discrete),
.doc h4:not(.discrete) {
font-weight: var(--alt-heading-font-weight);
}
.doc h1 .anchor,
.doc h2 .anchor,
.doc h3 .anchor,
.doc h4 .anchor,
.doc h5 .anchor,
.doc h6 .anchor {
position: absolute;
text-decoration: none;
width: 1.75ex;
margin-left: -1.5ex;
visibility: hidden;
font-size: 0.8em;
font-weight: normal;
padding-top: 0.05em;
}
.doc h1 .anchor::before,
.doc h2 .anchor::before,
.doc h3 .anchor::before,
.doc h4 .anchor::before,
.doc h5 .anchor::before,
.doc h6 .anchor::before {
content: "\00a7";
}
.doc h1:hover .anchor,
.doc h2:hover .anchor,
.doc h3:hover .anchor,
.doc h4:hover .anchor,
.doc h5:hover .anchor,
.doc h6:hover .anchor {
visibility: visible;
}
.doc p,
.doc dl {
margin: 0;
}
.doc a {
color: var(--link-font-color);
}
.doc a:hover {
color: var(--link_hover-font-color);
}
.doc a.bare {
hyphens: none;
}
.doc a.unresolved {
color: var(--link_unresolved-font-color);
}
.doc i.fa {
hyphens: none;
font-style: normal;
}
.doc p code,
.doc thead code,
.doc .colist > table code {
color: var(--code-font-color);
background: var(--code-background);
border-radius: 0.25em;
font-size: 0.95em;
padding: 0.125em 0.25em;
}
.doc code,
.doc pre {
hyphens: none;
}
.doc pre {
font-size: calc(16 / var(--rem-base) * 1rem);
line-height: 1.5;
margin: 0;
}
.doc blockquote {
margin: 0;
}
.doc .paragraph.lead > p {
font-size: calc(18 / var(--rem-base) * 1rem);
}
.doc .right {
float: right;
}
.doc .left {
float: left;
}
.doc .float-gap.right {
margin: 0 1rem 1rem 0;
}
.doc .float-gap.left {
margin: 0 0 1rem 1rem;
}
.doc .float-group::after {
content: "";
display: table;
clear: both;
}
.doc .text-left {
text-align: left;
}
.doc .text-center {
text-align: center;
}
.doc .text-right {
text-align: right;
}
.doc .text-justify {
text-align: justify;
}
.doc .stretch {
width: 100%;
}
.doc .big {
font-size: larger;
}
.doc .small {
font-size: smaller;
}
.doc .underline {
text-decoration: underline;
}
.doc .line-through {
text-decoration: line-through;
}
.doc .paragraph,
.doc .dlist,
.doc .hdlist,
.doc .olist,
.doc .ulist,
.doc .exampleblock,
.doc .imageblock,
.doc .listingblock,
.doc .literalblock,
.doc .tabs,
.doc .sidebarblock,
.doc .verseblock,
.doc .videoblock,
.doc .quoteblock,
.doc .partintro,
.doc details,
.doc hr {
margin: 1rem 0 0;
}
.doc > table.tableblock,
.doc > table.tableblock + *,
.doc .tablecontainer,
.doc .tablecontainer + *,
.doc :not(.tablecontainer) > table.tableblock,
.doc :not(.tablecontainer) > table.tableblock + * {
margin-top: 1.5rem;
}
.doc table.tableblock {
font-size: calc(15 / var(--rem-base) * 1rem);
}
.doc p.tableblock + p.tableblock {
margin-top: 0.5rem;
}
.doc table.tableblock pre {
font-size: inherit;
}
.doc td.tableblock > .content {
word-wrap: anywhere; /* aka overflow-wrap; used when hyphens are disabled or aren't sufficient */
}
.doc td.tableblock > .content > :first-child {
margin-top: 0;
}
.doc table.tableblock th,
.doc table.tableblock td {
padding: 0.5rem;
}
.doc table.tableblock,
.doc table.tableblock > * > tr > * {
border: 0 solid var(--table-border-color);
}
.doc table.grid-all > * > tr > * {
border-width: 1px;
}
.doc table.grid-cols > * > tr > * {
border-width: 0 1px;
}
.doc table.grid-rows > * > tr > * {
border-width: 1px 0;
}
.doc table.grid-all > thead th,
.doc table.grid-rows > thead th {
border-bottom-width: 2.5px;
}
.doc table.frame-all {
border-width: 1px;
}
.doc table.frame-ends {
border-width: 1px 0;
}
.doc table.frame-sides {
border-width: 0 1px;
}
.doc table.frame-none > colgroup + * > :first-child > *,
.doc table.frame-sides > colgroup + * > :first-child > * {
border-top-width: 0;
}
/* NOTE let the grid win in case of frame-none */
.doc table.frame-sides > :last-child > :last-child > * {
border-bottom-width: 0;
}
.doc table.frame-none > * > tr > :first-child,
.doc table.frame-ends > * > tr > :first-child {
border-left-width: 0;
}
.doc table.frame-none > * > tr > :last-child,
.doc table.frame-ends > * > tr > :last-child {
border-right-width: 0;
}
.doc table.stripes-all > tbody > tr,
.doc table.stripes-odd > tbody > tr:nth-of-type(odd),
.doc table.stripes-even > tbody > tr:nth-of-type(even),
.doc table.stripes-hover > tbody > tr:hover {
background: var(--table-stripe-background);
}
.doc table.tableblock > tfoot {
background: var(--table-footer-background);
}
.doc .halign-left {
text-align: left;
}
.doc .halign-right {
text-align: right;
}
.doc .halign-center {
text-align: center;
}
.doc .valign-top {
vertical-align: top;
}
.doc .valign-bottom {
vertical-align: bottom;
}
.doc .valign-middle {
vertical-align: middle;
}
.doc .admonitionblock {
margin: 1.4rem 0 0;
}
.doc .admonitionblock p,
.doc .admonitionblock td.content {
font-size: calc(16 / var(--rem-base) * 1rem);
}
.doc .admonitionblock td.content > :not(.title):first-child,
.doc .admonitionblock td.content > .title + * {
margin-top: 0;
}
.doc .admonitionblock td.content pre {
font-size: calc(15 / var(--rem-base) * 1rem);
}
.doc .admonitionblock > table {
table-layout: fixed;
position: relative;
width: 100%;
}
.doc .admonitionblock td.content {
padding: 1rem 1rem 0.75rem;
background: var(--admonition-background);
width: 100%;
word-wrap: anywhere;
}
.doc .admonitionblock td.icon {
font-size: calc(15 / var(--rem-base) * 1rem);
left: 0;
line-height: 1;
padding: 0;
position: absolute;
top: 0;
transform: translate(-0.5rem, -50%);
}
.doc .admonitionblock td.icon i {
align-items: center;
border-radius: 0.45rem;
display: inline-flex;
filter: initial;
height: 1.25rem;
padding: 0 0.5rem;
vertical-align: initial;
width: fit-content;
}
.doc .admonitionblock td.icon i::after {
content: attr(title);
font-weight: var(--admonition-label-font-weight);
font-style: normal;
text-transform: uppercase;
}
.doc .admonitionblock td.icon i.icon-caution {
background-color: var(--caution-color);
color: var(--caution-on-color);
}
.doc .admonitionblock td.icon i.icon-important {
background-color: var(--important-color);
color: var(--important-on-color);
}
.doc .admonitionblock td.icon i.icon-note {
background-color: var(--note-color);
color: var(--note-on-color);
}
.doc .admonitionblock td.icon i.icon-tip {
background-color: var(--tip-color);
color: var(--tip-on-color);
}
.doc .admonitionblock td.icon i.icon-warning {
background-color: var(--warning-color);
color: var(--warning-on-color);
}
.doc .imageblock,
.doc .videoblock {
display: flex;
flex-direction: column;
align-items: center;
}
.doc .imageblock .content {
align-self: stretch;
text-align: center;
}
.doc .imageblock.text-left,
.doc .videoblock.text-left {
align-items: flex-start;
}
.doc .imageblock.text-left .content {
text-align: left;
}
.doc .imageblock.text-right,
.doc .videoblock.text-right {
align-items: flex-end;
}
.doc .imageblock.text-right .content {
text-align: right;
}
.doc .imageblock img,
.doc .imageblock object,
.doc .imageblock svg,
.doc .image > img,
.doc .image > object,
.doc .image > svg {
display: inline-block;
height: auto;
max-width: 100%;
vertical-align: middle;
}
.doc .image:not(.left):not(.right) > img {
margin-top: -0.2em;
}
.doc .videoblock iframe,
.doc .videoblock video {
max-width: 100%;
vertical-align: middle;
}
#preamble .abstract blockquote {
background: var(--abstract-background);
border-left: 5px solid var(--abstract-border-color);
color: var(--abstract-font-color);
font-size: calc(16 / var(--rem-base) * 1rem);
padding: 0.75em 1em;
}
.doc .quoteblock,
.doc .verseblock {
background: var(--quote-background);
border-left: 5px solid var(--quote-border-color);
color: var(--quote-font-color);
}
.doc .quoteblock {
padding: 0.25rem 2rem 1.25rem;
}
.doc .quoteblock .attribution {
color: var(--quote-attribution-font-color);
font-size: calc(15 / var(--rem-base) * 1rem);
margin-top: 0.75rem;
}
.doc .quoteblock blockquote {
margin-top: 1rem;
}
.doc .quoteblock .paragraph {
font-style: italic;
}
.doc .quoteblock cite {
padding-left: 1em;
}
.doc .verseblock {
font-size: 1.15em;
padding: 1rem 2rem;
}
.doc .verseblock pre {
font-family: inherit;
font-size: inherit;
}
.doc ol,
.doc ul {
margin: 0;
padding: 0 0 0 2rem;
}
.doc ul.checklist,
.doc ul.none,
.doc ol.none,
.doc ul.no-bullet,
.doc ol.unnumbered,
.doc ul.unstyled,
.doc ol.unstyled {
list-style-type: none;
}
.doc ul.no-bullet,
.doc ol.unnumbered {
padding-left: 1.25rem;
}
.doc ul.unstyled,
.doc ol.unstyled {
padding-left: 0;
}
.doc ul.circle {
list-style-type: circle;
}
.doc ul.disc {
list-style-type: disc;
}
.doc ul.square {
list-style-type: square;
}
.doc ul.circle ul:not([class]),
.doc ul.disc ul:not([class]),
.doc ul.square ul:not([class]) {
list-style: inherit;
}
.doc ol.arabic {
list-style-type: decimal;
}
.doc ol.decimal {
list-style-type: decimal-leading-zero;
}
.doc ol.loweralpha {
list-style-type: lower-alpha;
}
.doc ol.upperalpha {
list-style-type: upper-alpha;
}
.doc ol.lowerroman {
list-style-type: lower-roman;
}
.doc ol.upperroman {
list-style-type: upper-roman;
}
.doc ol.lowergreek {
list-style-type: lower-greek;
}
.doc ul.checklist {
padding-left: 1.75rem;
}
.doc ul.checklist p > i.fa-check-square-o:first-child,
.doc ul.checklist p > i.fa-square-o:first-child {
display: inline-flex;
justify-content: center;
width: 1.25rem;
margin-left: -1.25rem;
}
.doc ul.checklist i.fa-check-square-o::before {
content: "\2713";
}
.doc ul.checklist i.fa-square-o::before {
content: "\274f";
}
.doc .dlist .dlist,
.doc .dlist .olist,
.doc .dlist .ulist,
.doc .olist .dlist,
.doc .olist .olist,
.doc .olist .ulist,
.doc .ulist .dlist,
.doc .ulist .olist,
.doc .ulist .ulist {
margin-top: 0.5rem;
}
.doc .olist li + li,
.doc .ulist li + li {
margin-top: 0.5rem;
}
.doc .ulist .listingblock,
.doc .olist .listingblock,
.doc .admonitionblock .listingblock {
padding: 0;
}
.doc .admonitionblock .title,
.doc .exampleblock .title,
.doc .imageblock .title,
.doc .literalblock .title,
.doc .listingblock .title,
.doc .openblock .title,
.doc .videoblock .title,
.doc table.tableblock caption {
color: var(--caption-font-color);
font-size: calc(16 / var(--rem-base) * 1rem);
font-style: var(--caption-font-style);
font-weight: var(--caption-font-weight);
hyphens: none;
letter-spacing: 0.01em;
padding-bottom: 0.075rem;
}
.doc table.tableblock caption {
text-align: left;
}
.doc .olist .title,
.doc .ulist .title {
font-style: var(--caption-font-style);
font-weight: var(--caption-font-weight);
margin-bottom: 0.25rem;
}
.doc .imageblock .title,
.doc .videoblock .title {
margin-top: 0.5rem;
padding-bottom: 0;
}
.doc details {
margin-left: 1rem;
}
.doc details > summary {
display: block;
position: relative;
line-height: var(--doc-line-height);
margin-bottom: 0.5rem;
}
.doc details > summary::-webkit-details-marker {
display: none;
}
.doc details > summary::before {
content: "";
border: solid transparent;
border-left-color: currentColor;
border-width: 0.3em 0 0.3em 0.5em;
position: absolute;
top: calc((var(--doc-line-height) * 0.5 - 0.3) * 1em);
left: -1rem;
transform: translateX(15%);
}
.doc details[open] > summary::before {
border-color: currentColor transparent transparent;
border-width: 0.5rem 0.3rem 0;
transform: translateY(15%);
}
.doc details > summary::after {
content: "";
width: 1rem;
height: 1em;
position: absolute;
top: calc((var(--doc-line-height) * 0.5 - 0.5) * 1em);
left: -1rem;
}
.doc details.result {
margin-top: 0.25rem;
}
.doc details.result > summary {
color: var(--caption-font-color);
font-style: italic;
margin-bottom: 0;
}
.doc details.result > .content {
margin-left: -1rem;
}
.doc .exampleblock > .content,
.doc details.result > .content {
background: var(--example-background);
border: 0.25rem solid var(--example-border-color);
border-radius: 0.5rem;
padding: 0.75rem;
}
.doc .exampleblock > .content::after,
.doc details.result > .content::after {
content: "";
display: table;
clear: both;
}
.doc .exampleblock > .content > :first-child,
.doc details > .content > :first-child {
margin-top: 0;
}
.doc .sidebarblock {
background: var(--sidebar-background);
border-radius: 0.75rem;
padding: 0.75rem 1.5rem;
}
.doc .sidebarblock > .content > .title {
font-size: calc(22.5 / var(--rem-base) * 1rem);
font-weight: var(--alt-heading-font-weight);
line-height: 1.3;
margin-bottom: 0.5rem;
text-align: center;
}
.doc .sidebarblock > .content > .title + *,
.doc .sidebarblock > .content > :not(.title):first-child {
margin-top: 0;
}
/* NEEDS REVIEW prevent pre in table from causing article to exceed bounds */
.doc table.tableblock pre,
.doc .listingblock.wrap pre {
white-space: pre-wrap;
}
.doc pre.highlight > code,
.doc .listingblock pre:not(.highlight),
.doc .literalblock pre {
background: var(--pre-background);
box-shadow: inset 0 0 1.75px var(--pre-border-color);
display: block;
overflow-x: auto;
padding: 0.875em;
}
.doc .listingblock > .content {
position: relative;
}
.doc .source-toolbox {
display: flex;
visibility: hidden;
position: absolute;
top: 0.25rem;
right: 0.5rem;
color: var(--pre-annotation-font-color);
font-family: var(--body-font-family);
font-size: calc(13 / var(--rem-base) * 1rem);
line-height: 1;
user-select: none;
white-space: nowrap;
z-index: 1;
}
.doc .listingblock:hover .source-toolbox {
visibility: visible;
}
.doc .source-toolbox .source-lang {
text-transform: uppercase;
letter-spacing: 0.075em;
}
.doc .source-toolbox > :not(:last-child)::after {
content: "|";
letter-spacing: 0;
padding: 0 1ch;
}
.doc .source-toolbox .copy-button {
display: flex;
flex-direction: column;
align-items: center;
background: none;
border: none;
color: inherit;
outline: none;
padding: 0;
font-size: inherit;
line-height: inherit;
width: 1em;
height: 1em;
}
.doc .source-toolbox .copy-icon {
flex: none;
width: inherit;
height: inherit;
}
.doc .source-toolbox img.copy-icon {
filter: invert(50.2%);
}
.doc .source-toolbox svg.copy-icon {
fill: currentColor;
}
.doc .source-toolbox .copy-toast {
flex: none;
position: relative;
display: inline-flex;
justify-content: center;
margin-top: 1em;
background-color: var(--doc-font-color);
border-radius: 0.25em;
padding: 0.5em;
color: var(--color-white);
cursor: auto;
opacity: 0;
transition: opacity 0.5s ease 0.5s;
}
.doc .source-toolbox .copy-toast::after {
content: "";
position: absolute;
top: 0;
width: 1em;
height: 1em;
border: 0.55em solid transparent;
border-left-color: var(--doc-font-color);
transform: rotate(-90deg) translateX(50%) translateY(50%);
transform-origin: left;
}
.doc .source-toolbox .copy-button.clicked .copy-toast {
opacity: 1;
transition: none;
}
.doc .language-console .hljs-meta {
user-select: none;
}
.doc .dlist dt {
font-style: italic;
}
.doc .dlist dd {
margin: 0 0 0 1.5rem;
}
.doc .dlist dd + dt,
.doc .dlist dd > p:first-child {
margin-top: 0.5rem;
}
.doc td.hdlist1,
.doc td.hdlist2 {
padding: 0.5rem 0 0;
vertical-align: top;
}
.doc tr:first-child > .hdlist1,
.doc tr:first-child > .hdlist2 {
padding-top: 0;
}
.doc td.hdlist1 {
font-weight: var(--body-font-weight-bold);
padding-right: 0.25rem;
}
.doc td.hdlist2 {
padding-left: 0.25rem;
}
.doc .colist {
font-size: calc(16 / var(--rem-base) * 1rem);
margin: 0.25rem 0 -0.25rem;
}
.doc .colist > table > tr > :first-child,
.doc .colist > table > tbody > tr > :first-child {
padding: 0.25em 0.5rem 0;
vertical-align: top;
}
.doc .colist > table > tr > :last-child,
.doc .colist > table > tbody > tr > :last-child {
padding: 0.25rem 0;
}
.doc .conum[data-value] {
border: 1px solid currentColor;
border-radius: 100%;
display: inline-block;
font-family: var(--body-font-family);
font-size: calc(13.5 / var(--rem-base) * 1rem);
font-style: normal;
line-height: 1.2;
text-align: center;
width: 1.25em;
height: 1.25em;
letter-spacing: -0.25ex;
text-indent: -0.25ex;
}
.doc .conum[data-value]::after {
content: attr(data-value);
}
.doc .conum[data-value] + b {
display: none;
}
.doc hr {
border: solid var(--section-divider-color);
border-width: 2px 0 0;
height: 0;
}
.doc b.button {
white-space: nowrap; /* effectively ignores hyphens setting */
}
.doc b.button::before {
content: "[";
padding-right: 0.25em;
}
.doc b.button::after {
content: "]";
padding-left: 0.25em;
}
.doc kbd {
display: inline-block;
font-size: calc(12 / var(--rem-base) * 1rem);
background: var(--kbd-background);
border: 1px solid var(--kbd-border-color);
border-radius: 0.25em;
box-shadow: 0 1px 0 var(--kbd-border-color), 0 0 0 0.1em var(--body-background) inset;
padding: 0.25em 0.5em;
vertical-align: text-bottom;
white-space: nowrap; /* effectively ignores hyphens setting */
}
.doc kbd,
.doc .keyseq {
line-height: 1;
}
.doc .keyseq {
font-size: calc(16 / var(--rem-base) * 1rem);
}
.doc .keyseq kbd {
margin: 0 0.125em;
}
.doc .keyseq kbd:first-child {
margin-left: 0;
}
.doc .keyseq kbd:last-child {
margin-right: 0;
}
.doc .menuseq,
.doc .path {
hyphens: none;
}
.doc .menuseq i.caret::before {
content: "\203a";
font-size: 1.1em;
font-weight: var(--body-font-weight-bold);
line-height: calc(1 / 1.1);
}
.doc :not(pre).nowrap {
white-space: nowrap;
}
.doc .nobreak {
hyphens: none;
word-wrap: normal;
}
.doc :not(pre).pre-wrap {
white-space: pre-wrap;
}
#footnotes {
font-size: 0.85em;
line-height: 1.5;
margin: 2rem -0.5rem 0;
}
.doc td.tableblock > .content #footnotes {
margin: 2rem 0 0;
}
#footnotes hr {
border-top-width: 1px;
margin-top: 0;
width: 20%;
}
#footnotes .footnote {
margin: 0.5em 0 0 1em;
}
#footnotes .footnote + .footnote {
margin-top: 0.25em;
}
#footnotes .footnote > a:first-of-type {
display: inline-block;
margin-left: -2em;
text-align: right;
width: 1.5em;
}
footer.footer {
background-color: var(--footer-background);
color: var(--footer-font-color);
font-size: calc(15 / var(--rem-base) * 1rem);
line-height: var(--footer-line-height);
padding: 1.5rem;
}
.footer p {
margin: 0.5rem 0;
}
.footer a {
color: var(--footer-link-font-color);
}
@media screen and (max-width: 1023.5px) {
html.is-clipped--navbar {
overflow-y: hidden;
}
}
body {
padding-top: var(--navbar-height);
}
.navbar {
background: var(--navbar-background);
color: var(--navbar-font-color);
font-size: calc(16 / var(--rem-base) * 1rem);
height: var(--navbar-height);
position: fixed;
top: 0;
width: 100%;
z-index: var(--z-index-navbar);
}
.navbar a {
text-decoration: none;
}
.navbar-brand {
display: flex;
flex: auto;
padding-left: 1rem;
}
.navbar-brand .navbar-item {
color: var(--navbar-font-color);
}
.navbar-brand .navbar-item:first-child {
align-self: center;
padding: 0;
font-size: calc(22 / var(--rem-base) * 1rem);
flex-wrap: wrap;
line-height: 1;
}
.navbar-brand .navbar-item:first-child a {
color: inherit;
word-wrap: normal;
}
.navbar-brand .navbar-item:first-child :not(:last-child) {
padding-right: 0.375rem;
}
.navbar-brand .navbar-item.search {
flex: auto;
justify-content: flex-end;
}
#search-input {
color: #333;
font-family: inherit;
font-size: 0.95rem;
width: 150px;
border: 1px solid #dbdbdb;
border-radius: 0.1em;
line-height: 1.5;
padding: 0 0.25em;
}
#search-input:disabled {
background-color: #dbdbdb;
/* disable cursor */
cursor: not-allowed;
pointer-events: all !important;
}
#search-input:disabled::placeholder {
color: #4c4c4c;
}
#search-input:focus {
outline: none;
}
.navbar-burger {
background: none;
border: none;
outline: none;
line-height: 1;
position: relative;
width: 3rem;
padding: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-left: auto;
min-width: 0;
}
.navbar-burger span {
background-color: var(--navbar-font-color);
height: 1.5px;
width: 1rem;
}
.navbar-burger:not(.is-active) span {
transition: transform ease-out 0.25s, opacity 0s 0.25s, margin-top ease-out 0.25s 0.25s;
}
.navbar-burger span + span {
margin-top: 0.25rem;
}
.navbar-burger.is-active span + span {
margin-top: -1.5px;
}
.navbar-burger.is-active span:nth-child(1) {
transform: rotate(45deg);
}
.navbar-burger.is-active span:nth-child(2) {
opacity: 0;
}
.navbar-burger.is-active span:nth-child(3) {
transform: rotate(-45deg);
}
.navbar-item,
.navbar-link {
color: var(--navbar-menu-font-color);
display: block;
line-height: var(--doc-line-height);
padding: 0.5rem 1rem;
}
.navbar-item.has-dropdown {
padding: 0;
}
.navbar-item .icon {
width: 1.25rem;
height: 1.25rem;
display: block;
}
.navbar-item .icon img,
.navbar-item .icon svg {
fill: currentColor;
width: inherit;
height: inherit;
}
.navbar-link {
padding-right: 2.5em;
}
.navbar-dropdown .navbar-item {
padding-left: 1.5rem;
padding-right: 1.5rem;
}
.navbar-dropdown .navbar-item.has-label {
display: flex;
justify-content: space-between;
}
.navbar-dropdown .navbar-item small {
color: var(--toolbar-muted-color);
font-size: calc(12 / var(--rem-base) * 1rem);
}
.navbar-divider {
background-color: var(--navbar-menu-border-color);
border: none;
height: 1px;
margin: 0.25rem 0;
}
.navbar .button {
display: inline-flex;
align-items: center;
background: var(--navbar-button-background);
border: 1px solid var(--navbar-button-border-color);
border-radius: 0.15rem;
height: 1.75rem;
color: var(--navbar-button-font-color);
padding: 0 0.75em;
white-space: nowrap;
}
@media screen and (max-width: 768.5px) {
.navbar-brand .navbar-item.search {
padding-left: 0;
padding-right: 0;
}
}
@media screen and (min-width: 769px) {
#search-input {
width: 200px;
}
}
@media screen and (max-width: 1023.5px) {
.navbar-brand {
height: inherit;
}
.navbar-brand .navbar-item {
align-items: center;
display: flex;
}
.navbar-menu {
background: var(--navbar-menu-background);
box-shadow: 0 8px 16px rgba(10, 10, 10, 0.1);
max-height: var(--body-min-height);
overflow-y: auto;
overscroll-behavior: none;
padding: 0.5rem 0;
}
.navbar-menu:not(.is-active) {
display: none;
}
.navbar-menu a.navbar-item:hover,
.navbar-menu .navbar-link:hover {
background: var(--navbar-menu_hover-background);
}
}
@media screen and (min-width: 1024px) {
.navbar-burger {
display: none;
}
.navbar,
.navbar-menu,
.navbar-end {
display: flex;
}
.navbar-item,
.navbar-link {
display: flex;
position: relative;
flex: none;
}
.navbar-item:not(.has-dropdown),
.navbar-link {
align-items: center;
}
.navbar-item.is-hoverable:hover .navbar-dropdown {
display: block;
}
.navbar-link::after {
border-width: 0 0 1px 1px;
border-style: solid;
content: "";
display: block;
height: 0.5em;
pointer-events: none;
position: absolute;
transform: rotate(-45deg);
width: 0.5em;
margin-top: -0.375em;
right: 1.125em;
top: 50%;
}
.navbar-end > .navbar-item,
.navbar-end .navbar-link {
color: var(--navbar-font-color);
}
.navbar-end > a.navbar-item:hover,
.navbar-end .navbar-link:hover,
.navbar-end .navbar-item.has-dropdown:hover .navbar-link {
background: var(--navbar_hover-background);
color: var(--navbar-font-color);
}
.navbar-end .navbar-link::after {
border-color: currentColor;
}
.navbar-dropdown {
background: var(--navbar-menu-background);
border: 1px solid var(--navbar-menu-border-color);
border-top: none;
border-radius: 0 0 0.25rem 0.25rem;
display: none;
top: 100%;
left: 0;
min-width: 100%;
position: absolute;
}
.navbar-dropdown .navbar-item {
padding: 0.5rem 3rem 0.5rem 1rem;
white-space: nowrap;
}
.navbar-dropdown .navbar-item small {
position: relative;
right: -2rem;
}
.navbar-dropdown .navbar-item:last-child {
border-radius: inherit;
}
.navbar-dropdown.is-right {
left: auto;
right: 0;
}
.navbar-dropdown a.navbar-item:hover {
background: var(--navbar-menu_hover-background);
}
}
/*! Adapted from the GitHub style by Vasily Polovnyov <vast@whiteants.net> */
.hljs-comment,
.hljs-quote {
color: #998;
font-style: italic;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-subst {
color: #333;
font-weight: var(--monospace-font-weight-bold);
}
.hljs-number,
.hljs-literal,
.hljs-variable,
.hljs-template-variable,
.hljs-tag .hljs-attr {
color: #008080;
}
.hljs-string,
.hljs-doctag {
color: #d14;
}
.hljs-title,
.hljs-section,
.hljs-selector-id {
color: #900;
font-weight: var(--monospace-font-weight-bold);
}
.hljs-subst {
font-weight: normal;
}
.hljs-type,
.hljs-class .hljs-title {
color: #458;
font-weight: var(--monospace-font-weight-bold);
}
.hljs-tag,
.hljs-name,
.hljs-attribute {
color: #000080;
font-weight: normal;
}
.hljs-regexp,
.hljs-link {
color: #009926;
}
.hljs-symbol,
.hljs-bullet {
color: #990073;
}
.hljs-built_in,
.hljs-builtin-name {
color: #0086b3;
}
.hljs-meta {
color: #999;
font-weight: var(--monospace-font-weight-bold);
}
.hljs-deletion {
background: #fdd;
}
.hljs-addition {
background: #dfd;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: var(--monospace-font-weight-bold);
}
body.-toc aside.toc.sidebar {
display: none;
}
@media screen and (max-width: 1023.5px) {
aside.toc.sidebar {
display: none;
}
main > .content {
overflow-x: auto;
}
}
@media screen and (min-width: 1024px) {
main {
flex: auto;
min-width: 0; /* min-width: 0 required for flexbox to constrain overflowing elements */
}
main > .content {
display: flex;
}
aside.toc.embedded {
display: none;
}
aside.toc.sidebar {
flex: 0 0 var(--toc-width);
order: 1;
}
}
@media screen and (min-width: 1216px) {
aside.toc.sidebar {
flex-basis: var(--toc-width--widescreen);
}
}
@media screen and (max-width: 1023.5px) {
html.is-clipped--nav {
overflow-y: hidden;
}
}
.nav-container {
position: fixed;
top: var(--navbar-height);
left: 0;
width: 100%;
font-size: calc(17 / var(--rem-base) * 1rem);
z-index: var(--z-index-nav);
visibility: hidden;
}
@media screen and (min-width: 769px) {
.nav-container {
width: var(--nav-width);
}
}
@media screen and (min-width: 1024px) {
.nav-container {
font-size: calc(15.5 / var(--rem-base) * 1rem);
flex: none;
position: static;
top: 0;
visibility: visible;
}
}
.nav-container.is-active {
visibility: visible;
}
.nav {
background: var(--nav-background);
position: relative;
top: var(--toolbar-height);
height: var(--nav-height);
}
@media screen and (min-width: 769px) {
.nav {
box-shadow: 0.5px 0 3px var(--nav-border-color);
}
}
@media screen and (min-width: 1024px) {
.nav {
top: var(--navbar-height);
box-shadow: none;
position: sticky;
height: var(--nav-height--desktop);
}
}
.nav a {
color: inherit;
}
.nav .panels {
display: flex;
flex-direction: column;
height: inherit;
}
.nav-panel-menu {
overflow-y: scroll;
overscroll-behavior: none;
height: var(--nav-panel-menu-height);
}
.nav-panel-menu:not(.is-active) .nav-menu {
opacity: 0.75;
}
.nav-panel-menu:not(.is-active)::after {
content: "";
background: rgba(0, 0, 0, 0.5);
display: block;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.nav-menu {
min-height: 100%;
padding: 0.5rem 0.75rem;
line-height: var(--nav-line-height);
position: relative;
}
.nav-menu-toggle {
background: transparent url(../img/octicons-16.svg#view-unfold) no-repeat center / 100% 100%;
border: none;
float: right;
height: 1em;
margin-right: -0.5rem;
opacity: 0.75;
outline: none;
padding: 0;
position: sticky;
top: calc((var(--nav-line-height) - 1 + 0.5) * 1rem);
visibility: hidden;
width: 1em;
}
.nav-menu-toggle.is-active {
background-image: url(../img/octicons-16.svg#view-fold);
}
.nav-panel-menu.is-active:hover .nav-menu-toggle {
visibility: visible;
}
.nav-menu h3.title {
color: var(--nav-heading-font-color);
font-size: inherit;
font-weight: var(--body-font-weight-bold);
margin: 0;
padding: 0.25em 0 0.125em;
}
.nav-list {
list-style: none;
margin: 0 0 0 0.75rem;
padding: 0;
}
.nav-menu > .nav-list + .nav-list {
margin-top: 0.5rem;
}
.nav-item {
margin-top: 0.5em;
}
/* adds some breathing room below a nested list */
.nav-item-toggle ~ .nav-list {
padding-bottom: 0.125rem;
}
/* matches list without a title */
.nav-item[data-depth="0"] > .nav-list:first-child {
display: block;
margin: 0;
}
.nav-item:not(.is-active) > .nav-list {
display: none;
}
.nav-item-toggle {
background: transparent url(../img/caret.svg) no-repeat center / 50%;
border: none;
outline: none;
line-height: inherit;
padding: 0;
position: absolute;
height: calc(var(--nav-line-height) * 1em);
width: calc(var(--nav-line-height) * 1em);
margin-top: -0.05em;
margin-left: calc(var(--nav-line-height) * -1em);
}
.nav-item.is-active > .nav-item-toggle {
transform: rotate(90deg);
}
.is-current-page > .nav-link,
.is-current-page > .nav-text {
font-weight: var(--body-font-weight-bold);
}
.nav-panel-explore {
background: var(--nav-background);
display: flex;
flex-direction: column;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.nav-panel-explore:not(:first-child) {
top: auto;
max-height: var(--nav-panel-explore-height);
}
.nav-panel-explore .context {
font-size: calc(15 / var(--rem-base) * 1rem);
flex-shrink: 0;
color: var(--nav-muted-color);
box-shadow: 0 -1px 0 var(--nav-panel-divider-color);
padding: 0 0.5rem;
display: flex;
align-items: center;
justify-content: space-between;
line-height: 1;
height: var(--drawer-height);
}
.nav-panel-explore:not(:first-child) .context {
cursor: pointer;
}
.nav-panel-explore .context .version {
display: flex;
align-items: inherit;
}
.nav-panel-explore .context .version::after {
content: "";
background: url(../img/chevron.svg) no-repeat center right / auto 100%;
width: 1.25em;
height: 0.75em;
}
.nav-panel-explore .components {
line-height: var(--nav-line-height);
flex-grow: 1;
box-shadow: inset 0 1px 5px var(--nav-panel-divider-color);
background: var(--nav-secondary-background);
padding: 0.75rem 0.75rem 0 0.75rem;
margin: 0;
overflow-y: scroll;
overscroll-behavior: none;
max-height: 100%;
display: block;
}
.nav-panel-explore:not(.is-active) .components {
display: none;
}
.nav-panel-explore .component {
display: block;
}
.nav-panel-explore .component + .component {
margin-top: 0.75rem;
}
.nav-panel-explore .component:last-child {
margin-bottom: 0.75rem;
}
.nav-panel-explore .component .title {
font-weight: var(--body-font-weight-bold);
text-indent: 0.375rem hanging;
}
.nav-panel-explore .versions {
display: flex;
flex-wrap: wrap;
padding-left: 0;
margin: -0.125rem -0.375rem 0 0.375rem;
line-height: 1;
list-style: none;
}
.nav-panel-explore .component .version {
margin: 0.375rem 0.375rem 0 0;
}
.nav-panel-explore .component .version a {
background: var(--nav-border-color);
border-radius: 0.25rem;
white-space: nowrap;
padding: 0.25em 0.5em;
display: inherit;
opacity: 0.75;
}
.nav-panel-explore .component .is-current a {
background: var(--nav-heading-font-color);
color: var(--nav-secondary-background);
font-weight: var(--body-font-weight-bold);
opacity: 1;
}
.page-versions {
margin: 0 0.2rem 0 auto;
position: relative;
line-height: 1;
}
@media screen and (min-width: 1024px) {
.page-versions {
margin-right: 0.7rem;
}
}
.page-versions .version-menu-toggle {
color: inherit;
background: url(../img/chevron.svg) no-repeat;
background-position: right 0.5rem top 50%;
background-size: auto 0.75em;
border: none;
outline: none;
line-height: inherit;
padding: 0.5rem 1.5rem 0.5rem 0.5rem;
position: relative;
z-index: var(--z-index-page-version-menu);
}
.page-versions .version-menu {
display: flex;
min-width: 100%;
flex-direction: column;
align-items: flex-end;
background: linear-gradient(to bottom, var(--page-version-menu-background) 0%, var(--page-version-menu-background) 100%) no-repeat;
padding: 1.375rem 1.5rem 0.5rem 0.5rem;
position: absolute;
top: 0;
right: 0;
white-space: nowrap;
}
.page-versions:not(.is-active) .version-menu {
display: none;
}
.page-versions .version {
display: block;
padding-top: 0.5rem;
}
.page-versions .version.is-current {
display: none;
}
.page-versions .version.is-missing {
color: var(--page-version-missing-font-color);
font-style: italic;
text-decoration: none;
}
nav.pagination {
display: flex;
border-top: 1px solid var(--toolbar-border-color);
line-height: 1;
margin: 2rem -1rem -1rem;
padding: 0.75rem 1rem 0;
}
nav.pagination span {
display: flex;
flex: 50%;
flex-direction: column;
}
nav.pagination .prev {
padding-right: 0.5rem;
}
nav.pagination .next {
margin-left: auto;
padding-left: 0.5rem;
text-align: right;
}
nav.pagination span::before {
color: var(--toolbar-muted-color);
font-size: 0.75em;
padding-bottom: 0.1em;
}
nav.pagination .prev::before {
content: "Prev";
}
nav.pagination .next::before {
content: "Next";
}
nav.pagination a {
font-weight: var(--body-font-weight-bold);
line-height: 1.3;
position: relative;
}
nav.pagination a::before,
nav.pagination a::after {
color: var(--toolbar-muted-color);
font-weight: normal;
font-size: 1.5em;
line-height: 0.75;
position: absolute;
top: 0;
width: 1rem;
}
nav.pagination .prev a::before {
content: "\2039";
transform: translateX(-100%);
}
nav.pagination .next a::after {
content: "\203a";
}
@page {
margin: 0.5in;
}
@media print {
.hide-for-print {
display: none !important;
}
html {
font-size: var(--body-font-size--print);
}
a {
color: inherit !important;
text-decoration: underline;
}
a.bare,
a[href^="#"],
a[href^="mailto:"] {
text-decoration: none;
}
tr,
img,
object,
svg {
page-break-inside: avoid;
}
thead {
display: table-header-group;
}
pre {
hyphens: none;
white-space: pre-wrap;
}
body {
padding-top: 2rem;
}
.navbar {
background: none;
color: inherit;
position: absolute;
}
.navbar * {
color: inherit !important;
}
.navbar > :not(.navbar-brand),
.nav-container,
.toolbar,
aside.toc,
nav.pagination {
display: none;
}
.doc {
color: inherit;
margin: auto;
max-width: none;
padding-bottom: 2rem;
}
.doc .admonitionblock td.icon {
color-adjust: exact;
}
.doc .listingblock code[data-lang]::before {
display: block;
}
footer.footer {
background: none;
border-top: 1px solid var(--panel-border-color);
color: var(--quote-attribution-font-color);
padding: 0.25rem 0.5rem 0;
}
.footer * {
color: inherit;
}
}
@import "typeface-roboto.css";
@import "typeface-roboto-mono.css";
@import "vars.css";
@import "base.css";
@import "body.css";
@import "nav.css";
@import "main.css";
@import "toolbar.css";
@import "breadcrumbs.css";
@import "page-versions.css";
@import "toc.css";
@import "doc.css";
@import "pagination.css";
@import "header.css";
@import "footer.css";
@import "highlight.css";
@import "print.css";
.toc-menu {
color: var(--toc-font-color);
}
.toc.sidebar .toc-menu {
margin-right: 0.75rem;
position: sticky;
top: var(--toc-top);
}
.toc .toc-menu h3 {
color: var(--toc-heading-font-color);
font-size: calc(16 / var(--rem-base) * 1rem);
font-weight: var(--body-font-weight-bold);
line-height: 1.3;
margin: 0 -0.5px;
padding-bottom: 0.25rem;
}
.toc.sidebar .toc-menu h3 {
display: flex;
flex-direction: column;
height: 2.5rem;
justify-content: flex-end;
}
.toc .toc-menu ul {
font-size: calc(15 / var(--rem-base) * 1rem);
line-height: var(--toc-line-height);
list-style: none;
margin: 0;
padding: 0;
}
.toc.sidebar .toc-menu ul {
max-height: var(--toc-height);
overflow-y: auto;
overscroll-behavior: none;
}
@supports (scrollbar-width: none) {
.toc.sidebar .toc-menu ul {
scrollbar-width: none;
}
}
.toc .toc-menu ul::-webkit-scrollbar {
width: 0;
height: 0;
}
@media screen and (min-width: 1024px) {
.toc .toc-menu h3 {
font-size: calc(15 / var(--rem-base) * 1rem);
}
.toc .toc-menu ul {
font-size: calc(13.5 / var(--rem-base) * 1rem);
}
}
.toc .toc-menu li {
margin: 0;
}
.toc .toc-menu li[data-level="2"] a {
padding-left: 1.25rem;
}
.toc .toc-menu li[data-level="3"] a {
padding-left: 2rem;
}
.toc .toc-menu a {
color: inherit;
border-left: 2px solid var(--toc-border-color);
display: inline-block;
padding: 0.25rem 0 0.25rem 0.5rem;
text-decoration: none;
}
.sidebar.toc .toc-menu a {
display: block;
outline: none;
}
.toc .toc-menu a:hover {
color: var(--link-font-color);
}
.toc .toc-menu a.is-active {
border-left-color: var(--link-font-color);
color: var(--doc-font-color);
}
.sidebar.toc .toc-menu a:focus {
background: var(--panel-background);
}
.toolbar {
color: var(--toolbar-font-color);
align-items: center;
background-color: var(--toolbar-background);
box-shadow: 0 1px 0 var(--toolbar-border-color);
display: flex;
font-size: calc(15 / var(--rem-base) * 1rem);
height: var(--toolbar-height);
justify-content: flex-start;
position: sticky;
top: var(--navbar-height);
z-index: var(--z-index-toolbar);
}
.toolbar a {
color: inherit;
}
.nav-toggle {
background: url(../img/menu.svg) no-repeat 50% 47.5%;
background-size: 49%;
border: none;
outline: none;
line-height: inherit;
padding: 0;
height: var(--toolbar-height);
width: var(--toolbar-height);
margin-right: -0.25rem;
}
@media screen and (min-width: 1024px) {
.nav-toggle {
display: none;
}
}
.nav-toggle.is-active {
background-image: url(../img/back.svg);
background-size: 41.5%;
}
.home-link {
display: block;
background: url(../img/home-o.svg) no-repeat center;
height: calc(var(--toolbar-height) / 2);
width: calc(var(--toolbar-height) / 2);
margin: calc(var(--toolbar-height) / 4);
}
.home-link:hover,
.home-link.is-current {
background-image: url(../img/home.svg);
}
.edit-this-page {
display: none;
padding-right: 0.5rem;
}
@media screen and (min-width: 1024px) {
.edit-this-page {
display: block;
}
}
.toolbar .edit-this-page a {
color: var(--toolbar-muted-color);
}
@font-face {
font-family: "Roboto Mono";
font-style: normal;
font-weight: 400;
src:
url(~@fontsource/roboto-mono/files/roboto-mono-latin-400-normal.woff2) format("woff2"),
url(~@fontsource/roboto-mono/files/roboto-mono-latin-400-normal.woff) format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/*
@font-face {
font-family: "Roboto Mono";
font-style: normal;
font-weight: 400;
src: url(~@fontsource/roboto-mono/files/roboto-mono-cyrillic-400-normal.woff2) format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
*/
@font-face {
font-family: "Roboto Mono";
font-style: normal;
font-weight: 600;
src:
url(~@fontsource/roboto-mono/files/roboto-mono-latin-500-normal.woff2) format("woff2"),
url(~@fontsource/roboto-mono/files/roboto-mono-latin-500-normal.woff) format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
/*
@font-face {
font-family: "Roboto Mono";
font-style: normal;
font-weight: 600;
src: url(~@fontsource/roboto-mono/files/roboto-mono-cyrillic-500-normal.woff2) format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
*/
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src:
url(~@fontsource/roboto/files/roboto-latin-400-normal.woff2) format("woff2"),
url(~@fontsource/roboto/files/roboto-latin-400-normal.woff) format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 400;
src: url(~@fontsource/roboto/files/roboto-cyrillic-400-normal.woff2) format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: "Roboto";
font-style: italic;
font-weight: 400;
src:
url(~@fontsource/roboto/files/roboto-latin-400-italic.woff2) format("woff2"),
url(~@fontsource/roboto/files/roboto-latin-400-italic.woff) format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Roboto";
font-style: italic;
font-weight: 400;
src: url(~@fontsource/roboto/files/roboto-cyrillic-400-italic.woff2) format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 600;
src:
url(~@fontsource/roboto/files/roboto-latin-500-normal.woff2) format("woff2"),
url(~@fontsource/roboto/files/roboto-latin-500-normal.woff) format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Roboto";
font-style: normal;
font-weight: 600;
src: url(~@fontsource/roboto/files/roboto-cyrillic-500-normal.woff2) format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
font-family: "Roboto";
font-style: italic;
font-weight: 600;
src:
url(~@fontsource/roboto/files/roboto-latin-500-italic.woff2) format("woff2"),
url(~@fontsource/roboto/files/roboto-latin-500-italic.woff) format("woff");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
font-family: "Roboto";
font-style: italic;
font-weight: 600;
src: url(~@fontsource/roboto/files/roboto-cyrillic-500-italic.woff2) format("woff2");
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
:root {
/* colors */
--color-white: #fff;
--color-smoke-10: #fefefe;
--color-smoke-30: #fafafa;
--color-smoke-50: #f5f5f5;
--color-smoke-70: #f0f0f0;
--color-smoke-90: #e1e1e1;
--color-gray-10: #c1c1c1;
--color-gray-30: #9c9c9c;
--color-gray-40: #8e8e8e;
--color-gray-50: #808080;
--color-gray-70: #5d5d5d;
--color-jet-20: #4a4a4a;
--color-jet-30: #424242;
--color-jet-50: #333;
--color-jet-70: #222;
--color-jet-80: #191919;
--color-black: #000;
/* fonts */
--rem-base: 18; /* used to compute rem value from desired pixel value (e.g., calc(18 / var(--rem-base) * 1rem) = 18px) */
--body-font-size: 1.0625em; /* 17px */
--body-font-size--desktop: 1.125em; /* 18px */
--body-font-size--print: 0.9375em; /* 15px */
--body-line-height: 1.15;
--body-font-color: var(--color-jet-70);
--body-font-family: "Roboto", sans-serif;
--body-font-weight-bold: 600;
--monospace-font-family: "Roboto Mono", monospace;
--monospace-font-weight-bold: 600;
/* base */
--body-background: var(--color-white);
--panel-background: var(--color-smoke-30);
--panel-border-color: var(--color-smoke-90);
--scrollbar-track-color: var(--color-smoke-30);
--scrollbar-thumb-color: var(--color-gray-10);
--scrollbar_hover-thumb-color: var(--color-gray-30);
/* navbar */
--navbar-background: var(--color-jet-80);
--navbar-font-color: var(--color-white);
--navbar_hover-background: var(--color-black);
--navbar-button-background: var(--color-white);
--navbar-button-border-color: var(--panel-border-color);
--navbar-button-font-color: var(--body-font-color);
--navbar-menu-border-color: var(--panel-border-color);
--navbar-menu-background: var(--color-white);
--navbar-menu-font-color: var(--body-font-color);
--navbar-menu_hover-background: var(--color-smoke-50);
/* nav */
--nav-background: var(--panel-background);
--nav-border-color: var(--color-gray-10);
--nav-line-height: 1.35;
--nav-heading-font-color: var(--color-jet-30);
--nav-muted-color: var(--color-gray-70);
--nav-panel-divider-color: var(--color-smoke-90);
--nav-secondary-background: var(--color-smoke-70);
/* toolbar */
--toolbar-background: var(--panel-background);
--toolbar-border-color: var(--panel-border-color);
--toolbar-font-color: var(--color-gray-70);
--toolbar-muted-color: var(--color-gray-40);
--page-version-menu-background: var(--color-smoke-70);
--page-version-missing-font-color: var(--color-gray-40);
/* admonitions */
--caution-color: #a0439c;
--caution-on-color: var(--color-white);
--important-color: #d32f2f;
--important-on-color: var(--color-white);
--note-color: #217ee7;
--note-on-color: var(--color-white);
--tip-color: #41af46;
--tip-on-color: var(--color-white);
--warning-color: #e18114;
--warning-on-color: var(--color-white);
/* doc */
--doc-font-color: var(--color-jet-50);
--doc-font-size: inherit;
--doc-font-size--desktop: calc(17 / var(--rem-base) * 1rem);
--doc-line-height: 1.6;
--doc-margin: 0 auto;
--doc-margin--desktop: 0 2rem;
--heading-font-color: var(--color-jet-80);
--heading-font-weight: normal;
--alt-heading-font-weight: var(--body-font-weight-bold);
--section-divider-color: var(--panel-border-color);
--link-font-color: #1565c0;
--link_hover-font-color: #104d92;
--link_unresolved-font-color: var(--important-color);
--abstract-background: var(--color-smoke-70);
--abstract-font-color: var(--color-jet-20);
--abstract-border-color: var(--panel-border-color);
--admonition-background: var(--panel-background);
--admonition-label-font-weight: var(--body-font-weight-bold);
--caption-font-color: var(--color-gray-70);
--caption-font-style: italic;
--caption-font-weight: var(--body-font-weight-bold);
--code-background: var(--panel-background);
--code-font-color: var(--body-font-color);
--example-background: var(--color-white);
--example-border-color: var(--color-gray-70);
--kbd-background: var(--panel-background);
--kbd-border-color: var(--color-gray-10);
--pre-background: var(--panel-background);
--pre-border-color: var(--panel-border-color);
--pre-annotation-font-color: var(--color-gray-50);
--quote-background: var(--panel-background);
--quote-border-color: var(--color-gray-70);
--quote-font-color: var(--color-gray-70);
--quote-attribution-font-color: var(--color-gray-40);
--sidebar-background: var(--color-smoke-90);
--table-border-color: var(--panel-border-color);
--table-stripe-background: var(--panel-background);
--table-footer-background: linear-gradient(to bottom, var(--color-smoke-70) 0%, var(--color-white) 100%);
/* toc */
--toc-font-color: var(--nav-muted-color);
--toc-heading-font-color: var(--doc-font-color);
--toc-border-color: var(--panel-border-color);
--toc-line-height: 1.2;
/* footer */
--footer-line-height: var(--doc-line-height);
--footer-background: var(--color-smoke-90);
--footer-font-color: var(--color-gray-70);
--footer-link-font-color: var(--color-jet-80);
/* dimensions and positioning */
--navbar-height: calc(63 / var(--rem-base) * 1rem);
--toolbar-height: calc(45 / var(--rem-base) * 1rem);
--drawer-height: var(--toolbar-height);
--body-top: var(--navbar-height);
--body-min-height: calc(100vh - var(--body-top));
--nav-height: calc(var(--body-min-height) - var(--toolbar-height));
--nav-height--desktop: var(--body-min-height);
--nav-panel-menu-height: calc(100% - var(--drawer-height));
--nav-panel-explore-height: calc(50% + var(--drawer-height));
--nav-width: calc(270 / var(--rem-base) * 1rem);
--toc-top: calc(var(--body-top) + var(--toolbar-height));
--toc-height: calc(100vh - var(--toc-top) - 2.5rem);
--toc-width: calc(162 / var(--rem-base) * 1rem);
--toc-width--widescreen: calc(216 / var(--rem-base) * 1rem);
--doc-max-width: calc(720 / var(--rem-base) * 1rem);
--doc-max-width--desktop: calc(828 / var(--rem-base) * 1rem);
/* stacking */
--z-index-nav: 1;
--z-index-toolbar: 2;
--z-index-page-version-menu: 3;
--z-index-navbar: 4;
}
'use strict'
module.exports = (...args) => {
const numArgs = args.length
if (numArgs === 3) return args[0] && args[1]
if (numArgs < 3) throw new Error('{{and}} helper expects at least 2 arguments')
args.pop()
return args.every((it) => it)
}
'use strict'
const TAG_ALL_RX = /<[^>]+>/g
module.exports = (html) => html && html.replace(TAG_ALL_RX, '')
'use strict'
module.exports = (a, b) => a === b
'use strict'
module.exports = (value) => (value || 0) + 1
'use strict'
module.exports = (a, b) => a !== b
'use strict'
module.exports = (val) => !val
'use strict'
module.exports = (...args) => {
const numArgs = args.length
if (numArgs === 3) return args[0] || args[1]
if (numArgs < 3) throw new Error('{{or}} helper expects at least 2 arguments')
args.pop()
return args.some((it) => it)
}
'use strict'
const { posix: path } = require('path')
module.exports = (to, from, ctx) => {
if (!to) return '#'
if (to.charAt() !== '/') return to
// NOTE only legacy invocation provides both to and from
if (!ctx) from = (ctx = from).data.root.page.url
if (!from) return (ctx.data.root.site.path || '') + to
let hash = ''
const hashIdx = to.indexOf('#')
if (~hashIdx) {
hash = to.slice(hashIdx)
to = to.slice(0, hashIdx)
}
if (to === from) return hash || (isDir(to) ? './' : path.basename(to))
const rel = path.relative(path.dirname(from + '.'), to)
return rel ? (isDir(to) ? rel + '/' : rel) + hash : (isDir(to) ? './' : '../' + path.basename(to)) + hash
}
function isDir (str) {
return str.charAt(str.length - 1) === '/'
}
'use strict'
module.exports = () => new Date().getFullYear().toString()
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="100"
height="100"
viewBox="0 0 100 100"
version="1.1"
inkscape:version="0.92.2 (5c3e80d, 2017-08-06)"
sodipodi:docname="back.svg"
enable-background="new">
<title>Left arrow</title>
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="6.108138"
inkscape:cx="21.142679"
inkscape:cy="42.629076"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="41"
inkscape:window-maximized="1"
scale-x="1" />
<metadata>
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Left arrow</dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Sarah White</dc:title>
</cc:Agent>
</dc:creator>
<dc:publisher>
<cc:Agent>
<dc:title>OpenDevise Inc.</dc:title>
</cc:Agent>
</dc:publisher>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-sa/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-sa/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#ShareAlike" />
</cc:License>
</rdf:RDF>
</metadata>
<g
transform="translate(-3.926492e-7,-270.54187)">
<path
d="m 50.000978,280.44162 -40.1010516,40.10025 40.1010516,40.10025 5.6556,-5.65551 -30.434757,-30.44194 h 64.878253 v -8.0056 H 25.221821 l 30.434757,-30.44001 z" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="30"
height="30"
viewBox="0 0 30 30"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="caret.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="16"
inkscape:cx="31.65919"
inkscape:cy="23.730414"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="2688"
inkscape:window-height="1478"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1022.3622)">
<path
style="opacity:1;fill:#c1c1c1;fill-opacity:1;stroke:#c1c1c1;stroke-width:1.99999976;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="m 10.18745,1025.362 14.0001,12.0002 -14.0001,12.0001 z"
id="rect3338"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc"
inkscape:transform-center-x="-2.1875" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="30"
height="30"
viewBox="0 0 30 30"
id="svg2"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="chevron.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="22.627417"
inkscape:cx="10.05311"
inkscape:cy="10.530062"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:window-width="2560"
inkscape:window-height="1406"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1" />
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Calque 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1022.3622)">
<path
style="opacity:1;fill:#5d5d5d;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
d="M 3.6699219,6.5898438 1.4550781,8.6152344 15,23.374272 28.544922,8.6152344 26.330078,6.5898438 15,18.759498 Z"
transform="translate(0,1022.3622)"
id="rect4136"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccc" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 100 100"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="home.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10">
<inkscape:path-effect
is_visible="true"
id="path-effect4225"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4221"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect4213"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4209"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4204"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4191"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4187"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect4183"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4179"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4173"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4169"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4165"
is_visible="true" />
</defs>
<sodipodi:namedview
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1406"
id="namedview8"
showgrid="false"
inkscape:zoom="8.1458701"
inkscape:cx="33.343764"
inkscape:cy="44.907032"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<g
transform="translate(0,-952.36218)"
id="g4">
<path
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="M 50.019531 13.576172 L 21.439453 39.115234 L 21.419922 86.460938 L 42.925781 86.460938 L 42.951172 61.294922 L 57.048828 61.294922 L 57.074219 86.460938 L 78.619141 86.460938 L 78.638672 39.150391 L 50.019531 13.576172 z "
id="path4175"
transform="translate(0,952.36218)" />
<path
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 63.815035,25.903568 0,-9.217018 8.656932,0 -2e-6,16.95383 z"
id="path4193"
transform="translate(0,952.36218)"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
inkscape:connector-curvature="0"
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 21.43888,991.47783 -9.98234,8.92037"
id="path4177" />
<path
inkscape:connector-curvature="0"
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 88.58189,1000.3982 -9.94315,-8.88535"
id="path4170" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 100 100"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="home-hovered.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10">
<inkscape:path-effect
is_visible="true"
id="path-effect4225"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4221"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect4213"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4209"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4204"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4191"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4187"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect4183"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4179"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4173"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4169"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4165"
is_visible="true" />
</defs>
<sodipodi:namedview
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1406"
id="namedview8"
showgrid="false"
inkscape:zoom="8.1458701"
inkscape:cx="-15.147065"
inkscape:cy="42.942846"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<g
transform="translate(0,-952.36218)"
id="g4">
<path
style="fill:#222;fill-rule:evenodd;stroke:#222;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="M 50.019531 13.576172 L 21.439453 39.115234 L 21.419922 86.460938 L 42.925781 86.460938 L 42.951172 61.294922 L 57.048828 61.294922 L 57.074219 86.460938 L 78.619141 86.460938 L 78.638672 39.150391 L 50.019531 13.576172 z "
id="path4175"
transform="translate(0,952.36218)" />
<path
style="fill:#222;fill-rule:evenodd;stroke:#222;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;fill-opacity:1"
d="m 63.815035,25.903568 0,-9.217018 8.656932,0 -2e-6,16.95383 z"
id="path4193"
transform="translate(0,952.36218)"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccc" />
<path
inkscape:connector-curvature="0"
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 21.43888,991.47783 -9.98234,8.92037"
id="path4177" />
<path
inkscape:connector-curvature="0"
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 88.58189,1000.3982 -9.94315,-8.88535"
id="path4170" />
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
x="0px"
y="0px"
viewBox="0 0 100 100"
id="svg2"
inkscape:version="0.91 r13725"
sodipodi:docname="menu.svg">
<metadata
id="metadata12">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs10">
<inkscape:path-effect
is_visible="true"
id="path-effect4225"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4221"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect4213"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4209"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4204"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4191"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4187"
is_visible="true" />
<inkscape:path-effect
effect="spiro"
id="path-effect4183"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4179"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4173"
is_visible="true" />
<inkscape:path-effect
is_visible="true"
id="path-effect4169"
effect="spiro" />
<inkscape:path-effect
effect="spiro"
id="path-effect4165"
is_visible="true" />
</defs>
<sodipodi:namedview
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="2560"
inkscape:window-height="1406"
id="namedview8"
showgrid="false"
inkscape:zoom="5.76"
inkscape:cx="14.532031"
inkscape:cy="43.425849"
inkscape:window-x="0"
inkscape:window-y="1440"
inkscape:window-maximized="1"
inkscape:current-layer="g4" />
<g
transform="translate(0,-952.36218)"
id="g4">
<g
id="g4238"
transform="translate(-1.5e-6,-0.2053541)">
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path4149"
d="m 35,972.34003 55.000003,0"
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647" />
<rect
y="964.84003"
x="10"
height="15"
width="15"
id="rect4184"
style="opacity:1;fill:#222;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647"
d="m 42.999999,1016.2452 44.999999,0"
id="path4180"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cc" />
<rect
style="opacity:1;fill:#222;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect4186"
width="10"
height="10"
x="23"
y="1011.2452" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path4182"
d="m 42.999999,1035.295 44.999999,0"
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647" />
<rect
y="1030.295"
x="23"
height="10"
width="10"
id="rect4188"
style="opacity:1;fill:#222;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<path
sodipodi:nodetypes="cc"
inkscape:connector-curvature="0"
id="path4210"
d="m 42.999999,997.1955 44.999999,0"
style="fill:none;fill-rule:evenodd;stroke:#222;stroke-width:5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.94117647" />
<rect
y="992.1955"
x="23"
height="10"
width="10"
id="rect4212"
style="opacity:1;fill:#222;fill-opacity:1;stroke:none;stroke-width:10;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<title>Octicons (16px subset)</title>
<desc>Octicons v11.2.0 by GitHub - https://primer.style/octicons/ - License: MIT</desc>
<metadata
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:RDF>
<cc:Work rdf:about="">
<dc:title>@primer/octicons</dc:title>
<dc:identifier>11.2.0</dc:identifier>
<dc:description>A scalable set of icons handcrafted with &lt;3 by GitHub</dc:description>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<cc:Agent>
<dc:title>GitHub</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title>Copyright (c) 2020 GitHub Inc.</dc:title>
</cc:Agent>
</dc:rights>
<cc:license rdf:resource="https://opensource.org/licenses/MIT" />
<dc:relation>https://primer.style/octicons/</dc:relation>
</cc:Work>
</rdf:RDF>
</metadata>
<symbol id="icon-clippy" viewBox="0 0 16 16">
<path
fill-rule="evenodd"
d="M5.75 1a.75.75 0 00-.75.75v3c0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75v-3a.75.75 0 00-.75-.75h-4.5zm.75 3V2.5h3V4h-3zm-2.874-.467a.75.75 0 00-.752-1.298A1.75 1.75 0 002 3.75v9.5c0 .966.784 1.75 1.75 1.75h8.5A1.75 1.75 0 0014 13.25v-9.5a1.75 1.75 0 00-.874-1.515.75.75 0 10-.752 1.298.25.25 0 01.126.217v9.5a.25.25 0 01-.25.25h-8.5a.25.25 0 01-.25-.25v-9.5a.25.25 0 01.126-.217z" />
</symbol>
<symbol id="icon-fold" viewBox="0 0 16 16">
<path d="M10.896 2H8.75V.75a.75.75 0 0 0-1.5 0V2H5.104a.25.25 0 0 0-.177.427l2.896 2.896a.25.25 0 0 0 .354 0l2.896-2.896A.25.25 0 0 0 10.896 2ZM8.75 15.25a.75.75 0 0 1-1.5 0V14H5.104a.25.25 0 0 1-.177-.427l2.896-2.896a.25.25 0 0 1 .354 0l2.896 2.896a.25.25 0 0 1-.177.427H8.75v1.25Zm-6.5-6.5a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM6 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 6 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM12 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 12 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5Z" />
</symbol>
<symbol id="icon-unfold" viewBox="0 0 16 16">
<path d="m8.177.677 2.896 2.896a.25.25 0 0 1-.177.427H8.75v1.25a.75.75 0 0 1-1.5 0V4H5.104a.25.25 0 0 1-.177-.427L7.823.677a.25.25 0 0 1 .354 0ZM7.25 10.75a.75.75 0 0 1 1.5 0V12h2.146a.25.25 0 0 1 .177.427l-2.896 2.896a.25.25 0 0 1-.354 0l-2.896-2.896A.25.25 0 0 1 5.104 12H7.25v-1.25Zm-5-2a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM6 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 6 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5ZM12 8a.75.75 0 0 1-.75.75h-.5a.75.75 0 0 1 0-1.5h.5A.75.75 0 0 1 12 8Zm2.25.75a.75.75 0 0 0 0-1.5h-.5a.75.75 0 0 0 0 1.5h.5Z" />
</symbol>
<use href="#icon-clippy" width="16" height="16" x="0" y="0" />
<view id="view-clippy" viewBox="0 0 16 16" />
<use href="#icon-fold" width="16" height="16" x="0" y="16" />
<view id="view-fold" viewBox="0 16 16 16" />
<use href="#icon-unfold" width="16" height="16" x="0" y="32" />
<view id="view-unfold" viewBox="0 32 16 16" />
</svg>
;(function () {
'use strict'
var SECT_CLASS_RX = /^sect(\d)$/
var navContainer = document.querySelector('.nav-container')
if (!navContainer) return
var navToggle = document.querySelector('.nav-toggle')
var nav = navContainer.querySelector('.nav')
var navMenuToggle = navContainer.querySelector('.nav-menu-toggle')
navToggle.addEventListener('click', showNav)
navContainer.addEventListener('click', trapEvent)
var menuPanel = navContainer.querySelector('[data-panel=menu]')
if (!menuPanel) return
var explorePanel = navContainer.querySelector('[data-panel=explore]')
var currentPageItem = menuPanel.querySelector('.is-current-page')
var originalPageItem = currentPageItem
if (currentPageItem) {
activateCurrentPath(currentPageItem)
scrollItemToMidpoint(menuPanel, currentPageItem.querySelector('.nav-link'))
} else {
menuPanel.scrollTop = 0
}
find(menuPanel, '.nav-item-toggle').forEach(function (btn) {
var li = btn.parentElement
btn.addEventListener('click', toggleActive.bind(li))
var navItemSpan = findNextElement(btn, '.nav-text')
if (navItemSpan) {
navItemSpan.style.cursor = 'pointer'
navItemSpan.addEventListener('click', toggleActive.bind(li))
}
})
if (navMenuToggle && menuPanel.querySelector('.nav-item-toggle')) {
navMenuToggle.style.display = ''
navMenuToggle.addEventListener('click', function () {
var collapse = !this.classList.toggle('is-active')
find(menuPanel, '.nav-item > .nav-item-toggle').forEach(function (btn) {
collapse ? btn.parentElement.classList.remove('is-active') : btn.parentElement.classList.add('is-active')
})
if (currentPageItem) {
if (collapse) activateCurrentPath(currentPageItem)
scrollItemToMidpoint(menuPanel, currentPageItem.querySelector('.nav-link'))
} else {
menuPanel.scrollTop = 0
}
})
}
if (explorePanel) {
explorePanel.querySelector('.context').addEventListener('click', function () {
// NOTE logic assumes there are only two panels
find(nav, '[data-panel]').forEach(function (panel) {
panel.classList.toggle('is-active')
})
})
}
// NOTE prevent text from being selected by double click
menuPanel.addEventListener('mousedown', function (e) {
if (e.detail > 1) e.preventDefault()
})
function onHashChange () {
var navLink
var hash = window.location.hash
if (hash) {
if (hash.indexOf('%')) hash = decodeURIComponent(hash)
navLink = menuPanel.querySelector('.nav-link[href="' + hash + '"]')
if (!navLink) {
var targetNode = document.getElementById(hash.slice(1))
if (targetNode) {
var current = targetNode
var ceiling = document.querySelector('article.doc')
while ((current = current.parentNode) && current !== ceiling) {
var id = current.id
// NOTE: look for section heading
if (!id && (id = SECT_CLASS_RX.test(current.className))) id = (current.firstElementChild || {}).id
if (id && (navLink = menuPanel.querySelector('.nav-link[href="#' + id + '"]'))) break
}
}
}
}
var navItem
if (navLink) {
navItem = navLink.parentNode
} else if (originalPageItem) {
navLink = (navItem = originalPageItem).querySelector('.nav-link')
} else {
return
}
if (navItem === currentPageItem) return
find(menuPanel, '.nav-item.is-active').forEach(function (el) {
el.classList.remove('is-active', 'is-current-path', 'is-current-page')
})
navItem.classList.add('is-current-page')
currentPageItem = navItem
activateCurrentPath(navItem)
scrollItemToMidpoint(menuPanel, navLink)
}
if (menuPanel.querySelector('.nav-link[href^="#"]')) {
if (window.location.hash) onHashChange()
window.addEventListener('hashchange', onHashChange)
}
function activateCurrentPath (navItem) {
var ancestorClasses
var ancestor = navItem.parentNode
while (!(ancestorClasses = ancestor.classList).contains('nav-menu')) {
if (ancestor.tagName === 'LI' && ancestorClasses.contains('nav-item')) {
ancestorClasses.add('is-active', 'is-current-path')
}
ancestor = ancestor.parentNode
}
navItem.classList.add('is-active')
}
function toggleActive () {
if (this.classList.toggle('is-active')) {
var padding = parseFloat(window.getComputedStyle(this).marginTop)
var rect = this.getBoundingClientRect()
var menuPanelRect = menuPanel.getBoundingClientRect()
var overflowY = (rect.bottom - menuPanelRect.top - menuPanelRect.height + padding).toFixed()
if (overflowY > 0) menuPanel.scrollTop += Math.min((rect.top - menuPanelRect.top - padding).toFixed(), overflowY)
}
}
function showNav (e) {
if (navToggle.classList.contains('is-active')) return hideNav(e)
trapEvent(e)
var html = document.documentElement
html.classList.add('is-clipped--nav')
navToggle.classList.add('is-active')
navContainer.classList.add('is-active')
var bounds = nav.getBoundingClientRect()
var expectedHeight = window.innerHeight - Math.round(bounds.top)
if (Math.round(bounds.height) !== expectedHeight) nav.style.height = expectedHeight + 'px'
html.addEventListener('click', hideNav)
}
function hideNav (e) {
trapEvent(e)
var html = document.documentElement
html.classList.remove('is-clipped--nav')
navToggle.classList.remove('is-active')
navContainer.classList.remove('is-active')
html.removeEventListener('click', hideNav)
}
function trapEvent (e) {
e.stopPropagation()
}
function scrollItemToMidpoint (panel, el) {
var rect = panel.getBoundingClientRect()
var effectiveHeight = rect.height
var navStyle = window.getComputedStyle(nav)
if (navStyle.position === 'sticky') effectiveHeight -= rect.top - parseFloat(navStyle.top)
panel.scrollTop = Math.max(0, (el.getBoundingClientRect().height - effectiveHeight) * 0.5 + el.offsetTop)
}
function find (from, selector) {
return [].slice.call(from.querySelectorAll(selector))
}
function findNextElement (from, selector) {
var el = from.nextElementSibling
return el && selector ? el[el.matches ? 'matches' : 'msMatchesSelector'](selector) && el : el
}
})()
;(function () {
'use strict'
var sidebar = document.querySelector('aside.toc.sidebar')
if (!sidebar) return
if (document.querySelector('body.-toc')) return sidebar.parentNode.removeChild(sidebar)
var levels = parseInt(sidebar.dataset.levels || 2, 10)
if (levels < 0) return
var articleSelector = 'article.doc'
var article = document.querySelector(articleSelector)
if (!article) return
var headingsSelector = []
for (var level = 0; level <= levels; level++) {
var headingSelector = [articleSelector]
if (level) {
for (var l = 1; l <= level; l++) headingSelector.push((l === 2 ? '.sectionbody>' : '') + '.sect' + l)
headingSelector.push('h' + (level + 1) + '[id]' + (level > 1 ? ':not(.discrete)' : ''))
} else {
headingSelector.push('h1[id].sect0')
}
headingsSelector.push(headingSelector.join('>'))
}
var headings = find(headingsSelector.join(','), article.parentNode)
if (!headings.length) return sidebar.parentNode.removeChild(sidebar)
var lastActiveFragment
var links = {}
var list = headings.reduce(function (accum, heading) {
var link = document.createElement('a')
link.textContent = heading.textContent
links[(link.href = '#' + heading.id)] = link
var listItem = document.createElement('li')
listItem.dataset.level = parseInt(heading.nodeName.slice(1), 10) - 1
listItem.appendChild(link)
accum.appendChild(listItem)
return accum
}, document.createElement('ul'))
var menu = sidebar.querySelector('.toc-menu')
if (!menu) (menu = document.createElement('div')).className = 'toc-menu'
var title = document.createElement('h3')
title.textContent = sidebar.dataset.title || 'Contents'
menu.appendChild(title)
menu.appendChild(list)
var startOfContent = !document.getElementById('toc') && article.querySelector('h1.page ~ :not(.is-before-toc)')
if (startOfContent) {
var embeddedToc = document.createElement('aside')
embeddedToc.className = 'toc embedded'
embeddedToc.appendChild(menu.cloneNode(true))
startOfContent.parentNode.insertBefore(embeddedToc, startOfContent)
}
window.addEventListener('load', function () {
onScroll()
window.addEventListener('scroll', onScroll)
})
function onScroll () {
var scrolledBy = window.pageYOffset
var buffer = getNumericStyleVal(document.documentElement, 'fontSize') * 1.15
var ceil = article.offsetTop
if (scrolledBy && window.innerHeight + scrolledBy + 2 >= document.documentElement.scrollHeight) {
lastActiveFragment = Array.isArray(lastActiveFragment) ? lastActiveFragment : Array(lastActiveFragment || 0)
var activeFragments = []
var lastIdx = headings.length - 1
headings.forEach(function (heading, idx) {
var fragment = '#' + heading.id
if (idx === lastIdx || heading.getBoundingClientRect().top + getNumericStyleVal(heading, 'paddingTop') > ceil) {
activeFragments.push(fragment)
if (lastActiveFragment.indexOf(fragment) < 0) links[fragment].classList.add('is-active')
} else if (~lastActiveFragment.indexOf(fragment)) {
links[lastActiveFragment.shift()].classList.remove('is-active')
}
})
list.scrollTop = list.scrollHeight - list.offsetHeight
lastActiveFragment = activeFragments.length > 1 ? activeFragments : activeFragments[0]
return
}
if (Array.isArray(lastActiveFragment)) {
lastActiveFragment.forEach(function (fragment) {
links[fragment].classList.remove('is-active')
})
lastActiveFragment = undefined
}
var activeFragment
headings.some(function (heading) {
if (heading.getBoundingClientRect().top + getNumericStyleVal(heading, 'paddingTop') - buffer > ceil) return true
activeFragment = '#' + heading.id
})
if (activeFragment) {
if (activeFragment === lastActiveFragment) return
if (lastActiveFragment) links[lastActiveFragment].classList.remove('is-active')
var activeLink = links[activeFragment]
activeLink.classList.add('is-active')
if (list.scrollHeight > list.offsetHeight) {
list.scrollTop = Math.max(0, activeLink.offsetTop + activeLink.offsetHeight - list.offsetHeight)
}
lastActiveFragment = activeFragment
} else if (lastActiveFragment) {
links[lastActiveFragment].classList.remove('is-active')
lastActiveFragment = undefined
}
}
function find (selector, from) {
return [].slice.call((from || document).querySelectorAll(selector))
}
function getNumericStyleVal (el, prop) {
return parseFloat(window.getComputedStyle(el)[prop])
}
})()
;(function () {
'use strict'
var article = document.querySelector('article.doc')
if (!article) return
var toolbar = document.querySelector('.toolbar')
var supportsScrollToOptions = 'scrollTo' in document.documentElement
function decodeFragment (hash) {
return hash && (~hash.indexOf('%') ? decodeURIComponent(hash) : hash).slice(1)
}
function computePosition (el, sum) {
return article.contains(el) ? computePosition(el.offsetParent, el.offsetTop + sum) : sum
}
function jumpToAnchor (e) {
if (e) {
if (e.altKey || e.ctrlKey) return
window.location.hash = '#' + this.id
e.preventDefault()
}
var y = computePosition(this, 0) - toolbar.getBoundingClientRect().bottom
var instant = e === false && supportsScrollToOptions
instant ? window.scrollTo({ left: 0, top: y, behavior: 'instant' }) : window.scrollTo(0, y)
}
window.addEventListener('load', function jumpOnLoad (e) {
var fragment, target
if ((fragment = decodeFragment(window.location.hash)) && (target = document.getElementById(fragment))) {
jumpToAnchor.call(target, false)
setTimeout(jumpToAnchor.bind(target, false), 250)
}
window.removeEventListener('load', jumpOnLoad)
})
Array.prototype.slice.call(document.querySelectorAll('a[href^="#"]')).forEach(function (el) {
var fragment, target
if ((fragment = decodeFragment(el.hash)) && (target = document.getElementById(fragment))) {
el.addEventListener('click', jumpToAnchor.bind(target))
}
})
})()
;(function () {
'use strict'
var toggle = document.querySelector('.page-versions .version-menu-toggle')
if (!toggle) return
var selector = document.querySelector('.page-versions')
toggle.addEventListener('click', function (e) {
selector.classList.toggle('is-active')
e.stopPropagation() // trap event
})
document.documentElement.addEventListener('click', function () {
selector.classList.remove('is-active')
})
})()
;(function () {
'use strict'
var navbarBurger = document.querySelector('.navbar-burger')
if (!navbarBurger) return
navbarBurger.addEventListener('click', toggleNavbarMenu.bind(navbarBurger))
function toggleNavbarMenu (e) {
e.stopPropagation() // trap event
document.documentElement.classList.toggle('is-clipped--navbar')
navbarBurger.setAttribute('aria-expanded', this.classList.toggle('is-active'))
var menu = document.getElementById(this.getAttribute('aria-controls') || this.dataset.target)
if (menu.classList.toggle('is-active')) {
menu.style.maxHeight = ''
var expectedMaxHeight = window.innerHeight - Math.round(menu.getBoundingClientRect().top)
var actualMaxHeight = parseInt(window.getComputedStyle(menu).maxHeight, 10)
if (actualMaxHeight !== expectedMaxHeight) menu.style.maxHeight = expectedMaxHeight + 'px'
}
}
})()
;(function () {
'use strict'
var CMD_RX = /^\$ (\S[^\\\n]*(\\\n(?!\$ )[^\\\n]*)*)(?=\n|$)/gm
var LINE_CONTINUATION_RX = /( ) *\\\n *|\\\n( ?) */g
var TRAILING_SPACE_RX = / +$/gm
var config = (document.getElementById('site-script') || { dataset: {} }).dataset
var supportsCopy = window.navigator.clipboard
var svgAs = config.svgAs
var uiRootPath = (config.uiRootPath == null ? window.uiRootPath : config.uiRootPath) || '.'
;[].slice.call(document.querySelectorAll('.doc pre.highlight, .doc .literalblock pre')).forEach(function (pre) {
var code, language, lang, copy, toast, toolbox
if (pre.classList.contains('highlight')) {
code = pre.querySelector('code')
if ((language = code.dataset.lang) && language !== 'console') {
;(lang = document.createElement('span')).className = 'source-lang'
lang.appendChild(document.createTextNode(language))
}
} else if (pre.innerText.startsWith('$ ')) {
var block = pre.parentNode.parentNode
block.classList.remove('literalblock')
block.classList.add('listingblock')
pre.classList.add('highlightjs', 'highlight')
;(code = document.createElement('code')).className = 'language-console hljs'
code.dataset.lang = 'console'
code.appendChild(pre.firstChild)
pre.appendChild(code)
} else {
return
}
;(toolbox = document.createElement('div')).className = 'source-toolbox'
if (lang) toolbox.appendChild(lang)
if (supportsCopy) {
;(copy = document.createElement('button')).className = 'copy-button'
copy.setAttribute('title', 'Copy to clipboard')
if (svgAs === 'svg') {
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg')
svg.setAttribute('class', 'copy-icon')
var use = document.createElementNS('http://www.w3.org/2000/svg', 'use')
use.setAttribute('href', uiRootPath + '/img/octicons-16.svg#icon-clippy')
svg.appendChild(use)
copy.appendChild(svg)
} else {
var img = document.createElement('img')
img.src = uiRootPath + '/img/octicons-16.svg#view-clippy'
img.alt = 'copy icon'
img.className = 'copy-icon'
copy.appendChild(img)
}
;(toast = document.createElement('span')).className = 'copy-toast'
toast.appendChild(document.createTextNode('Copied!'))
copy.appendChild(toast)
toolbox.appendChild(copy)
}
pre.parentNode.appendChild(toolbox)
if (copy) copy.addEventListener('click', writeToClipboard.bind(copy, code))
})
function extractCommands (text) {
var cmds = []
var m
while ((m = CMD_RX.exec(text))) cmds.push(m[1].replace(LINE_CONTINUATION_RX, '$1$2'))
return cmds.join(' && ')
}
function writeToClipboard (code) {
var text = code.innerText.replace(TRAILING_SPACE_RX, '')
if (code.dataset.lang === 'console' && text.startsWith('$ ')) text = extractCommands(text)
window.navigator.clipboard.writeText(text).then(
function () {
this.classList.add('clicked')
this.offsetHeight // eslint-disable-line no-unused-expressions
this.classList.remove('clicked')
}.bind(this),
function () {}
)
}
})()
;(function () {
'use strict'
var hljs = require('highlight.js/lib/highlight')
hljs.registerLanguage('asciidoc', require('highlight.js/lib/languages/asciidoc'))
hljs.registerLanguage('bash', require('highlight.js/lib/languages/bash'))
hljs.registerLanguage('clojure', require('highlight.js/lib/languages/clojure'))
hljs.registerLanguage('cpp', require('highlight.js/lib/languages/cpp'))
hljs.registerLanguage('cs', require('highlight.js/lib/languages/cs'))
hljs.registerLanguage('css', require('highlight.js/lib/languages/css'))
hljs.registerLanguage('diff', require('highlight.js/lib/languages/diff'))
hljs.registerLanguage('dockerfile', require('highlight.js/lib/languages/dockerfile'))
hljs.registerLanguage('elixir', require('highlight.js/lib/languages/elixir'))
hljs.registerLanguage('go', require('highlight.js/lib/languages/go'))
hljs.registerLanguage('groovy', require('highlight.js/lib/languages/groovy'))
hljs.registerLanguage('haskell', require('highlight.js/lib/languages/haskell'))
hljs.registerLanguage('java', require('highlight.js/lib/languages/java'))
hljs.registerLanguage('javascript', require('highlight.js/lib/languages/javascript'))
hljs.registerLanguage('json', require('highlight.js/lib/languages/json'))
hljs.registerLanguage('julia', require('highlight.js/lib/languages/julia'))
hljs.registerLanguage('kotlin', require('highlight.js/lib/languages/kotlin'))
hljs.registerLanguage('lua', require('highlight.js/lib/languages/lua'))
hljs.registerLanguage('markdown', require('highlight.js/lib/languages/markdown'))
hljs.registerLanguage('nix', require('highlight.js/lib/languages/nix'))
hljs.registerLanguage('none', require('highlight.js/lib/languages/plaintext'))
hljs.registerLanguage('objectivec', require('highlight.js/lib/languages/objectivec'))
hljs.registerLanguage('perl', require('highlight.js/lib/languages/perl'))
hljs.registerLanguage('php', require('highlight.js/lib/languages/php'))
hljs.registerLanguage('properties', require('highlight.js/lib/languages/properties'))
hljs.registerLanguage('puppet', require('highlight.js/lib/languages/puppet'))
hljs.registerLanguage('python', require('highlight.js/lib/languages/python'))
hljs.registerLanguage('ruby', require('highlight.js/lib/languages/ruby'))
hljs.registerLanguage('rust', require('highlight.js/lib/languages/rust'))
hljs.registerLanguage('scala', require('highlight.js/lib/languages/scala'))
hljs.registerLanguage('shell', require('highlight.js/lib/languages/shell'))
hljs.registerLanguage('sql', require('highlight.js/lib/languages/sql'))
hljs.registerLanguage('swift', require('highlight.js/lib/languages/swift'))
hljs.registerLanguage('xml', require('highlight.js/lib/languages/xml'))
hljs.registerLanguage('yaml', require('highlight.js/lib/languages/yaml'))
;[].slice.call(document.querySelectorAll('pre code.hljs[data-lang]')).forEach(function (node) {
hljs.highlightBlock(node)
})
})()
<!DOCTYPE html>
<html lang="en">
<head>
{{> head defaultPageTitle='Page Not Found'}}
</head>
<body class="status-404">
{{> header}}
{{> body}}
{{> footer}}
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
{{> head defaultPageTitle='Untitled'}}
</head>
<body class="article{{#with (or page.attributes.role page.role)}} {{{this}}}{{/with}}">
{{> header}}
{{> body}}
{{> footer}}
</body>
</html>
<article class="doc">
<h1 class="page">{{{or page.title 'Page Not Found'}}}</h1>
<div class="paragraph">
<p>The page you&#8217;re looking for does not exist. It may have been moved. You can{{#with site.homeUrl}} return to the <a href="{{{this}}}">start page</a>, or{{/with}} follow one of the links in the navigation to the left.</p>
</div>
<div class="paragraph">
<p>If you arrived on this page by clicking on a link, please notify the owner of the site that the link is broken.
If you typed the URL of this page manually, please double check that you entered the address correctly.</p>
</div>
</article>
<article class="doc">
{{#with page.title}}
<h1 class="page">{{{this}}}</h1>
{{/with}}
{{{page.contents}}}
{{> pagination}}
</article>
<div class="body">
{{> nav}}
{{> main}}
</div>
<nav class="breadcrumbs" aria-label="breadcrumbs">
{{#if page.breadcrumbs}}
<ul>
{{#with page.componentVersion}}
{{#if (and ./title (ne ./title @root.page.breadcrumbs.0.content))}}
<li><a href="{{{relativize ./url}}}">{{{./title}}}</a></li>
{{/if}}
{{/with}}
{{#each page.breadcrumbs}}
<li>
{{~#if (and ./url (eq ./urlType 'internal'))~}}
<a href="{{{relativize ./url}}}">{{{./content}}}</a>
{{~else~}}
{{{./content}}}
{{~/if~}}
</li>
{{/each}}
</ul>
{{/if}}
</nav>
{{#if (and page.fileUri (not env.CI))}}
<div class="edit-this-page"><a href="{{page.fileUri}}">Edit this Page</a></div>
{{else if (and page.editUrl (or env.FORCE_SHOW_EDIT_PAGE_LINK (not page.origin.private)))}}
<div class="edit-this-page"><a href="{{page.editUrl}}">Edit this Page</a></div>
{{/if}}
<footer class="footer">
<p>This page was built using the Antora default UI.</p>
<p>The source code for this UI is licensed under the terms of the MPL-2.0 license.</p>
</footer>
<script id="site-script" src="{{{uiRootPath}}}/js/site.js" data-ui-root-path="{{{uiRootPath}}}"></script>
<script async src="{{{uiRootPath}}}/js/vendor/highlight.js"></script>
{{#if env.SITE_SEARCH_PROVIDER}}
{{> search-scripts}}
{{/if}}
{{> footer-content}}
{{> footer-scripts}}
{{!-- <link rel="icon" href="{{{uiRootPath}}}/img/favicon.ico" type="image/x-icon"> --}}
{{#with page.canonicalUrl}}
<link rel="canonical" href="{{{this}}}">
{{/with}}
{{#unless (eq page.attributes.pagination undefined)}}
{{#with page.previous}}
<link rel="prev" href="{{{relativize ./url}}}">
{{/with}}
{{#with page.next}}
<link rel="next" href="{{{relativize ./url}}}">
{{/with}}
{{/unless}}
{{#with page.description}}
<meta name="description" content="{{{detag this}}}">
{{/with}}
{{#with page.keywords}}
<meta name="keywords" content="{{{this}}}">
{{/with}}
{{#with (or antoraVersion site.antoraVersion)}}
<meta name="generator" content="Antora {{{this}}}">
{{/with}}
{{!-- Add additional meta tags here --}}
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
{{#with site.keys.googleAnalytics}}
<script async src="https://www.googletagmanager.com/gtag/js?id={{this}}"></script>
<script>function gtag(){dataLayer.push(arguments)};window.dataLayer=window.dataLayer||[];gtag('js',new Date());gtag('config','{{this}}')</script>
{{/with}}
{{!--
<script>var uiRootPath = '{{{uiRootPath}}}'</script>
--}}
<link rel="stylesheet" href="{{{uiRootPath}}}/css/site.css">
<title>{{{detag (or page.title defaultPageTitle)}}}{{#with site.title}} :: {{this}}{{/with}}</title>
{{> head-prelude}}
{{> head-title}}
{{> head-info}}
{{> head-styles}}
{{> head-meta}}
{{> head-scripts}}
{{> head-icons}}
<header class="header">
<nav class="navbar">
<div class="navbar-brand">
<a class="navbar-item" href="{{{or site.url siteRootPath}}}">{{site.title}}</a>
{{#if env.SITE_SEARCH_PROVIDER}}
<div class="navbar-item search hide-for-print">
<div id="search-field" class="field">
<input id="search-input" type="text" placeholder="Search the docs"{{#if page.home}} autofocus{{/if}}>
</div>
</div>
{{/if}}
<button class="navbar-burger" aria-controls="topbar-nav" aria-expanded="false" aria-label="Toggle main menu">
<span></span>
<span></span>
<span></span>
</button>
</div>
<div id="topbar-nav" class="navbar-menu">
<div class="navbar-end">
<a class="navbar-item" href="#">Home</a>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link" href="#">Products</a>
<div class="navbar-dropdown">
<a class="navbar-item" href="#">Product A</a>
<a class="navbar-item" href="#">Product B</a>
<a class="navbar-item" href="#">Product C</a>
</div>
</div>
<div class="navbar-item has-dropdown is-hoverable">
<a class="navbar-link" href="#">Services</a>
<div class="navbar-dropdown">
<a class="navbar-item" href="#">Service A</a>
<a class="navbar-item" href="#">Service B</a>
<a class="navbar-item" href="#">Service C</a>
</div>
</div>
<div class="navbar-item">
<span class="control">
<a class="button is-primary" href="#">Download</a>
</span>
</div>
</div>
</div>
</nav>
</header>
{{!-- Add header scripts here --}}
{{> header-scripts}}
{{> header-content}}
<main class="article">
{{> toolbar}}
<div class="content">
{{#if (eq page.layout '404')}}
{{> article-404}}
{{else}}
{{> toc}}
{{> article}}
{{/if}}
</div>
</main>
<div class="nav-panel-explore{{#unless page.navigation}} is-active{{/unless}}" data-panel="explore">
{{#if page.component}}
<div class="context">
<span class="title">{{page.component.title}}</span>
<span class="version">{{#if (or page.componentVersion.version (ne page.componentVersion.displayVersion 'default'))}}{{page.componentVersion.displayVersion}}{{/if}}</span>
</div>
{{/if}}
<ul class="components">
{{#each site.components}}
<li class="component{{#if (eq this @root.page.component)}} is-current{{/if}}">
<div class="title"><a href="{{{relativize ./url}}}">{{{./title}}}</a></div>
{{#if (or ./versions.[1] ./versions.[0].version (ne ./versions.[0].displayVersion 'default'))}}
<ul class="versions">
{{#each ./versions}}
<li class="version
{{~#if (and (eq .. @root.page.component) (eq this @root.page.componentVersion))}} is-current{{/if~}}
{{~#if (eq this ../latest)}} is-latest{{/if}}">
<a href="{{{relativize ./url}}}">{{./displayVersion}}</a>
</li>
{{/each}}
</ul>
{{/if}}
</li>
{{/each}}
</ul>
</div>
{{#with page.navigation}}
<div class="nav-panel-menu is-active" data-panel="menu">
<nav class="nav-menu">
<button class="nav-menu-toggle" aria-label="Toggle expand/collapse all" style="display: none"></button>
{{#with @root.page.componentVersion}}
<h3 class="title"><a href="{{{relativize ./url}}}">{{./title}}</a></h3>
{{/with}}
{{> nav-tree navigation=this}}
</nav>
</div>
{{/with}}
<button class="nav-toggle"></button>
{{#if navigation.length}}
<ul class="nav-list">
{{#each navigation}}
<li class="nav-item{{#if (eq ./url @root.page.url)}} is-current-page{{/if}}" data-depth="{{or ../level 0}}">
{{#if ./content}}
{{#if ./items.length}}
<button class="nav-item-toggle"></button>
{{/if}}
{{#if ./url}}
<a class="nav-link" href="
{{~#if (eq ./urlType 'internal')}}{{{relativize ./url}}}
{{~else}}{{{./url}}}{{~/if}}">{{{./content}}}</a>
{{else}}
<span class="nav-text">{{{./content}}}</span>
{{/if}}
{{/if}}
{{> nav-tree navigation=./items level=(increment ../level)}}
</li>
{{/each}}
</ul>
{{/if}}
<div class="nav-container"{{#if page.component}} data-component="{{page.component.name}}" data-version="{{page.version}}"{{/if}}>
<aside class="nav">
<div class="panels">
{{> nav-menu}}
{{> nav-explore}}
</div>
</aside>
</div>
{{#with page.versions}}
<div class="page-versions">
<button class="version-menu-toggle" title="Show other versions of page">{{@root.page.componentVersion.displayVersion}}</button>
<div class="version-menu">
{{#each this}}
<a class="version
{{~#if (eq ./version @root.page.version)}} is-current{{/if~}}
{{~#if ./missing}} is-missing{{/if}}" href="{{{relativize ./url}}}">{{./displayVersion}}</a>
{{/each}}
</div>
</div>
{{/with}}
{{#unless (eq page.attributes.pagination undefined)}}
{{#if (or page.previous page.next)}}
<nav class="pagination">
{{#if (ne page.attributes.pagination 'next')}}
{{#with page.previous}}
<span class="prev"><a href="{{{relativize ./url}}}">{{{./content}}}</a></span>
{{/with}}
{{/if}}
{{#if (ne page.attributes.pagination 'prev')}}
{{#with page.next}}
<span class="next"><a href="{{{relativize ./url}}}">{{{./content}}}</a></span>
{{/with}}
{{/if}}
</nav>
{{/if}}
{{/unless}}
<aside class="toc sidebar" data-title="{{{or page.attributes.toctitle 'Contents'}}}" data-levels="{{{or page.attributes.toclevels 2}}}">
<div class="toc-menu"></div>
</aside>
<div class="toolbar" role="navigation">
{{> nav-toggle}}
{{#with site.homeUrl}}
<a href="{{{relativize this}}}" class="home-link{{#if @root.page.home}} is-current{{/if}}"></a>
{{/with}}
{{> breadcrumbs}}
{{> page-versions}}
{{> edit-this-page}}
</div>
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment