/**
* Register custom Hexo tag {% tabs %}.
* @module hexo/tag/tabs
*/
const { splitOption, splitTabOptions } = require('../../util/tab-options');
/**
* Register custom Hexo tag {% tabs %}.
* <p>
* This tag is rendered as a Bulma tabs component.
* <p>
* Each individual tab must be wrapped with <code><!-- tab [...tab_param] --><!-- endtab --></code>
*
* <h5>Tag Parameters:<h5>
* <table class="params">
* <tr>
* <th>Name</th>
* <th>Type</th>
* <th class="description">Description</th>
* </tr>
* <tr>
* <td class="name"><code>align</code></td>
* <td class="type"><span class="param-type"><code>string</code></span></td>
* <td class="description">[Optional] Alignment of the tabs. Available values are: left, centered, right, fullwidth. Default alignment is left.</td>
* </tr>
* <tr>
* <td class="name"><code>size</code></td>
* <td class="type"><span class="param-type"><code>string</code></span></td>
* <td class="description">[Optional] Size of the tabs. Available values are: small, medium, large. Default size is normal.</td>
* </tr>
* <tr>
* <td class="name"><code>style</code></td>
* <td class="type"><span class="param-type"><code>string</code></span></td>
* <td class="description">[Optional] Style of the tabs. Available values are: boxed, toggle, toggle-rounded. Default style is none.</td>
* </tr>
* </table>
*
* <h5>Tab Parameters:<h5>
* <table class="params">
* <tr>
* <th>Name</th>
* <th>Type</th>
* <th class="description">Description</th>
* </tr>
* <tr>
* <td class="name"><code>id</code></td>
* <td class="type"><span class="param-type"><code>string</code></span></td>
* <td class="description">[Optional] The ID of the current tab. This value must be unique on each page.</td>
* </tr>
* <tr>
* <td class="name"><code>active</code></td>
* <td class="type"><span class="param-type"><code>boolean</code></span></td>
* <td class="description">[Optional] Whether to show current tab by default. Only one of the tabs can be set to active.</td>
* </tr>
* <tr>
* <td class="name"><code>title</code></td>
* <td class="type"><span class="param-type"><code>string</code></span></td>
* <td class="description">[Optional] Title of the current tab.</td>
* </tr>
* <tr>
* <td class="name"><code>icon</code></td>
* <td class="type"><span class="param-type"><code>string</code></span></td>
* <td class="description">[Optional] Icon of the current tab. Use FontAwesome icon class name for this field.</td>
* </tr>
* </table>
*
* @see https://bulma.io/documentation/components/tabs/.
*
* @param {Hexo} hexo The Hexo instance.
*
* @example
* {% tabs align:fullwidth size:small style:toggle-rounded %}
* <!-- tab active id:tab_hello title:Hello -->
* This is hello.
* <!-- endtab -->
* <!-- tab id:tab_info 'icon:fas fa-info' 'title:Info Tab' -->This is info.<!-- endtab -->
* {% endtabs %}
*/
module.exports = function (hexo) {
let tabCount = 0;
function processTag(options, content) {
let classes = '';
for (const option of options) {
const [key, value] = splitOption(option);
switch (key) {
case 'align':
classes += ` is-${value}`;
break;
case 'size':
classes += ` is-${value}`;
break;
case 'style':
if (value === 'toggle-rounded') {
classes += ' is-toggle';
}
classes += ` is-${value}`;
break;
}
}
const pattern = /<!--\s*tab\s*(.*?)\s*-->([\s\S]*?)<!--\s*endtab\s*-->/g;
let match;
let tabElements = '';
let contentElements = '';
while ((match = pattern.exec(content)) !== null) {
const [, tabOptions, tabContent] = match;
// A counter, as random numbers in the same millisecond could be the same
let id = 'tab-' + Date.now() + '-' + tabCount++;
let icon = '';
let title = '';
let active = false;
for (const option of splitTabOptions(tabOptions)) {
const [key, value] = splitOption(option);
switch (key) {
case 'id':
id = value;
break;
case 'active':
active = true;
break;
case 'icon':
icon = `<span class="icon is-small ml-0">
<i class="${value}" aria-hidden="true"></i>
</span>`;
break;
case 'title':
title = value;
break;
}
}
tabElements += `<li${active ? ' class="is-active"' : ''}>
<a href="#${id}">
${hexo.render.renderSync({ text: icon + title, engine: 'markdown' })}
</a>
</li>`;
contentElements += `<div id="${id}" class="tab-content${active ? '' : ' is-hidden'}">
${hexo.render.renderSync({ text: tabContent, engine: 'markdown' })}
</div>`;
}
return `<div class="tabs my-3${classes}">
<ul class="mx-0 my-0">
${tabElements}
</ul>
</div>
${contentElements}`;
}
const script = `<script>
(function () {
function switchTab() {
if (!location.hash) {
return;
}
let hash = location.hash.substring(1);
try {
// Non-ASCII IDs are percent-encoded in the URL
hash = decodeURIComponent(hash);
} catch (e) {}
const id = '#' + CSS.escape(hash);
const $tabMenu = document.querySelector(\`.tabs a[href="\${id}"]\`);
if (!$tabMenu) {
return;
}
const $tabMenuContainer = $tabMenu.parentElement.parentElement;
Array.from($tabMenuContainer.children).forEach($menu => $menu.classList.remove('is-active'));
Array.from($tabMenuContainer.querySelectorAll('a'))
.map($menu => document.getElementById($menu.getAttribute("href").substring(1)))
.forEach($content => $content.classList.add('is-hidden'));
if ($tabMenu) {
$tabMenu.parentElement.classList.add('is-active');
}
const $activeTab = document.querySelector(id);
if ($activeTab) {
$activeTab.classList.remove('is-hidden');
}
}
// The script is in <head>, so wait for the tabs to switch to the tab in the URL
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', switchTab);
} else {
switchTab();
}
window.addEventListener('hashchange', switchTab, false);
})();
</script>`;
hexo.extend.injector.register('head_end', script);
hexo.extend.tag.register('tabs', processTag, { ends: true });
};