Skip to content

Commit

Permalink
Merge latest changes from Antora default UI
Browse files Browse the repository at this point in the history
  • Loading branch information
brunchboy committed Feb 19, 2023
2 parents 68596dd + ae0671b commit 9d42aef
Show file tree
Hide file tree
Showing 26 changed files with 532 additions and 12,666 deletions.
29 changes: 16 additions & 13 deletions docs/modules/ROOT/nav.adoc
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
* xref:prerequisites.adoc[UI Development Prerequisites]
* xref:set-up-project.adoc[Set up a UI Project]
* xref:build-preview-ui.adoc[Build and Preview the UI]
* xref:development-workflow.adoc[UI Development Workflow]
* xref:templates.adoc[Work with the Handlebars Templates]
* xref:stylesheets.adoc[Work with the CSS Stylesheets]
** xref:add-fonts.adoc[Add Fonts]
* xref:style-guide.adoc[UI Element Styles]
** xref:inline-text-styles.adoc[Inline Text]
** xref:admonition-styles.adoc[Admonitions]
** xref:list-styles.adoc[Lists]
** xref:sidebar-styles.adoc[Sidebars]
** xref:ui-macro-styles.adoc[UI Macros]
* xref:prerequisites.adoc[]
* xref:set-up-project.adoc[]
* xref:build-preview-ui.adoc[]
* xref:development-workflow.adoc[]
* xref:templates.adoc[]
** xref:create-helper.adoc[]
* xref:add-static-files.adoc[]
* xref:stylesheets.adoc[]
** xref:add-fonts.adoc[]
* xref:copy-to-clipboard.adoc[]
* xref:style-guide.adoc[]
** xref:inline-text-styles.adoc[]
** xref:admonition-styles.adoc[]
** xref:list-styles.adoc[]
** xref:sidebar-styles.adoc[]
** xref:ui-macro-styles.adoc[]
99 changes: 99 additions & 0 deletions docs/modules/ROOT/pages/add-static-files.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
= Add Static Files

A static UI file is any file provided by the UI that is added directly to your site.
A common example of a static file is a favicon image.
One way to add 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 using a UI bundle instead.

== Set up the static files folder

You'll first need a place to store the static files in the UI project.
Let's create a folder under [.path]_src_ named [.path]_static_ for this purpose.

$ mkdir src/static

You can add static files, such as a favicon image (e.g., [.path]_favicon.ico_), to this folder.
The UI build will add files in this folder to the root of the UI bundle, dropping the [.path]_static_ folder prefix from the path.

Antora automatically passes through static files in the bundle to the UI output folder (`+_+` by default), ignoring any hidden files (i.e., files that begin with a dot).
A static file is any file that's not designated as a layout, partial, or helper.
That means our favicon image file will end up at the path [.path]_++_/favicon.ico++_.

.Contents of site
....
_/
favicon.ico
css/
site.css
...
sitemap.xml
...
....

If that's where you want the file to go, there's nothing else you have to do.
Otherwise, you have the option of promoting select static files to the site root.

== Promote static files

If you want to promote certain static files out of the UI output folder, you need to identify them.
Antora looks for a file named [.path]_ui.yml_, the UI descriptor, in the UI bundle to configure the behavior of the UI.

Start by creating the file [.path]_src/ui.yml_ in your UI project.
The UI build copies this file, if present, to the root of the UI bundle.

This file does not have any required keys.
The key we're interested in is `static_files`.
This key identifies files by relative path in the UI bundle that should be promoted from the UI output folder to the site root.
The files must be specified as an array, where each entry is either a relative paths or a path glob.
Unlike other static files, promoted static files can begin with a dot.

Here's how to configure the UI descriptor to promote the favicon image file to the site root.

.src/ui.yml
[,yaml]
----
static_files:
- favicon.ico
----

If you have multiple favicon files with different file extensions, you can match all of them using a glob.

.src/ui.yml
[,yaml]
----
static_files:
- favicon*
----

With this configuration in place, Antora will read the favicon image from the UI bundle and copy it to the root of the site.

.Contents of site
....
_/
css/
site.css
...
favicon.ico
sitemap.xml
...
....

Let's now look at how to put the static files to use.

== 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 the favicon image at the root of the site.

.src/partials/head-icons.hbs
[,yaml]
----
<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.
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/admonition-styles.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
= 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.

Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/build-preview-ui.adoc
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
= Build a UI Project for Local Preview
:navtitle: Build and Preview the UI

== Build Preview Site

Expand Down
48 changes: 48 additions & 0 deletions docs/modules/ROOT/pages/copy-to-clipboard.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
= 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 provides JavaScript that adds a clipboard button to all source blocks.
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.

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
----
177 changes: 177 additions & 0 deletions docs/modules/ROOT/pages/create-helper.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
= 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.
Loading

0 comments on commit 9d42aef

Please sign in to comment.