Skip to content

Commit

Permalink
feat(md-enhance): add event id support for codetabs
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed May 20, 2022
1 parent e51eac9 commit e61275f
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 28 deletions.
@@ -1,73 +1,97 @@
// Vitest Snapshot v1

exports[`code tabs > shoud render mutiple block 1`] = `
"<CodeTabs :active=\\"0\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]'>
</CodeTabs>
"
`;
exports[`code tabs > shoud render mutiple block 2`] = `
"<CodeTabs :active=\\"0\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]'>
</CodeTabs>
"
`;
exports[`code tabs > shoud render single block 1`] = `
"<CodeTabs :active=\\"0\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]'>
</CodeTabs>
"
`;
exports[`code tabs > shoud render single block 2`] = `
"<CodeTabs :active=\\"0\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]'>
</CodeTabs>
"
`;
exports[`code tabs > shoud support active 1`] = `
"<CodeTabs :active=\\"0\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]' :active=\\"0\\">
</CodeTabs>
"
`;
exports[`code tabs > shoud support active 2`] = `
"<CodeTabs :active=\\"0\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]' :active=\\"0\\">
</CodeTabs>
"
`;
exports[`code tabs > shoud support active 3`] = `
"<CodeTabs :active=\\"1\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]' :active=\\"1\\">
</CodeTabs>
"
`;
exports[`code tabs > shoud support active 4`] = `
"<CodeTabs :active=\\"1\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]' :active=\\"1\\">
</CodeTabs>
"
`;
exports[`code tabs > shoud support id 1`] = `
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]' code-id=\\"event\\">
</CodeTabs>
"
`;
exports[`code tabs > shoud support id 2`] = `
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]' code-id=\\"event-id\\">
</CodeTabs>
"
`;
exports[`code tabs > shoud support id 3`] = `
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]' code-id=\\"id with space\\">
</CodeTabs>
"
`;
exports[`code tabs > shoud support id 4`] = `
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]' code-id=\\"id starts and having space in the end\\">
</CodeTabs>
"
`;
exports[`code tabs > should ignore other items 1`] = `
"<CodeTabs :active=\\"0\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]' :active=\\"0\\">
</CodeTabs>
"
`;
exports[`code tabs > should ignore other items 2`] = `
"<CodeTabs :active=\\"0\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"}]' :active=\\"0\\">
</CodeTabs>
"
`;
exports[`code tabs > should ignore other items 3`] = `
"<CodeTabs :active=\\"1\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]' :active=\\"1\\">
</CodeTabs>
"
`;
exports[`code tabs > should ignore other items 4`] = `
"<CodeTabs :active=\\"1\\" :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]'>
"<CodeTabs :data='[{\\"code\\":\\"<pre><code class=\\\\\\"language-js\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"js\\"},{\\"code\\":\\"<pre><code class=\\\\\\"language-ts\\\\\\">const a = 1;\\\\n</code></pre>\\\\n\\",\\"title\\":\\"ts\\"}]' :active=\\"1\\">
</CodeTabs>
"
`;
49 changes: 49 additions & 0 deletions packages/md-enhance/__tests__/unit/codeTabs.spec.ts
Expand Up @@ -69,6 +69,55 @@ const a = 1;
).toMatchSnapshot();
});

it("shoud support id", () => {
expect(
markdownIt.render(`
::: code-tabs#event
@tab js
\`\`\`js
const a = 1;
\`\`\`
:::
`)
).toMatchSnapshot();

expect(
markdownIt.render(`
::: code-tabs#event-id
@tab js
\`\`\`js
const a = 1;
\`\`\`
:::
`)
).toMatchSnapshot();

expect(
markdownIt.render(`
::: code-tabs#id with space
@tab js
\`\`\`js
const a = 1;
\`\`\`
:::
`)
).toMatchSnapshot();

expect(
markdownIt.render(`
::: code-tabs # id starts and having space in the end
@tab js
\`\`\`js
const a = 1;
\`\`\`
:::
`)
).toMatchSnapshot();
});

it("shoud support active", () => {
expect(
markdownIt.render(`
Expand Down
3 changes: 1 addition & 2 deletions packages/md-enhance/__tests__/unit/demo.spec.ts
Expand Up @@ -110,8 +110,7 @@ h1 {
\`\`\`
:::
`,
{}
`
)
).toMatchSnapshot();
});
Expand Down
47 changes: 39 additions & 8 deletions packages/md-enhance/src/client/components/CodeTabs.ts
@@ -1,32 +1,49 @@
import { defineComponent, h, ref } from "vue";
import { useStorage } from "@vueuse/core";
import { defineComponent, h, ref, watch } from "vue";
import type { PropType, VNode } from "vue";

import "../styles/code-tabs.scss";

const codeTabStore = useStorage<Record<string, string>>(
"VUEPRESS_CODE_TAB_STORE",
{}
);

export default defineComponent({
name: "CodeTabs",

props: {
active: { type: Number, default: 0 },
// active: { type: Number, required: true },
data: {
type: Array as PropType<
{
code: string;
title: string;
}[]
>,
default: () => [],
required: true,
},
codeId: {
type: String,
default: "",
},
// data: { type: Array as PropType<CodeData[]>, required: true },
},

setup(props) {
const getInitialIndex = (): number => {
if (props.codeId) {
const valueIndex = props.data.findIndex(
({ title }) => codeTabStore.value[props.codeId] === title
);

if (valueIndex !== -1) return valueIndex;
}

return props.active;
};

// index of current active item
const activeIndex = ref(
// initialized by props
props.active
);
const activeIndex = ref(getInitialIndex());

// refs of the tab buttons
const tabRefs = ref<HTMLUListElement[]>([]);
Expand Down Expand Up @@ -55,8 +72,22 @@ export default defineComponent({
event.preventDefault();
activatePrev();
}

if (props.codeId)
codeTabStore.value[props.codeId] = props.data[activeIndex.value].title;
};

watch(
() => codeTabStore.value[props.codeId],
(newValue, oldValue) => {
if (props.codeId && newValue !== oldValue) {
const index = props.data.findIndex(({ title }) => title === newValue);

if (index !== -1) activeIndex.value = index;
}
}
);

return (): VNode | null => {
return props.data.length
? h("div", { class: "code-tabs" }, [
Expand Down
22 changes: 16 additions & 6 deletions packages/md-enhance/src/node/markdown-it/codeTabs.ts
Expand Up @@ -27,7 +27,9 @@ export const codeTabs: PluginSimple = (md) => {
const markup = state.src.slice(start, pos);
const params = state.src.slice(pos, max);

if (params.trim() !== "code-tabs") return false;
const [name, id = ""] = params.split("#", 2);

if (name.trim() !== "code-tabs") return false;

// Since start is found, we can report success here in validation mode
if (silent) return true;
Expand Down Expand Up @@ -91,7 +93,8 @@ export const codeTabs: PluginSimple = (md) => {

openToken.markup = markup;
openToken.block = true;
openToken.info = params;
openToken.info = name;
openToken.meta = { id: id.trim() };
openToken.map = [startLine, nextLine - (autoClosed ? 1 : 0)];

state.md.block.tokenize(
Expand Down Expand Up @@ -227,10 +230,13 @@ export const codeTabs: PluginSimple = (md) => {
env,
self
): string => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const { meta } = tokens[index];

let inCodeTab = false;
let foundFence = false;
let codeTabIndex = -1;
let activeIndex = 0;
let activeIndex = -1;
let title = "";
const codeTabsData: { code: string; title: string }[] = [];

Expand Down Expand Up @@ -275,9 +281,13 @@ export const codeTabs: PluginSimple = (md) => {
}
}

return `<CodeTabs :active="${activeIndex}" :data='${JSON.stringify(
codeTabsData
).replace(/'/g, "&#39")}'>\n`;
return `<CodeTabs :data='${
// single quote will break @vue/compiler-sfc
JSON.stringify(codeTabsData).replace(/'/g, "&#39")
}'${activeIndex !== -1 ? ` :active="${activeIndex}"` : ""}${
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
meta.id ? ` code-id="${meta.id as string}"` : ""
}>\n`;
};

md.renderer.rules.code_tabs_close = (): string => "</CodeTabs>\n";
Expand Down

0 comments on commit e61275f

Please sign in to comment.