Skip to content

Commit

Permalink
feat: support select and accordeon, refactor extension
Browse files Browse the repository at this point in the history
  • Loading branch information
v8tenko committed Oct 15, 2024
1 parent 98e9cd1 commit a4537c3
Show file tree
Hide file tree
Showing 19 changed files with 1,320 additions and 516 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@diplodoc/tabs-extension",
"version": "3.4.0",
"version": "3.5.0-beta1",
"description": "Tabs plugin for Diplodoc transformer and builder",
"main": "build/plugin/index.js",
"types": "build/plugin/index.d.ts",
Expand Down
37 changes: 26 additions & 11 deletions src/common.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
import type {TabsOrientation} from './plugin/transform';
import type {TabsController} from './runtime/TabsController';
import {type TabsController} from './runtime/TabsController';

export const TAB_RE = /`?{% list tabs .*?%}`?/;
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 TABS_LIST_CLASSNAME = 'yfm-tab-list';
export const ACTIVE_CLASSNAME = 'active';
export const VERTICAL_TAB_CLASSNAME = 'yfm-vertical-tab';
export const VERTICAL_TAB_FORCED_OPEN = 'data-diplodoc-radio-forced';

export const TAB_GROUP_CLASSNAME = 'yfm-tab-group';
export const TAB_ACTIVE_KEY = 'data-diplodoc-is-active';
export const GROUP_DATA_KEY = 'data-diplodoc-group';
export const TAB_DATA_KEY = 'data-diplodoc-key';
export const TAB_DATA_VARIANT = 'data-diplodoc-variant';
export const TAB_DATA_ID = 'data-diplodoc-id';
export const DEFAULT_TABS_GROUP_PREFIX = 'defaultTabsGroup-';
export const ACTIVE_TAB_TEXT = '{selected}';
export const TAB_FORCED_OPEN = 'data-diplodoc-forced';

export const TABS_DROPDOWN_CLASSNAME = 'yfm-tabs-dropdown';
export const TABS_DROPDOWN_MENU_CLASSNAME = 'yfm-tabs-dropdown-menu';
export const TABS_DROPDOWN_SELECT = 'yfm-tabs-dropdown-select';

export const TABS_ACCORDION_CLASSNAME = 'yfm-tabs-accordion';
export const TABS_ACCORDION_CLIENT_HEIGHT = 'data-yfm-tabs-accordion-client-heights';

export const TABS_RADIO_CLASSNAME = 'yfm-tabs-vertical';
export const VERTICAL_TAB_CLASSNAME = 'yfm-vertical-tab';
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 const ACTIVE_TAB_TEXT = '{selected}';
export enum TabsVariants {
Regular = 'regular',
Radio = 'radio',
Dropdown = 'dropdown',
Accordion = 'accordion',
}

export interface Tab {
group?: string;
key: string;
align: TabsOrientation;
variant: TabsVariants;
}

export interface SelectedTabEvent {
Expand Down
188 changes: 188 additions & 0 deletions src/plugin/find.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import Token from 'markdown-it/lib/token';

import {DEFAULT_TABS_GROUP_PREFIX, TAB_RE, TabsVariants} from '../common';

import {RuntimeTab, TabsProps} from './types';
import {generateID, trim, unquote} from './utils';

function findCloseTokenIndex(tokens: Token[], idx: number) {
let level = 0;
let i = idx;
while (i < tokens.length) {
if (matchOpenToken(tokens, i)) {
level++;
} else if (matchCloseToken(tokens, i)) {
if (level === 0) {
return i;
}
level--;
}

i++;
}

return null;
}

function matchCloseToken(tokens: Token[], i: number) {
return (
tokens[i].type === 'paragraph_open' &&
tokens[i + 1].type === 'inline' &&
tokens[i + 1].content.trim() === '{% endlist %}'
);
}

function matchOpenToken(tokens: Token[], i: number) {
return (
tokens[i].type === 'paragraph_open' &&
tokens[i + 1].type === 'inline' &&
tokens[i + 1].content.match(TAB_RE)
);
}

export function props(content: string): TabsProps {
const clean = trim(content.replace('list tabs', ''));

const props = clean.split(' ');
const result: TabsProps = {
content: clean,
orientation: TabsVariants.Regular,
group: `${DEFAULT_TABS_GROUP_PREFIX}${generateID()}`,
};

for (const prop of props) {
const [key, value] = prop.split('=').map(trim);

switch (key) {
case 'horizontal':
case 'radio':
case 'dropdown':
case 'accordion':
result.orientation = key as TabsVariants;
break;
case 'group':
result.group = unquote(value);
break;
default:
// TODO: lint unknown tabs props
}
}

return result;
}

type Result =
| {
step: number;
}
| {
content: string;
closeTokenIndex: number;
};

export function tryToFindTabs(tokens: Token[], index: number): Result {
const match = matchOpenToken(tokens, index);
const openTag = match && match[0];
const isNotEscaped = openTag && !(openTag.startsWith('`') && openTag.endsWith('`'));

if (!match || !isNotEscaped) {
return {
step: 1,
};
}

const closeTokenIndex = findCloseTokenIndex(tokens, index + 3);

if (!closeTokenIndex) {
tokens[index].attrSet('YFM005', 'true');

return {
step: 3,
};
}

return {
content: openTag,
closeTokenIndex,
};
}

export function findTabs(tokens: Token[], idx: number, closeTokenIdx: number) {
const tabs = [];
let i = idx;
let nestedLevel = -1;
let pending: RuntimeTab = {
name: '',
tokens: [],
listItem: new Token('list_item_open', '', 0),
};

while (i < tokens.length) {
const token = tokens[i];

switch (token.type) {
case 'ordered_list_open':
case 'bullet_list_open':
if (nestedLevel > -1) {
pending.tokens.push(token);
}

nestedLevel++;

break;
case 'list_item_open':
if (nestedLevel) {
pending.tokens.push(token);
} else {
pending = {name: '', tokens: [], listItem: token};
}

break;
case 'list_item_close':
if (nestedLevel) {
pending.tokens.push(token);
} else {
tabs.push(pending);
}

break;
case 'ordered_list_close':
case 'bullet_list_close':
if (!nestedLevel) {
return tabs;
}

nestedLevel--;

pending.tokens.push(token);

break;
case 'paragraph_open':
if (
i === closeTokenIdx &&
tokens[i + 1].content &&
tokens[i + 1].content.trim() === '{% endlist %}'
) {
if (pending && !nestedLevel) {
tabs.push(pending);
}

return tabs;
}

if (!pending.name && tokens[i + 1].type === 'inline') {
pending.name = tokens[i + 1].content;

i += 2;
} else {
pending.tokens.push(token);
}
break;
default:
pending.tokens.push(token);
}
i++;
}

return tabs;
}
15 changes: 15 additions & 0 deletions src/plugin/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type StateCore from 'markdown-it/lib/rules_core/state_core';

import {RuntimeTab} from './types';
import {generateTokensByType} from './variants';
import {TabsGenerationProps} from './variants/types';

export function generateTabsTokens(
tabs: RuntimeTab[],
state: StateCore,
props: TabsGenerationProps,
) {
const tokens = generateTokensByType(props.orientation)(tabs, state, props);

return tokens;
}
Loading

0 comments on commit a4537c3

Please sign in to comment.