Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add dropdown mode #67

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading