An Asciidoctor extension that adds a tabs block to the AsciiDoc syntax.
📎
|
This extension is intended to be used with HTML backends (e.g., html5 ).
For all other backends (i.e., the filetype is not html), the custom block enclosure is discarded and its contents (a dlist) is converted normally.
|
💡
|
This extension is also published as an npm package named @asciidoctor/tabs for use with Asciidoctor.js, and hence, with Antora.
See the README for the npm package and its Antora integration guide for details.
|
Each set of tabs (i.e., a “tabset” or tabs block) is constructed from a description list (dlist) enclosed in an example block annotated with the tabs style (i.e., [tab]
).
That nested combination of blocks gets translated by this extension into a single tabs block that is a specialization of an open block.
The tabbed interface produced from this block can help organize information by code language, operating system, or product variant. The benefit of organizing information in this way is that it condenses the use of vertical space by only showing what’s relevant to the reader (and thus hiding information that’s irrelevant or redundant). The result is that readers enjoy a better user experience when reading your documentation.
Create a Gemfile in your project:
source 'https://rubygems.org'
gem 'asciidoctor-tabs'
# or use the code directlly from GitHub
# gem 'asciidoctor-tabs', github: 'asciidoctor/asciidoctor-tabs'
Then optionally configure Bundler to install gems locally:
$ bundle config --local path .bundle/gems
Then use Bundler to install the gem:
$ bundle
A tabset is defined using a description list (dlist) enclosed in an example block annotated with the tabs style.
[tabs]
====
Tab A:: Contents of Tab A.
Tab B::
+
Contents of Tab B.
Tab C::
+
--
Contents of Tab C.
Contains more than one block.
--
====
The tabs themselves are modeled as a dlist. Each item in the dlist becomes a separate tab. The term is used as the tab’s label and the description is used as the tab’s contents. The contents can be defined as primary text, attached blocks, or both. If the tab has a single attached block, and that block is an open block with no attributes, the open block enclosure itself is discarded upon conversion.
You may choose to extend the block delimiter length from the typical 4 characters to 6 in order to avoid conflicts with any example blocks inside the tabs block (or just as a matter of style).
[tabs]
======
Tab A::
+
====
Example block in Tab A.
====
Tab B:: Just text.
======
Using this technique, you can also create nested tabsets.
[tabs]
======
Tab A::
+
Selecting Tab A reveals a tabset with Tab Y and Tab Z.
+
[tabs]
====
Tab Y:: Contents of Tab Y, nested inside Tab A.
Tab Z:: Contents of Tab Z, nested inside Tab A.
====
Tab B:: Just text.
======
If you want to synchronize (i.e., sync) the tab selection across tabsets, set the tabs-sync-option
on the document.
:tabs-sync-option:
[tabs]
====
Tab A:: Triggers selection of Tab A in other congruent tabsets.
Tab B:: Triggers selection of Tab B in other congruent tabsets.
====
...
[tabs]
====
Tab A:: Triggers selection of Tab A in other congruent tabsets.
Tab B:: Triggers selection of Tab B in other congruent tabsets.
====
Only tabsets that have the same sync group ID are synchronized.
By default, the sync group ID is computed by taking the text of each tab, sorting that list, and joining it on |
(e.g., A|B
).
Each unique combination of tabs—or congruent tablist—implicitly creates a new sync group.
You can override the sync group ID of a tabset using the sync-group-id
attribute on the block.
This allows you to control the scope of the sync or to force a tabset to participate in a sync group even if its not congruent.
:tabs-sync-option:
[tabs,sync-group-id=group-1]
====
Tab A:: Triggers selection of Tab A in second tabset.
Tab B:: Triggers selection of Tab B in second tabset.
====
[tabs,sync-group-id=group-1]
====
Tab A:: Triggers selection of Tab A in first tabset.
Tab B:: Triggers selection of Tab B in first tabset.
====
[tabs,sync-group-id=group-2]
====
Tab A:: Triggers selection of Tab A in fourth tabset.
Tab B:: Triggers selection of Tab B in fourth tabset.
====
[tabs,sync-group-id=group-2]
====
Tab A:: Triggers selection of Tab A in third tabset.
Tab B:: Triggers selection of Tab B in third tabset.
====
Instead of enabling tabs sync globally, you can set the sync
option on individual tabs blocks.
[tabs%sync]
====
Tab A:: Triggers selection of Tab A in third tabset.
Tab B:: Triggers selection of Tab B in third tabset.
====
[tabs]
====
Tab A:: Does not trigger selection of Tab A in other tabsets.
Tab B:: Does not trigger selection of Tab B in other tabsets.
====
[tabs%sync]
====
Tab A:: Triggers selection of Tab A in first tabset.
Tab B:: Triggers selection of Tab B in first tabset.
====
Conversely, if you want to delist a tabs block from the global sync, set the nosync
option on that block.
:tabs-sync-option:
[tabs]
====
Tab A:: Triggers selection of Tab A in third tabset.
Tab B:: Triggers selection of Tab B in third tabset.
====
[tabs%nosync]
====
Tab A:: Does not trigger selection of Tab A in other tabsets.
Tab B:: Does not trigger selection of Tab B in other tabsets.
====
[tabs]
====
Tab A:: Triggers selection of Tab A in first tabset.
Tab B:: Triggers selection of Tab B in first tabset.
====
If you want to persist the sync selection, assign a value to the data-sync-storage-key
attribute on the <script>
tag.
<script data-sync-storage-key="preferred-tab">
By default, the sync selection (per group) will be persisted to local storage (i.e., data-sync-storage-scope="local"
) using the specified key.
You can set the data-sync-storage-scope
attribute on the <script>
tag to session
to use session storage instead of local storage.
<script data-sync-storage-key="preferred-tab" data-sync-storage-scope="session">
When using the extension on a standalone document (which will automatically embed the supporting script), you can configure these options using the tabs-sync-storage-key
and tabs-sync-storage-scope
document attributes, respectively.
:tabs-sync-storage-key: tabs
:tabs-sync-storage-scope: session
In this case, the converter will set the corresponding attributes on the <script>
tag automatically.
$ asciidoctor -r asciidoctor-tabs tabs.adoc
You can specify an alternate stylesheet for tabs using the tabs-stylesheet
document attribute.
$ asciidoctor -r asciidoctor-tabs -a tabs-stylesheet=my-tabs.css tabs.adoc
The value of the tabs-stylesheet
attribute is handled in the same way as the built-in stylesheet
document attribute.
A relative path is resolved starting from the value of the stylesdir
document attribute, which defaults to the directory of the document.
There are two ways to use the extension with the Asciidoctor API.
In either case, you must require the Asciidoctor gem (asciidoctor
) before requiring this one.
You can require asciidoctor/tabs
to register the extension as a global extension, just like with the CLI.
require 'asciidoctor'
require 'asciidoctor/tabs'
Asciidoctor.convert_file 'tabs.adoc', safe: :safe
Or you can pass a registry instance to the Extensions.register
method to register the extension with a scoped registry.
require 'asciidoctor'
require 'asciidoctor/tabs/extensions'
registry = Asciidoctor::Extensions.create
Asciidoctor::Tabs::Extensions.register registry
Asciidoctor.convert_file 'tabs.adoc', extension_registry: registry, safe: :safe
If you’re not using other scoped extensions, you can pass in the extensions group without first creating a registry instance:
Asciidoctor.convert_file 'tabs.adoc', extensions: Asciidoctor::Tabs::Extensions.group, safe: :safe
This extension works by transforming the dlist inside the example block into a tabbed interface. The example block enclosure is discarded. The tabbed interface is supported by a stylesheet (style) and script (behavior) that are added to the HTML document by this extension. (These assets can be found in the data folder of the gem).
📎
|
The stylesheet and script are only added when producing a standalone document.
The stylesheet is added to the end of the <head> tag and the script added to the end of the <body> tag.
If the linkcss attribute is set by the API, the CLI, the document, or the safe mode, the HTML links to these assets.
Otherwise, the contents of these assets are embedded into the HTML.
|
The tabbed interface consists of two output elements.
The first element contains an unordered list of all the tab labels in document order.
The second element contains all the tab panes.
The labels and panes are correlated through the use of a unique ID.
Each tab is assigned an id
attribute and each pane is assigned an aria-labelledby
attribute that references the corresponding ID.
The added stylesheet sets up the appearance of the tabbed interface and the added script supports the interaction (i.e., tab selection).
A tab can be selected when the page loads using a URL fragment (e.g., #id-of-tab-here
).
Otherwise, the first tab is selected when the page loads.
Follow the instructions below to learn how to get started developing on this project.
Copy the GitHub repository URL and pass it to the git clone
command:
$ git clone https://github.com/asciidoctor/asciidoctor-tabs
Next, switch to the project directory:
$ cd asciidoctor-tabs
The development dependencies are defined in the Gemfile at the root of the project.
Use the bundle
command from Bundler to install these dependencies under the project directory:
$ bundle --path=.bundle/gems
You must invoke bundle
from the project’s root directory so it can locate the Gemfile.
The test suite is located in the spec directory. The tests are based on RSpec.
You can run all of the tests using Rake:
$ bundle exec rake spec
For more fine-grained control, you can also run the tests directly using RSpec:
$ bundle exec rspec
To run all tests in a single spec, pass the spec file to the rpec
command:
$ bundle exec rspec spec/reducer_spec.rb
If you only want to run a single test (or a group of tests), you can do so by first tagging the test cases, then filtering the test run using that tag.
Start by adding the only
tag to one or more specifications:
it 'should do something new', only: true do
expect(true).to be true
end
Next, run RSpec with the only
flag enabled:
$ bundle exec rspec -t only
RSpec will only run the specifications that contain this flag.
You can also filter tests by keyword.
Let’s assume we want to run all the tests that have role
in their description.
Run RSpec with the example filter:
$ bundle exec rspec -e role
RSpec will only run the specifications that have a description containing the text only
.
To generate a code coverage report when running tests using simplecov, set the COVERAGE
environment variable as follows when running the tests:
$ COVERAGE=deep bundle exec rake spec
You’ll see a total coverage score, a detailed coverage report, and a link to HTML report in the output. The HTML report helps you understand which lines and branches were missed, if any.
Asciidoctor Tabs was written by Dan Allen of OpenDevise Inc. and contributed to the Asciidoctor project.
Copyright © 2018-present Dan Allen (OpenDevise Inc.) and the individual contributors to this project. Use of this software is granted under the terms of the MIT License.
See the LICENSE for the full license text.