diff --git a/Readme.md b/Readme.md index 6137156..02255cc 100644 --- a/Readme.md +++ b/Readme.md @@ -100,6 +100,25 @@ Example: {% endlist %} ``` +Additionally, you can use radiobatons using a contruction + +``` +{% list tabs vertical %} + + - Tab 1 + + Text 1. + + * You can use list + * And **other** features. + + - Tab 2 + + Text 2. + +{% endlist %} +``` + The keys for the tabs are generated automatically. They are based on the tab's names using the github anchors style. You can set your own keys for tabs with this statement: diff --git a/src/common.ts b/src/common.ts index 7b22653..3c415b4 100644 --- a/src/common.ts +++ b/src/common.ts @@ -1,21 +1,27 @@ +import type {TabsOrientation} from './plugin/transform'; import type {TabsController} from './runtime/TabsController'; export const TABS_CLASSNAME = 'yfm-tabs'; +export const TABS_VERTICAL_CLASSNAME = 'yfm-tabs-vertical'; export const TABS_LIST_CLASSNAME = 'yfm-tab-list'; export const TAB_CLASSNAME = 'yfm-tab'; export const TAB_PANEL_CLASSNAME = 'yfm-tab-panel'; export const ACTIVE_CLASSNAME = 'active'; +export const VERTICAL_TAB_CLASSNAME = 'yfm-vertical-tab'; export const GROUP_DATA_KEY = 'data-diplodoc-group'; export const TAB_DATA_KEY = 'data-diplodoc-key'; export const TAB_DATA_ID = 'data-diplodoc-id'; +export const TAB_DATA_VERTICAL_TAB = 'data-diplodoc-vertical-tab'; export const TAB_ACTIVE_KEY = 'data-diplodoc-is-active'; +export const TAB_RADIO_KEY = 'data-diplodoc-input'; export const DEFAULT_TABS_GROUP_PREFIX = 'defaultTabsGroup-'; export interface Tab { group?: string; key: string; + align: TabsOrientation; } export interface SelectedTabEvent { diff --git a/src/plugin/transform.ts b/src/plugin/transform.ts index c807e10..ae98491 100644 --- a/src/plugin/transform.ts +++ b/src/plugin/transform.ts @@ -11,11 +11,14 @@ import { GROUP_DATA_KEY, TABS_CLASSNAME, TABS_LIST_CLASSNAME, + TABS_VERTICAL_CLASSNAME, TAB_ACTIVE_KEY, TAB_CLASSNAME, TAB_DATA_ID, TAB_DATA_KEY, + TAB_DATA_VERTICAL_TAB, TAB_PANEL_CLASSNAME, + VERTICAL_TAB_CLASSNAME, } from '../common'; export type PluginOptions = { @@ -25,7 +28,9 @@ export type PluginOptions = { bundle: boolean; }; -const TAB_RE = /`?{% list tabs( group=([^ ]*))? %}`?/; +export type TabsOrientation = 'vertical' | 'horizontal'; + +const TAB_RE = /`?{% list tabs( group=([^ ]*))?( (vertical)|(horizontal))? %}`?/; let runsCounter = 0; @@ -119,6 +124,7 @@ function findTabs(tokens: Token[], idx: number) { function insertTabs( tabs: Tab[], state: StateCore, + align: TabsOrientation, {start, end}: {start: number; end: number}, { containerClasses, @@ -155,11 +161,20 @@ function insertTabs( tabListOpen.block = true; tabListClose.block = true; - tabsOpen.attrSet('class', [TABS_CLASSNAME, containerClasses].filter(Boolean).join(' ')); + const areTabsVerticalClass = align === 'vertical' && TABS_VERTICAL_CLASSNAME; + + tabsOpen.attrSet( + 'class', + [TABS_CLASSNAME, containerClasses, areTabsVerticalClass].filter(Boolean).join(' '), + ); tabsOpen.attrSet(GROUP_DATA_KEY, tabsGroup); tabListOpen.attrSet('class', TABS_LIST_CLASSNAME); tabListOpen.attrSet('role', 'tablist'); + if (align === 'vertical') { + tabsTokens.push(tabsOpen); + } + for (let i = 0; i < tabs.length; i++) { const tabOpen = new state.Token('tab_open', 'div', 1); const tabInline = new state.Token('inline', '', 0); @@ -168,6 +183,12 @@ function insertTabs( const tabPanelOpen = new state.Token('tab-panel_open', 'div', 1); const tabPanelClose = new state.Token('tab-panel_close', 'div', -1); + const verticalTabOpen = new state.Token('tab_open', 'input', 0); + const verticalTabLabelOpen = new state.Token('label_open', 'label', 1); + + tabOpen.map = tabs[i].listItem.map; + tabOpen.markup = tabs[i].listItem.markup; + const tab = tabs[i]; const tabId = getTabId(tab, {runId}); const tabKey = getTabKey(tab); @@ -175,6 +196,11 @@ function insertTabs( const tabPanelId = generateID(); + verticalTabOpen.block = true; + + verticalTabOpen.attrJoin('class', 'radio'); + verticalTabOpen.attrSet('type', 'radio'); + tabOpen.map = tabs[i].listItem.map; tabOpen.markup = tabs[i].listItem.markup; tabText.content = tabs[i].name; @@ -187,6 +213,7 @@ function insertTabs( tabOpen.attrSet(TAB_DATA_KEY, tabKey); tabOpen.attrSet(TAB_ACTIVE_KEY, i === 0 ? 'true' : 'false'); tabOpen.attrSet('class', TAB_CLASSNAME); + tabOpen.attrJoin('class', 'yfm-tab-group'); tabOpen.attrSet('role', 'tab'); tabOpen.attrSet('aria-controls', tabPanelId); tabOpen.attrSet('aria-selected', 'false'); @@ -197,21 +224,39 @@ function insertTabs( tabPanelOpen.attrSet('aria-labelledby', tabId); tabPanelOpen.attrSet('data-title', tab.name); + if (align === 'vertical') { + tabOpen.attrSet(TAB_DATA_VERTICAL_TAB, 'true'); + tabOpen.attrJoin('class', VERTICAL_TAB_CLASSNAME); + } + if (i === 0) { - tabOpen.attrJoin('class', ACTIVE_CLASSNAME); - tabOpen.attrSet('aria-selected', 'true'); + if (align === 'horizontal') { + tabOpen.attrJoin('class', ACTIVE_CLASSNAME); + tabOpen.attrSet('aria-selected', 'true'); + } else { + verticalTabOpen.attrSet('checked', 'true'); + } + tabPanelOpen.attrJoin('class', ACTIVE_CLASSNAME); } - tabListTokens.push(tabOpen, tabInline, tabClose); - tabPanelsTokens.push(tabPanelOpen, ...tabs[i].tokens, tabPanelClose); + if (align === 'vertical') { + tabsTokens.push(tabOpen, verticalTabOpen, verticalTabLabelOpen, tabInline, tabClose); + tabsTokens.push(tabPanelOpen, ...tabs[i].tokens, tabPanelClose); + } else { + tabListTokens.push(tabOpen, tabInline, tabClose); + tabPanelsTokens.push(tabPanelOpen, ...tabs[i].tokens, tabPanelClose); + } + } + + if (align === 'horizontal') { + tabsTokens.push(tabsOpen); + tabsTokens.push(tabListOpen); + tabsTokens.push(...tabListTokens); + tabsTokens.push(tabListClose); + tabsTokens.push(...tabPanelsTokens); } - tabsTokens.push(tabsOpen); - tabsTokens.push(tabListOpen); - tabsTokens.push(...tabListTokens); - tabsTokens.push(tabListClose); - tabsTokens.push(...tabPanelsTokens); tabsTokens.push(tabsClose); state.tokens.splice(start, end - start + 1, ...tabsTokens); @@ -290,6 +335,7 @@ export function transform({ } const tabsGroup = match[2] || `${DEFAULT_TABS_GROUP_PREFIX}${generateID()}`; + const orientation = (match[4] || 'horizontal') as TabsOrientation; const {tabs, index} = findTabs(state.tokens, i + 3); @@ -297,6 +343,7 @@ export function transform({ insertTabs( tabs, state, + orientation, {start: i, end: index + 3}, { containerClasses, diff --git a/src/react/useDiplodocTabs.ts b/src/react/useDiplodocTabs.ts index 550e6ca..2106f04 100644 --- a/src/react/useDiplodocTabs.ts +++ b/src/react/useDiplodocTabs.ts @@ -22,6 +22,7 @@ export function useDiplodocTabs(callback: UseDiplodocTabsCallback) { window[GLOBAL_SYMBOL].selectTabById(tabId, options), [], ), + // @todo remove selectTab: useCallback((tab: Tab) => window[GLOBAL_SYMBOL].selectTab(tab), []), }; } diff --git a/src/runtime/TabsController.ts b/src/runtime/TabsController.ts index 6334d2a..1badf3a 100644 --- a/src/runtime/TabsController.ts +++ b/src/runtime/TabsController.ts @@ -5,12 +5,14 @@ import { SelectedTabEvent, TABS_CLASSNAME, TABS_LIST_CLASSNAME, + TABS_VERTICAL_CLASSNAME, TAB_CLASSNAME, TAB_DATA_ID, TAB_DATA_KEY, TAB_PANEL_CLASSNAME, Tab, } from '../common'; +import type {TabsOrientation} from '../plugin/transform'; import { ElementOffset, getClosestScrollableParent, @@ -24,6 +26,7 @@ const Selector = { TAB_LIST: `.${TABS_LIST_CLASSNAME}`, TAB: `.${TAB_CLASSNAME}`, TAB_PANEL: `.${TAB_PANEL_CLASSNAME}`, + VERTICAL_TABS: `.${TABS_VERTICAL_CLASSNAME}`, }; export interface ISelectTabByIdOptions { @@ -43,12 +46,18 @@ export class TabsController { this._document = document; this._document.addEventListener('click', (event) => { const target = getEventTarget(event) as HTMLElement; + const areVertical = this.areTabsVertical(target); - if (isCustom(event) || !this.isValidTabElement(target)) { + if (isCustom(event)) { + return; + } + + if (!(this.isValidTabElement(target) || areVertical)) { return; } const tab = this.getTabDataFromHTMLElement(target); + if (tab) { this._selectTab(tab, target); } @@ -110,6 +119,7 @@ export class TabsController { } const tab = this.getTabDataFromHTMLElement(target); + if (tab) { this._selectTab(tab, target); } @@ -124,7 +134,7 @@ export class TabsController { } private _selectTab(tab: Tab, targetTab?: HTMLElement) { - const {group, key} = tab; + const {group, key, align} = tab; if (!group) { return; @@ -134,10 +144,10 @@ export class TabsController { const previousTargetOffset = scrollableParent && getOffsetByScrollableParent(targetTab, scrollableParent); - const updatedTabs = this.updateHTML({group, key}); + const updatedTabs = this.updateHTML({group, key, align}, align); if (updatedTabs > 0) { - this.fireSelectTabEvent({group, key}, targetTab?.dataset.diplodocId); + this.fireSelectTabEvent({group, key, align}, targetTab?.dataset.diplodocId); if (previousTargetOffset) { this.resetScroll(targetTab, scrollableParent, previousTargetOffset); @@ -145,7 +155,54 @@ export class TabsController { } } - private updateHTML(tab: Required) { + private updateHTML(tab: Required, align: TabsOrientation) { + switch (align) { + case 'vertical': { + return this.updateHTMLVertical(tab); + } + case 'horizontal': { + return this.updateHTMLHorizontal(tab); + } + } + + return 0; + } + + private updateHTMLVertical(tab: Required) { + const {group, key} = tab; + + const [tabs] = this._document.querySelectorAll( + `${Selector.TABS}[${GROUP_DATA_KEY}="${group}"] ${Selector.TAB}[${TAB_DATA_KEY}="${key}"]`, + ); + + let updated = 0; + const root = tabs.parentNode!; + const elements = root.children; + + for (let i = 0; i < elements.length; i += 2) { + const [title, content] = [elements.item(i), elements.item(i + 1)] as HTMLElement[]; + + const input = title.children.item(0) as HTMLInputElement; + + if (input.hasAttribute('checked')) { + title.classList.remove('active'); + content?.classList.remove('active'); + input.removeAttribute('checked'); + } + + if (title === tabs) { + title.classList.add('active'); + content?.classList.add('active'); + input.setAttribute('checked', 'true'); + } + + updated++; + } + + return updated; + } + + private updateHTMLHorizontal(tab: Required) { const {group, key} = tab; const tabs = this._document.querySelectorAll( @@ -205,9 +262,9 @@ export class TabsController { } private fireSelectTabEvent(tab: Required, diplodocId?: string) { - const {group, key} = tab; + const {group, key, align} = tab; - const eventTab: Tab = group.startsWith(DEFAULT_TABS_GROUP_PREFIX) ? {key} : tab; + const eventTab: Tab = group.startsWith(DEFAULT_TABS_GROUP_PREFIX) ? {key, align} : tab; this._onSelectTabHandlers.forEach((handler) => { handler({tab: eventTab, currentTabId: diplodocId}); @@ -219,13 +276,28 @@ export class TabsController { element.matches(Selector.TAB) && element.dataset.diplodocId ? element.closest(Selector.TAB_LIST) : null; + return tabList?.closest(Selector.TABS); } + private areTabsVertical(target: HTMLElement) { + const parent = target.parentElement; + + return target.dataset.diplodocVerticalTab || Boolean(parent?.dataset.diplodocVerticalTab); + } + private getTabDataFromHTMLElement(target: HTMLElement): Tab | null { + if (this.areTabsVertical(target)) { + const tab = target.dataset.diplodocVerticalTab ? target : target.parentElement!; + + const key = tab.dataset.diplodocKey; + const group = (tab.closest(Selector.TABS) as HTMLElement)?.dataset.diplodocGroup; + return key && group ? {group, key, align: 'vertical'} : null; + } + const key = target.dataset.diplodocKey; const group = (target.closest(Selector.TABS) as HTMLElement)?.dataset.diplodocGroup; - return key && group ? {group, key} : null; + return key && group ? {group, key, align: 'horizontal'} : null; } private getTabs(target: HTMLElement): {tabs: Tab[]; nodes: NodeListOf} { @@ -241,9 +313,11 @@ export class TabsController { return; } + /** horizontal-only supported feature (used in left/right button click) */ tabs.push({ group, key, + align: 'horizontal', }); }); diff --git a/src/runtime/scss/tabs.scss b/src/runtime/scss/tabs.scss index 9f92a09..0d6bb23 100644 --- a/src/runtime/scss/tabs.scss +++ b/src/runtime/scss/tabs.scss @@ -22,6 +22,70 @@ border-bottom: 1px solid rgba(0, 0, 0, 0.07); } +.yfm-tabs-vertical { + & > .yfm-tab-list { + flex-direction: column; + border-bottom: unset; + } + + & > .yfm-tab-panel { + margin-left: 30px; + } + + & > .yfm-tab-group { + display: flex; + flex-direction: row; + margin-bottom: 5px; + } + + .yfm-vertical-tab > input[type=radio] { + visibility: hidden; + width: 0; + height: 0; + } + + .yfm-vertical-tab > label { + display: inline-block; + cursor: pointer; + position: relative; + padding-left: 25px; + margin-right: 0; + line-height: 18px; + user-select: none; + } + + .yfm-vertical-tab > label:before { + content: ""; + position: absolute; + left: 0; + bottom: 1px; + border-radius: 50%; + width: 18px; + height: 18px; + border: #dfdfdf 1px solid; + background-color: white; + } + + /* Checked */ + .yfm-vertical-tab input[type=radio]:checked + label:before { + content: "•"; + background-color: rgb(82, 130, 255); + text-align: center; + vertical-align: middle; + font-size: 20px; + white-space: pre; + display: inline-flex; + justify-content: center; + align-items: center; + color: white; + } + + .yfm-vertical-tab { + border-bottom: unset !important; + } + +} + .yfm-tab { margin-bottom: -1px; margin-right: 20px; diff --git a/test/data/tabs.ts b/test/data/tabs.ts index a4fcdc8..203b0ee 100644 --- a/test/data/tabs.ts +++ b/test/data/tabs.ts @@ -888,6 +888,1023 @@ export const base = [ hidden: false, }, ]; + +export const vertical = [ + { + "type": "heading_open", + "tag": "h1", + "map": [ + 0, + 1 + ], + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "#", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "inline", + "tag": "", + "map": [ + 0, + 1 + ], + "nesting": 0, + "level": 1, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "Create a folder", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "Create a folder", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "heading_close", + "tag": "h1", + "map": null, + "nesting": -1, + "level": 0, + "children": null, + "content": "", + "markup": "#", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tabs_open", + "tag": "div", + "map": null, + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab_open", + "tag": "div", + "map": [ + 4, + 8 + ], + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "-", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab_open", + "tag": "input", + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "label_open", + "tag": "label", + "map": null, + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + }, + { + "type": "inline", + "tag": "", + "map": null, + "nesting": 0, + "level": 0, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "Python", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + }, + { + "type": "tab_close", + "tag": "div", + "map": null, + "nesting": -1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab-panel_open", + "tag": "div", + "map": null, + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_open", + "tag": "p", + "map": [ + 6, + 7 + ], + "nesting": 1, + "level": 2, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "inline", + "tag": "", + "map": [ + 6, + 7 + ], + "nesting": 0, + "level": 3, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "About python", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "About python", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_close", + "tag": "p", + "map": null, + "nesting": -1, + "level": 2, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab-panel_close", + "tag": "div", + "map": null, + "nesting": -1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab_open", + "tag": "div", + "map": [ + 8, + 12 + ], + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "-", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab_open", + "tag": "input", + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "label_open", + "tag": "label", + "map": null, + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + }, + { + "type": "inline", + "tag": "", + "map": null, + "nesting": 0, + "level": 0, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "Tab with list", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + }, + { + "type": "tab_close", + "tag": "div", + "map": null, + "nesting": -1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab-panel_open", + "tag": "div", + "map": null, + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "bullet_list_open", + "tag": "ul", + "map": [ + 9, + 12 + ], + "nesting": 1, + "level": 2, + "children": null, + "content": "", + "markup": "-", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "list_item_open", + "tag": "li", + "map": [ + 9, + 10 + ], + "nesting": 1, + "level": 3, + "children": null, + "content": "", + "markup": "-", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_open", + "tag": "p", + "map": [ + 9, + 10 + ], + "nesting": 1, + "level": 4, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": true + }, + { + "type": "inline", + "tag": "", + "map": [ + 9, + 10 + ], + "nesting": 0, + "level": 5, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "One", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "One", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_close", + "tag": "p", + "map": null, + "nesting": -1, + "level": 4, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": true + }, + { + "type": "list_item_close", + "tag": "li", + "map": null, + "nesting": -1, + "level": 3, + "children": null, + "content": "", + "markup": "-", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "list_item_open", + "tag": "li", + "map": [ + 10, + 12 + ], + "nesting": 1, + "level": 3, + "children": null, + "content": "", + "markup": "-", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_open", + "tag": "p", + "map": [ + 10, + 11 + ], + "nesting": 1, + "level": 4, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": true + }, + { + "type": "inline", + "tag": "", + "map": [ + 10, + 11 + ], + "nesting": 0, + "level": 5, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "Two", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "Two", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_close", + "tag": "p", + "map": null, + "nesting": -1, + "level": 4, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": true + }, + { + "type": "list_item_close", + "tag": "li", + "map": null, + "nesting": -1, + "level": 3, + "children": null, + "content": "", + "markup": "-", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "bullet_list_close", + "tag": "ul", + "map": null, + "nesting": -1, + "level": 2, + "children": null, + "content": "", + "markup": "-", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab-panel_close", + "tag": "div", + "map": null, + "nesting": -1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab_open", + "tag": "div", + "map": [ + 12, + 16 + ], + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "-", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab_open", + "tag": "input", + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "label_open", + "tag": "label", + "map": null, + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + }, + { + "type": "inline", + "tag": "", + "map": null, + "nesting": 0, + "level": 0, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "Tab with list", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + }, + { + "type": "tab_close", + "tag": "div", + "map": null, + "nesting": -1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab-panel_open", + "tag": "div", + "map": null, + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "ordered_list_open", + "tag": "ol", + "map": [ + 13, + 16 + ], + "nesting": 1, + "level": 2, + "children": null, + "content": "", + "markup": ".", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "list_item_open", + "tag": "li", + "map": [ + 13, + 14 + ], + "nesting": 1, + "level": 3, + "children": null, + "content": "", + "markup": ".", + "info": "1", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_open", + "tag": "p", + "map": [ + 13, + 14 + ], + "nesting": 1, + "level": 4, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": true + }, + { + "type": "inline", + "tag": "", + "map": [ + 13, + 14 + ], + "nesting": 0, + "level": 5, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "One", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "One", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_close", + "tag": "p", + "map": null, + "nesting": -1, + "level": 4, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": true + }, + { + "type": "list_item_close", + "tag": "li", + "map": null, + "nesting": -1, + "level": 3, + "children": null, + "content": "", + "markup": ".", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "list_item_open", + "tag": "li", + "map": [ + 14, + 16 + ], + "nesting": 1, + "level": 3, + "children": null, + "content": "", + "markup": ".", + "info": "2", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_open", + "tag": "p", + "map": [ + 14, + 15 + ], + "nesting": 1, + "level": 4, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": true + }, + { + "type": "inline", + "tag": "", + "map": [ + 14, + 15 + ], + "nesting": 0, + "level": 5, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "Two", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "Two", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_close", + "tag": "p", + "map": null, + "nesting": -1, + "level": 4, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": true + }, + { + "type": "list_item_close", + "tag": "li", + "map": null, + "nesting": -1, + "level": 3, + "children": null, + "content": "", + "markup": ".", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "ordered_list_close", + "tag": "ol", + "map": null, + "nesting": -1, + "level": 2, + "children": null, + "content": "", + "markup": ".", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tab-panel_close", + "tag": "div", + "map": null, + "nesting": -1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "tabs_close", + "tag": "div", + "map": null, + "nesting": -1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_open", + "tag": "p", + "map": [ + 18, + 19 + ], + "nesting": 1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "inline", + "tag": "", + "map": [ + 18, + 19 + ], + "nesting": 0, + "level": 1, + "children": [ + { + "type": "text", + "tag": "", + "attrs": null, + "map": null, + "nesting": 0, + "level": 0, + "children": null, + "content": "After tabs", + "markup": "", + "info": "", + "meta": null, + "block": false, + "hidden": false + } + ], + "content": "After tabs", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + }, + { + "type": "paragraph_close", + "tag": "p", + "map": null, + "nesting": -1, + "level": 0, + "children": null, + "content": "", + "markup": "", + "info": "", + "meta": null, + "block": true, + "hidden": false + } +] + export const escaped = [ { type: 'paragraph_open', diff --git a/test/plugin.test.ts b/test/plugin.test.ts index 6c4e1d3..cb8eb2b 100644 --- a/test/plugin.test.ts +++ b/test/plugin.test.ts @@ -1,6 +1,6 @@ import {PluginOptions, transform} from '../src/plugin/transform'; import {callPlugin, tokenize} from './utils'; -import {base, escaped, nestedTokenTypes} from './data/tabs'; +import {base, escaped, nestedTokenTypes, vertical} from './data/tabs'; // @ts-ignore import Token from 'markdown-it/lib/token'; @@ -26,6 +26,29 @@ const defaultContent = [ 'After tabs', ]; + +const defaultVerticalContent = [ + '# Create a folder', + '', + '{% list tabs vertical %}', + '', + '- Python', + '', + ' About python', + '', + '- Tab with list', + ' - One', + ' - Two', + '', + '- Tab with list', + ' 1. One', + ' 2. Two', + '', + '{% endlist %}', + '', + 'After tabs', +]; + const convertAttrsToObject = ({attrs}: Token) => attrs?.reduce((acc: Record, [name, value]) => { acc[name] = value; @@ -41,6 +64,15 @@ function makeTransform(params?: {transformOptions?: Partial; cont } describe('plugin', () => { + test('should convert vertical tabs to correct new token array', () => { + // ACT + const {tokens: result} = makeTransform({content: defaultVerticalContent}); + + // ASSERT + const clearJSON = JSON.parse(JSON.stringify(result.map(({attrs: _, ...item}) => item))); + expect(clearJSON).toEqual(vertical); + }) + test('Should convert to correct new token array', () => { // ACT const {tokens: result} = makeTransform(); @@ -72,7 +104,7 @@ describe('plugin', () => { const attrsObject = convertAttrsToObject(tab); expect(Object.keys(attrsObject)).toEqual(attrs); - expect(attrsObject['class']).toEqual(`yfm-tab${i === 0 ? ' active' : ''}`); + expect(attrsObject['class']).toEqual(`yfm-tab yfm-tab-group${i === 0 ? ' active' : ''}`); expect(attrsObject['role']).toEqual('tab'); expect(attrsObject['tabindex']).toEqual(i === 0 ? '0' : '-1'); });