GithubHelp home page GithubHelp logo

asciidoctor / asciidoctor-tabs Goto Github PK

View Code? Open in Web Editor NEW
35.0 6.0 8.0 239 KB

An extension for Asciidoctor that adds a tabs block to the AsciiDoc syntax.

License: MIT License

Ruby 79.62% CSS 2.98% JavaScript 13.33% Shell 4.06%

asciidoctor-tabs's Introduction

Asciidoctor Tabs

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.

Overview

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.

Install

Using gem command

$ gem install --prerelease asciidoctor-tabs

Using Bundler

Create a Gemfile in your project:

Gemfile
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

Syntax

A tabset is defined using a description list (dlist) enclosed in an example block annotated with the tabs style.

tabs.adoc
[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).

tab-with-example-block.adoc
[tabs]
======
Tab A::
+
====
Example block in Tab A.
====

Tab B:: Just text.
======

Using this technique, you can also create nested tabsets.

tab-with-nested-tabs.adoc
[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.
======

Tabs Sync

If you want to synchronize (i.e., sync) the tab selection across tabsets, set the tabs-sync-option on the document.

tabs-sync.adoc
: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-with-custom-sync-groups.adoc
: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-with-sync-option.adoc
[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-with-nosync-option.adoc
: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.

Usage

CLI

$ 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.

API

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

How it Works

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.

Development

Follow the instructions below to learn how to get started developing on this project.

Retrieve the source code

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

Install the dependencies

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.

Run the tests

The test suite is located in the spec directory. The tests are based on RSpec.

Run all tests

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

Run specific tests

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.

Generate code coverage

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.

Authors

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.

Trademarks

AsciiDoc® is a trademark of the Eclipse Foundation, Inc.

asciidoctor-tabs's People

Contributors

ahus1 avatar mojavelinux avatar okeanos avatar vy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

asciidoctor-tabs's Issues

Don't remove open block enclosure if block has metadata (style, id, role, etc.)

As a convenience, the tabs extension removes the open block enclosure around the contents of a tab if the block is an open block and is the only child. However, this breaks the case when that child is itself a tabs block (since a tabs block becomes an open block at runtime).

The open block enclosure should only be removed if the block has no style, id, or role. That will preserve the enclosure that is necessary to make a nested tabs block.

In additional, the style on the generated tabs block should be set to "tabs" for other extensions to be able to recognize it as such (the role alone is not sufficient).

Implement role attribute

Would like to see asciidoctor-tabs have a full role attribute implemented and working.
This would keep it additionally consistent with the way other blocks works.

  • e.g. [tabs.chocolate]

https://docs.asciidoctor.org/asciidoc/latest/attributes/role/

Currently only the ID is working.

  • e.g. [tabs#redWine]

Currently, If we lazily put a "." proceeding the name, (example: [tabs.]) then the terminal will report an empty role.

The idea behind using the role feature on a block is the ability to use a CSS style on 1 or more tabset(s).
Some branding styling guidelines mandate the use of a class ahead of an ID, simply for streamlining into an existing website. Having the use of a role makes the implementation easier.

Add smoke test for npm package

Add a basic smoke test to verify the npm package is sound.

Eventually, we'll want to run all the tests from the Ruby-based extension in JavaScript. For now, this basic smoke test should prevent pushing out a bad package.

Honor linkcss attribute on document

Currently, the tabs CSS and JS are always embedded into the standalone HTML document. If the linkcss attribute is set on the document (such as in secure mode), the extension should instead output HTML to link to the tabs stylesheet (tabs.css) and behavior file (tabs.js). If the copycss attribute is set, copy the files from gem into the docdir.

Using linkcss without copycss allows to user to use their own copies of these assets.

"skipping reference to missing attribute" in tabs

Using Antora 3.1.4 and @asciidoctor/tabs 1.0.0-beta.5 I've followed the Antora setup as described Using a Dedicated UI Project.

Tabs render fine, but attributes used in the tabs will produce compile warnings. For example:

:group-id: dk.ape
:artifact-id: ape

[tabs]
======
Gradle::
+
[source,groovy,subs=attributes+]
----
implemenation("{group-id}:{artifact-id}:{page-component-version}")
----

Maven::
+
[source,xml,subs=attributes+]
----
<dependency>
    <groupId>{group-id}</groupId>
    <artifactId>{artifact-id}</artifactId>
    <version>{page-component-version}</version>
</dependency>
----
======

will give the following compile warnings:

npx antora --fetch antora-playbook.yml

[17:50:22.027] WARN (asciidoctor): skipping reference to missing attribute: group-id
    file: /mnt/c/repos/community-documentation/modules/guides/pages/libs.adoc
    source: /mnt/c/repos/community-documentation (branch: lib-partials <worktree>)
[17:50:22.033] WARN (asciidoctor): skipping reference to missing attribute: artifact-id
    file: /mnt/c/repos/community-documentation/modules/guides/pages/libs.adoc
    source: /mnt/c/repos/community-documentation (branch: lib-partials <worktree>)
[17:50:22.035] WARN (asciidoctor): skipping reference to missing attribute: group-id
    file: /mnt/c/repos/community-documentation/modules/guides/pages/libs.adoc
    source: /mnt/c/repos/community-documentation (branch: lib-partials <worktree>)
[17:50:22.036] WARN (asciidoctor): skipping reference to missing attribute: artifact-id
    file: /mnt/c/repos/community-documentation/modules/guides/pages/libs.adoc
    source: /mnt/c/repos/community-documentation (branch: lib-partials <worktree>)
Site generation complete!
Open file:///mnt/c/repos/community-documentation/build/site/index.html in a browser to view your site.

producing:
image
image

Interestingly, the {page-component-version} attribute works just fine. If I add the same code blocks in the tabs just above, everything works fine (except now I have too many code blocks):

[source,groovy,subs=attributes+]
----
implemenation("{group-id}:{artifact-id}:{page-component-version}")
----

[source,xml,subs=attributes+]
----
<dependency>
    <groupId>{group-id}</groupId>
    <artifactId>{artifact-id}</artifactId>
    <version>{page-component-version}</version>
</dependency>
----

[tabs]
======
Gradle::
+
[source,groovy,subs=attributes+]
----
implemenation("{group-id}:{artifact-id}:{page-component-version}")
----

Maven::
+
[source,xml,subs=attributes+]
----
<dependency>
    <groupId>{group-id}</groupId>
    <artifactId>{artifact-id}</artifactId>
    <version>{page-component-version}</version>
</dependency>
----
======

produces:

image

Toggling external content

Hi, @mojavelinux! 👋
Thanks a lot for your great work in general and for this extension in particular 👍
Context: We migrate a part of PlayFramework docs that was written by Paradox and use AsciiDoc Tabs. But we have a one issue.
Is it possible to add behavior when the switching tabs also change the content outside the tabs?

I'll explain what I mean. In Paradox it's looks like next:

Java
:   @@snip [example-second.java](/docs/src/main/resources/tab-switching/examples.java)

Scala
:   @@snip [example-second.scala](/docs/src/main/resources/tab-switching/examples.scala)

The module file is generated in ... for @java[Java]@scala[Scala]

And when I'm switching the tabs, the text in paragraph also is changed. You can see how it works on this page for example.

chrome-capture-2023-12-19

I can try to create a pull request to solve this issue, but I need your opinion about how the solution should look to be accepted 🙏

Linking to specific tabs

It'd be very nice if we could be able to link to a specific tab within a tabbed code block.

Currently, when we give an ID to a tab and xref it, the link goes to the first tab of the block, not to the ID'ed one.

Error when using the extension with asciidoctor.js version 3.0.2

Summary

The extension errors out when used with asciidoctor js version 3.0.2

Env setup

npm i -g  asciidoctor &&   npm i -g @asciidoctor/tabs

asciidoctor -V
Asciidoctor.js 3.0.2 (Asciidoctor 2.0.20) [https://asciidoctor.org]
Runtime Environment (node v18.16.1 on linux)
CLI version 4.0.0

Sample adoc

[tabs]
====
Tab A:: Contents of Tab A.

Tab B::
+
Contents of Tab B.

Tab C::
+
--
Contents of Tab C.

Contains more than one block.
--
====

Command

asciidoctor -r @asciidoctor/tabs tabs.adoc

Error

/scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/asciidoctor/node_modules/@asciidoctor/opal-runtime/src/index.cjs:1516
    stubs = stubs.split(',');
                  ^

TypeError: stubs.split is not a function
    at Opal.add_stubs (/scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/asciidoctor/node_modules/@asciidoctor/opal-runtime/src/index.cjs:1516:19)
    at /scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/@asciidoctor/tabs/dist/index.js:11:8
    at Object.<anonymous> (/scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/@asciidoctor/tabs/dist/index.js:257:3)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1143:19)
    at require (node:internal/modules/cjs/helpers:110:18)
    at Object.<anonymous> (/scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/@asciidoctor/tabs/lib/extensions.js:3:1)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1143:19)
    at require (node:internal/modules/cjs/helpers:110:18)
    at Object.<anonymous> (/scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/@asciidoctor/tabs/lib/index.js:3:20)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Module.require (node:internal/modules/cjs/loader:1143:19)
    at require (node:internal/modules/cjs/helpers:110:18)
    at Invoker.requireLibrary (/scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/asciidoctor/node_modules/@asciidoctor/cli/lib/invoker.js:128:12)
    at /scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/asciidoctor/node_modules/@asciidoctor/cli/lib/invoker.js:135:29
    at Array.forEach (<anonymous>)
    at Invoker.prepareProcessor (/scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/asciidoctor/node_modules/@asciidoctor/cli/lib/invoker.js:134:20)
    at Invoker.invoke (/scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/asciidoctor/node_modules/@asciidoctor/cli/lib/invoker.js:24:13)
    at Object.<anonymous> (/scratch/blueuser/miniconda3/envs/testtabs/lib/node_modules/asciidoctor/bin/asciidoctor:9:22)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)
    at node:internal/main/run_main_module:23:47

Node.js v18.16.1

Make default tab selection configurable

Currently, the first tab in the list is selected by default (unless a tab is selected by some other means, such as a URL fragment or sync group). It should be possible to configure/override which tab is selected by default.

One way to do this is to allow the default tab to be specified by its 1-based index in the list.

[tabs,default=2]
====
Tab A:: A
Tab B:: B
====

Another approach would be to allow the default tab to be specified by its ID:

[tabs#tabs,default=#tabs_tab_a]
====
Tab A:: A
Tab B:: B
====

(This becomes simpler if each tab can be assigned an explicit ID, as proposed in #57).

Yet another approach would be to allow the default tab to be specified by its label:

[tabs,default=Tab A]
====
Tab A:: A
Tab B:: B
====

However, this is tricky for a number of reasons. The first is that the label may include non-text content or formatting that makes matching difficult. A possible approach here would be to compare the AsciiDoc source of both values, perhaps after removing a leading anchor. It's certainly possible, but just gets kind of messy. I'd like to get some feedback about whether anyone would actually want this approach before spending the time to implement it.

Promote explicit ID on tab

For those who want to assign a stable ID to a tab, it should be possible to specify the ID explicitly on the tab item. To do so, the extension should look for the anchor at the start of the dlist entry and promote it. For example:

[tabs]
====
[[my-tab-id]]Tab A::
Tab B::
====

The ID of the tab with the content (i.e., label) "Tab A" would be my-tab-id.

Technically this already works in practice. The list item for the tab still gets an auto-generated ID, but the tab also responds to the custom ID specified at the start of the label.

I think the question here is whether the explicit ID should replace the auto-generated ID. I feel like it probably should. The output we would expect, then, is:

<li id="my-tab-id" class="tab">
<p>Tab A</p>
</li>

I also wonder whether it should be possible to specify whether the ID of the tabs (i.e., tabset) should be prepended to the explicit ID (like when it is generated) or whether the ID should be used as is.

Opal throws an error with Antora

I am trying to use asciidoctor-tabs as an extension with Antora. I have added both the CSS and Javascript to our supplemental files but when I attempt to register the extension via our make file in the form of:

npx antora --extension @antora/lunr-extension --extension @asciidoctor/tabs --stacktrace

The build command fails with:

    Cause: ReferenceError
        at Object.<anonymous> (/home/docproduction/projects/uyuni-docs/node_modules/@asciidoctor/tabs/dist/index.js:257:4)
        at Module._compile (node:internal/modules/cjs/loader:1376:14)
        at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
        at Module.load (node:internal/modules/cjs/loader:1207:32)
        at Module._load (node:internal/modules/cjs/loader:1023:12)
        at Module.require (node:internal/modules/cjs/loader:1235:19)
        at require (node:internal/modules/helpers:176:18)
        at Object.<anonymous> (/home/docproduction/projects/uyuni-docs/node_modules/@asciidoctor/tabs/lib/extensions.js:3:1)
        at Module._compile (node:internal/modules/cjs/loader:1376:14)
        at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
make: *** [Makefile.en:44: antora-suma-en] Error 1```

These tabs would be very useful for us! If you can provide insight on how we might achieve this I would be grateful.

@asciidoctor/tabs 1.0.0-beta.6
[email protected]
@antora/cli: 3.1.7
@antora/site-generator: 3.1.7

Perhaps a similar issue as: #66

If this is the case how can I avoid this error?

Warning when dlist has ID and filetype is not html

When converting an AsciiDoc document with a tabs block to a non-HTML format (e.g., DocBook), and the dlist has an ID, Asciidoctor emits a warning about a duplicate ID.

In this case, the extension should append the block to the parent and return nil so that the processor does not attempt to register a ref for the dlist a second time.

Allow sync group ID to be specified explicitly

Currently, the sync group ID is derived automatically by joining the sync ID of each tab in the tablist. This mandates that tabs blocks be congruent (i.e., have the same tablist) in order to participate in the same sync group. Allow this group ID to be overridden so tabs blocks which are non-congruent (i.e, have a different tablist) can participate in the same sync group.*

The group ID can be specified using the sync-group-id attribute on the block. For example:

[tabs]
====
A:: A
B:: B
====

[tabs,sync-group-id=A|B]
====
A:: A
B:: B
C:: C
====

Since Asciidoctor doesn't currently support passing through data attributes to the HTML, use a CSS class to pass this attribute (e.g, data-sync-group-id=A|B). The tabs script will need to detect the presence of this CSS class and convert it to a data attribute in place of the automatically derived one.

* Keep in mind that if a tab is selected that's not in other tabs blocks in the same group, the default tab for that tabs block will be selected when the page is reloaded.

Tabs sync can cause content in view to jump

If the tabs sync feature is enabled, and there are tabs blocks above the tabs block that the user is interacting with (aka the current tabs block), the current tabs block may appear to jump when selecting different tabs. This can happen when the tab panes that become active on a tab selection have a different height than the tab panes that were previously visible. This height difference can cause the current tabs block to shift and even go out of view. The tabs script should attempt to hold the current tabs block in the same position on the page so that the user does not see it shift vertically.

Align terminology with accessiblity guidelines for tabs

Tabs are one of those components that are well-defined and specified by ARIA and Open UI. Asciidoctor Tabs should adhere to the terminology in these guidelines as best as possible.

Here's the HTML structure we'd like to aim for:

<div id="_tabs_1" class="tabs">
<div class="ulist">
<ul class="tablist">
<li id="_tabs_1_tab_a" class="tab is-selected" role="tab" aria-controls="_tabs_1_tab_a--panel" aria-selected="true">
<p>Tab A</p>
</li>
<li id="_tabs_1_tab_b" class="tab" role="tab" aria-controls="_tabs_1_tab_b--panel">
<p>Tab B</p>
</li>
</ul>
</div>
<div class="content">
<div id="_tabs_1_tab_a--panel" class="tabpanel" role="tabpanel" aria-labelledby="_tabs_1_tab_a">
<div class="paragraph">
<p>Contents of tab A.</p>
</div>
</div>
<div id="_tabs_1_tab_b--panel" class="tabpanel" role="tabpanel" aria-labelledby="_tabs_1_tab_b" hidden>
<div class="paragraph">
<p>Contents of tab B.</p>
</div>
</div>
</div>
</div>

The ID for the tab panels is purely internal. It is constructed from the tab ID by appending --panel.

Since the HTML converter Asciidoctor does not support adding the role attribute, those attributes will likely need to be added by JavaScript (the behavior script). The same is likely true for the aria- attributes.

I feel strongly that tablist should be an actual list (which we can model in AsciiDoc) rather than a lose set of buttons in a div. Therefore, I've stuck with the ulist structure for now.

This issue is related to #15. I'd like to get this done first, then make that switch.

Please note that this issue is not about implementing the full accessibility recommendations. We first need to align the terminology.

CSS to prevent table from overflowing has unwanted side effects

The CSS that was added in 1.0.0-alpha.11 to prevent a table from overflowing the bounds of the tabpanel causes the table width to be ignored. While a horizontal scrollbar appears if the table overflows the bounds, a table which does not overflow the bounds does not honor its specified width. (While the table stretches, the tbody/tr elements don't). All tables effectively become autowidth tables.

This behavior is caused by adding display: block to the table. The approach of changing the display property of a table to block is a known CSS hack to force a table to honor the maximum width of the parent. Unfortunately, it's not going to work given all the possible cases we have to support. It breaks the content.

It's also not a perfect solution because it breaks border collapsing. While we are employing a workaround, it doesn't handle all permutations of grid and frame correctly.

The only viable solution that I know of is to wrap the table in a div and put the overflow-x: auto rule on that div.

<div class="tablecontainer">
<table class="tableblock frame-all grid-all stretch">
... 
</table>
</div>

This must be done in JavaScript to make it a universal solution.

This enclosure prevents the table from overflowing the bounds of the parent without having to restore to hacks. The only downside is that the stylesheet must move the margin bottom on the table (if applicable) to this enclosure so the scrollbar appears in the correct place. We'll either put these styles in the Asciidoctor Tabs stylesheet and/or into the Asciidoctor default stylesheet (in the next patch release).

Create tabset as openblock

Currently, the tabset is created entirely using passthrough blocks. The tabset should instead be created as an open block. This allows it to be better recognized by the referencing system and by code that operates on the parsed document such as a tree processor.

The only drawback of this change is that it will move the location of the div with the "content" class. That will require the following style change for anyone who has been using a custom stylesheet:

old:

.tabset > .content { ... }

new:

.tabset .tab-pane { ... }

It also means that the tab panes will no longer be enclosed, though another div could be added around them if need be.

UPDATE: The new structure is .tabs .tabpanel.

Support multiple tabs with the same content

It should be possible to create multiple tabs that point to the same content using the multiple terms feature of a dlist entry.

[tabs]
====
Tab A::
Tab B::
+
Shared content for Tab A and Tab B.

Tab C::
+
Content for Tab C only.
====

There should only be one tab pane for the terms (i.e., tab labels) that share the same content.

Add is-loaded to tabs blocks on next tick following initialization

If you add a transition to the tabs when the selected tab is changed, the problem you run into is that it ends up playing during initialization if the non-default tab is selected from storage. Consider the following example:

.tabs.is-loaded .tablist > ul li {
  transition: all 200ms ease-in-out;
}

If the non-default tab is selected during initialization, the user sees an animation from the first tab to the selected tab. What we want is for the non-default tab to appear selected immediately.

What's needed is a CSS class that is added after the tick (i.e., the animation frame) that initializes the tabs. That way, the transition won't take effect until the user interacts with the tabs. Effectively 3 phases of initialization (.is-loading, :not(.is-loading), and .is-loaded), not just two.

After removing the is-loading CSS class from the tabs blocks, schedule a function to run to run in the next tick that adds the is-loaded CSS class to the tabs blocks.

This change does not eliminate the need to use .tabs:not(.is-loading) selectors in the CSS as though are there to respond immediately to the work done during initialization. The .tabs.is-loaded selector is for declaring transitions on user interactions.

Honor title on tabs block

If the tabs block has a (block) title, include it in the HTML output. It should be wrapped in a div with the class "title" as the first child of the container (with the class "tabset"). Normal subs should be applied to the text.

This is really just an interim measure until #15 is implemented, which should support a block title just like any other open block.

Decode URL fragment when searching for tab with matching ID

If the URL fragment contains the character %, decode the value so that it can be successfully compared to a matching tab ID. Currently, this comparison will fail because one value is URL encoded and the other is not. This happens when the ID contains non-Latin characters such as .

Substitutions not applied to first paragraph of a tab's content

If an attribute reference is used in the principal text or first attached paragraph of a tab item (i.e., dlist entry), that attribute reference is not processed.

Example:

:url-download: https://example.org

[tabs]
====
Download:: Download the installer from {url-download}.

Install::
Run the installer you downloaded from {url-download}.
====

This happens because substitutions are not configured properly for this node. This node should be configured with normal substitutions (just like a list item or paragraph).

Only sync tabs with congruent tablists

Only tabs blocks with unique tablists should be synchronized. Each unique combination of tabs (order insensitive) implicitly creates a new sync group. When the sync selection is stored, it should be stored under a key unique to this sync group. The sync IDs in the group should be joined using a pipe character. For example, preferred-tab-a|b|c.

By making this change, we avoid conflicts between tab selections for tabs blocks with different sets of tabs on the same page.

In the future, we may permit the sync group to be overridden to accommodate scenarios where the tablists are not congruent (such as an extra tab in some occurrences).

Set ID for tab on list item

Currently, the ID for a tab is set using an inline anchor. The ListItem in Asciidoctor supports an ID when set from the API, which is how this extension is using it. Therefore, the ID should be set directly on the list item.

Before:

<li>
<p><a id="_tabset1_tab_a"></a>Tab A</p>
</li>

After:

<li id="_tabset1_tab_a">
<p>Tab A</p>
</li>

The behavior (JavaScript) should be updated accordingly.

Nested tabsets

I’m trying to determine if asciidoctor-tabs supports nested tabsets (placing a tabset within another tabset). If it doesn't, then everything I describe below is probably moot and this issue can probably be closed or transitioned to an enhancement request.

The issue I’m seeing is that, on initial page load, nested tabsets appear to render correctly, but upon switching between the top-level tabs, any nested tabsets underneath show up as floating tabs without any content or border. Curiously, though, if I click on one of the floating tabs, its content will (re)appear correctly.

I’m trying to troubleshoot whether the problem lies with:

  • my AsciiDoc syntax,
  • my implementation of asciidoctor-tabs,
  • the Antora Default UI,
  • an issue with the asciidoctor-tabs extension itself, or
  • an issue with Antora/Asciidoctor more generally

Visual aid showing the issue in action:

asciidoctor-tabs-issue

My environment:

My AsciiDoc (mildly sanitized for sake of demonstration):

Page:

[tabs]
======
Debian/Ubuntu (APT)::
+
include::guides:partial$cassandra-install-apt.adoc[]

CentOS/RHEL (YUM)::
+
include::guides:partial$cassandra-install-yum.adoc[]

Tarball::
+
include::guides:partial$cassandra-install-tarball.adoc[]
======

Partial 1 (cassandra-install-apt.adoc):

. Step 1
+
[tabs]
====
Command::
+
--
[source,shell,subs="attributes+"]
----
include::example$bash/commands/get-deb-package.sh[]
----
--

Result::
+
--
[source,console,subs="attributes+"]
----
include::example$bash/results/get-deb-package.result[]
----
--
====

. Step 2
+
[tabs]
====
cURL::
+
--
[source,shell]
----
include::guides:example$bash/commands/curl-add-repo-keys.sh[]
----
--

Wget::
+
--
[source,shell]
----
include::guides:example$bash/commands/wget-add-repo-keys.sh[]
----
--

Result::
+
--
[source,console]
----
include::guides:example$bash/results/add-repo-keys.result[]
----
--
====

. Step 3
+
[source,shell]
----
include::guides:example$bash/commands/apt-get-update.sh[]
----

. Step 4
+
[source,shell]
----
include::guides:example$bash/commands/apt-add-key.sh[]
----

. Step 5
+
[source,shell,subs="attributes+"]
----
include::guides:example$bash/commands/apt-get-cass.sh[]
----

I can post the content of the additional partials if necessary for further troubleshooting.

Autogenerate IDs in a way consistent with section IDs

This extension autogenerates an ID for each tabset, then uses that value as a prefix to autogenerate an ID for each tab. The ID is necessary in order for the behavior (JavaScript) to correlate the tab's label and content.

The extension is currently using a home-grown strategy to autogenerate the ID. This introduces an inconsistency compared to how IDs for sections are generated. Instead, the extension should delegate to Section.generate_id to generate the ID.

The extension should compute the ID of a tabset as follows:

  • the value of idprefix attribute (default _)
  • the string literal "tabset"
  • the 1-based occurrence of a tabset in the document

The extension should compute the ID of a tab as follows:

  • the ID of the tabset
  • the value of the idseparator attribute (default _)
  • the result of converting label to an ID using Section.generate_id (using the idprefix already established)

By default, the ID of the first tabset is _tabset1. Assuming the first tab in that tabset has the label "First Tab", its ID would be _tabset1_first_tab. If idprefix= and idseparator=-, then the ID of the first tabset is tabset1 and the first tab is tabset1-first-tab.

Add option to save synced tab selection across pages

Add a configuration option to save the synced tab selection (by sync ID) so it persists across pages.

When setting up the tabs, look for the following two data attributes on the <script> tag:

  • data-sync-storage-key (e.g., preferred-tab)
  • data-sync-storage-scope (optional, default: local, allowed values: local, session)

For example:

<script src="path/to/tabs.js" data-sync-storage-key="preferred-tab"></script>

If data-sync-storage-key is specified, look up the value of that key in the scoped specified by data-sync-storage-scope. If the value matches the sync ID of a tab in the tablist, preselect that tab. Otherwise, preselect the first tab in the list.

When a tab with a sync ID is selected, and data-sync-storage-key is specified, save that value on that key in the scope specified by data-sync-storage-scope.

This feature should only be available when tab sync is enabled.

Automatic tab number should respect document order

When generating an ID for the tabs block (which also gets used to generate IDs for its child elements), the tab number used in the ID should follow the document order. Currently, if a tabs block is nested in another tab, the tab numbers become out of order. This happens since the children of a tabs block get parsed before the ID is generated for that tabs block. This can be corrected by reserving the tab number for the tabs block before parsing the children. In the event the extension does not return a tabs block, it must reset the tab number to the previous value (since it should not occupy a tab number).

Allow custom stylesheet to be specified

Allow a custom stylesheet for tabs to be specified using the tabs-stylesheet document attribute. This attribute should work exactly like the stylesheet attribute. If the value is empty (the default), the stylesheet provided by the extension should be used. If the attribute is unset, then no stylesheet should be used.

Provide fallback behavior for non-HTML backend

If this extension is used with a non-HTML backend (filetype is not html), output the original dlist without the example block enclosure.

In this case, the dlist should inherit the ID from the example block.

Add option to sync tabs

If multiple tabs blocks with the same (or similar labels) are used in the same document, Asciidoctor Tabs should offer an option for the tab selection to be synced across blocks.

By default, this feature should be turned off as its only something that is needed in a certain context. When turned on using the tabs-sync-option attribute, every tab in the same document with the same label should be selected when one of them is selected.

Sync tabs on hash change

The tabs sync feature does not cover the case when the tab is selected on a hash change. This can occur either on the initial page load or when clicking on xref. The tabs sync behavior should be the same in these cases.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.