feat: tabs can now contain custom IDs

This commit is contained in:
Corbin Crutchley
2022-03-10 21:07:59 -08:00
parent 7131b13012
commit 3d77ed0933
8 changed files with 121 additions and 76 deletions

View File

@@ -26,7 +26,7 @@ module.exports = {
},
transformIgnorePatterns: [
// ...your ignore patterns
"^((?!node_modules).)*node_modules.((?!unified|unist|hast|remark|mdast|micromark|retext|nlcst|rehype|decode-named-character-reference|character-entities|zwitch|longest-streak|unherit|parse-|strip-|html-void-elements|stringify-entities|ccount|markdown-|slash|vfile|property-|space-separated-|comma-separated-|web-namespaces|react-children-utilities|junk).)*$",
"^((?!node_modules).)*node_modules.((?!unified|unist|hast|remark|mdast|micromark|retext|nlcst|rehype|decode-named-character-reference|character-entities|zwitch|longest-streak|unherit|parse-|strip-|html-void-elements|stringify-entities|ccount|markdown-|slash|vfile|property-|space-separated-|comma-separated-|web-namespaces|junk).)*$",
"^.+\\.module\\.(css|sass|scss)$",
],
// moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths),

67
package-lock.json generated
View File

@@ -12,6 +12,8 @@
"dependencies": {
"@remark-embedder/core": "^2.0.0",
"@remark-embedder/transformer-oembed": "^2.0.0",
"@types/github-slugger": "^1.3.0",
"@types/lodash": "^4.14.179",
"batteries-not-included": "^0.1.0",
"classnames": "2.3.1",
"copy-webpack-plugin": "^9.0.1",
@@ -20,12 +22,13 @@
"disqus-react": "^1.1.2",
"framer-motion": "^4.1.17",
"gatsby-remark-embedder": "^5.0.0",
"github-slugger": "^1.4.0",
"gray-matter": "4.0.3",
"junk": "^4.0.0",
"lodash": "^4.17.21",
"lunr": "^2.3.9",
"next": "^12.1.0",
"react": "^17.0.2",
"react-children-utilities": "^2.6.3",
"react-dom": "^17.0.2",
"react-medium-image-zoom": "^4.3.5",
"react-paginate": "^8.0.3",
@@ -33,7 +36,7 @@
"rehype-img-size": "github:unicorn-utterances/rehype-img-size#relative-path-full",
"rehype-parse": "^8.0.3",
"rehype-react": "^7.0.3",
"rehype-slug-custom-id": "^1.0.0",
"rehype-slug-custom-id": "^1.1.0",
"rehype-stringify": "^9.0.2",
"remark-behead": "^2.3.3",
"remark-gfm": "^3.0.1",
@@ -86,7 +89,7 @@
"lint-staged": "^12.1.2",
"next-compose-plugins": "^2.2.1",
"next-sitemap": "^1.6.237",
"prettier": "^2.5.0",
"prettier": "^2.5.1",
"react-test-renderer": "^17.0.2",
"ts-jest": "^27.1.1"
}
@@ -4014,6 +4017,11 @@
"@types/ms": "*"
}
},
"node_modules/@types/github-slugger": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz",
"integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g=="
},
"node_modules/@types/graceful-fs": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
@@ -4087,6 +4095,11 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true
},
"node_modules/@types/lodash": {
"version": "4.14.179",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz",
"integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w=="
},
"node_modules/@types/lunr": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.4.tgz",
@@ -13262,9 +13275,9 @@
}
},
"node_modules/prettier": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz",
"integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
"integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
@@ -13461,14 +13474,6 @@
"node": ">=0.10.0"
}
},
"node_modules/react-children-utilities": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/react-children-utilities/-/react-children-utilities-2.6.3.tgz",
"integrity": "sha512-ncP/JU26u/V8PyORIUzR2+yMb8YSGKLMbaqo9xwvQlNUAXMyUk2mja4torT7HKvGAzknMPR9Qnl2U6c7KD4kZw==",
"peerDependencies": {
"react": "17.x.x || 16.x.x || 15.x.x"
}
},
"node_modules/react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -13836,9 +13841,9 @@
}
},
"node_modules/rehype-slug-custom-id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/rehype-slug-custom-id/-/rehype-slug-custom-id-1.0.0.tgz",
"integrity": "sha512-gt8KAAXLtH3Fd8zZrQZx8DZogLZImrHc0jjwQQoGYtXV8cnb5O2veSQ3ArwdgAFzzM9fC+bycWR8Qw39Z14NHA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/rehype-slug-custom-id/-/rehype-slug-custom-id-1.1.0.tgz",
"integrity": "sha512-lLdTHGd7u5bOXRDnD78/VHrVZsG63nN6lZUuQgcuupGt1+v+uDW7pCeQS0cvptRcZPYzg8B+0bf8sUiMBKsjZw==",
"dependencies": {
"@types/hast": "^2.0.0",
"github-slugger": "^1.1.1",
@@ -19024,6 +19029,11 @@
"@types/ms": "*"
}
},
"@types/github-slugger": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@types/github-slugger/-/github-slugger-1.3.0.tgz",
"integrity": "sha512-J/rMZa7RqiH/rT29TEVZO4nBoDP9XJOjnbbIofg7GQKs4JIduEO3WLpte+6WeUz/TcrXKlY+bM7FYrp8yFB+3g=="
},
"@types/graceful-fs": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz",
@@ -19097,6 +19107,11 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true
},
"@types/lodash": {
"version": "4.14.179",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.179.tgz",
"integrity": "sha512-uwc1x90yCKqGcIOAT6DwOSuxnrAbpkdPsUOZtwrXb4D/6wZs+6qG7QnIawDuZWg0sWpxl+ltIKCaLoMlna678w=="
},
"@types/lunr": {
"version": "2.3.4",
"resolved": "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.4.tgz",
@@ -25854,9 +25869,9 @@
"dev": true
},
"prettier": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.0.tgz",
"integrity": "sha512-FM/zAKgWTxj40rH03VxzIPdXmj39SwSjwG0heUcNFwI+EMZJnY93yAiKXM3dObIKAM5TA88werc8T/EwhB45eg==",
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
"integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
"dev": true
},
"prettier-linter-helpers": {
@@ -26006,12 +26021,6 @@
"object-assign": "^4.1.1"
}
},
"react-children-utilities": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/react-children-utilities/-/react-children-utilities-2.6.3.tgz",
"integrity": "sha512-ncP/JU26u/V8PyORIUzR2+yMb8YSGKLMbaqo9xwvQlNUAXMyUk2mja4torT7HKvGAzknMPR9Qnl2U6c7KD4kZw==",
"requires": {}
},
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
@@ -26294,9 +26303,9 @@
}
},
"rehype-slug-custom-id": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/rehype-slug-custom-id/-/rehype-slug-custom-id-1.0.0.tgz",
"integrity": "sha512-gt8KAAXLtH3Fd8zZrQZx8DZogLZImrHc0jjwQQoGYtXV8cnb5O2veSQ3ArwdgAFzzM9fC+bycWR8Qw39Z14NHA==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/rehype-slug-custom-id/-/rehype-slug-custom-id-1.1.0.tgz",
"integrity": "sha512-lLdTHGd7u5bOXRDnD78/VHrVZsG63nN6lZUuQgcuupGt1+v+uDW7pCeQS0cvptRcZPYzg8B+0bf8sUiMBKsjZw==",
"requires": {
"@types/hast": "^2.0.0",
"github-slugger": "^1.1.1",

View File

@@ -34,6 +34,8 @@
"dependencies": {
"@remark-embedder/core": "^2.0.0",
"@remark-embedder/transformer-oembed": "^2.0.0",
"@types/github-slugger": "^1.3.0",
"@types/lodash": "^4.14.179",
"batteries-not-included": "^0.1.0",
"classnames": "2.3.1",
"copy-webpack-plugin": "^9.0.1",
@@ -42,12 +44,13 @@
"disqus-react": "^1.1.2",
"framer-motion": "^4.1.17",
"gatsby-remark-embedder": "^5.0.0",
"github-slugger": "^1.4.0",
"gray-matter": "4.0.3",
"junk": "^4.0.0",
"lodash": "^4.17.21",
"lunr": "^2.3.9",
"next": "^12.1.0",
"react": "^17.0.2",
"react-children-utilities": "^2.6.3",
"react-dom": "^17.0.2",
"react-medium-image-zoom": "^4.3.5",
"react-paginate": "^8.0.3",
@@ -55,7 +58,7 @@
"rehype-img-size": "github:unicorn-utterances/rehype-img-size#relative-path-full",
"rehype-parse": "^8.0.3",
"rehype-react": "^7.0.3",
"rehype-slug-custom-id": "^1.0.0",
"rehype-slug-custom-id": "^1.1.0",
"rehype-stringify": "^9.0.2",
"remark-behead": "^2.3.3",
"remark-gfm": "^3.0.1",
@@ -108,7 +111,7 @@
"lint-staged": "^12.1.2",
"next-compose-plugins": "^2.2.1",
"next-sitemap": "^1.6.237",
"prettier": "^2.5.0",
"prettier": "^2.5.1",
"react-test-renderer": "^17.0.2",
"ts-jest": "^27.1.1"
}

View File

@@ -7,7 +7,6 @@ import {
TabPanel as ReactTabPanel,
} from "react-tabs";
import { ReactElement, useCallback } from "react";
import { onlyText } from "react-children-utilities";
import { useIsomorphicLayoutEffect } from "react-use";
import { MarkdownDataContext } from "utils/markdown/MarkdownRenderer/data-context";
@@ -27,8 +26,7 @@ const Tabs: React.FC = ({ children }) => {
return maybeTabComp?.type === ReactTab;
})
.map((tabComp: ReactElement) => {
// Contents of tab header
return onlyText(tabComp.props.children);
return tabComp.props["data-tabname"];
});
return tabTextArr as string[];
}, [children]);

View File

@@ -14,7 +14,7 @@ import { parent } from "constants/site-config";
import { rehypeHeaderText } from "./plugins/add-header-text";
import remarkTwoslash from "remark-shiki-twoslash";
import { UserConfigSettings } from "shiki-twoslash";
import { rehypeTabs } from "utils/markdown/plugins/tabs";
import { rehypeTabs, RehypeTabsProps } from "utils/markdown/plugins/tabs";
import { PluggableList } from "unified";
// Optional now. Probably should move to an array that's passed or something
@@ -76,7 +76,15 @@ export default async function markdownToHtml(
dir: imgDirectory,
},
],
[
rehypeTabs,
{
injectSubheaderProps: true,
tabSlugifyProps: {
enableCustomId: true,
},
} as RehypeTabsProps,
],
[
rehypeSlug,
{

View File

@@ -10,7 +10,7 @@ test("headers are tabified", (done) => {
.use(remarkParse)
.use(remarkStringify)
.use(remarkToRehype, { allowDangerousHtml: true })
.use(rehypeTabs)
.use(rehypeTabs, {})
.use(rehypeStringify, { allowDangerousHtml: true })
.process(
`
@@ -33,11 +33,11 @@ Bonjour!
function (err, file) {
expect(err).toBeNull();
expect(file?.value).toMatchInlineSnapshot(`
"<tabs><tab-list><tab>English</tab><tab>French</tab><tab>Italian</tab></tab-list><tab-panel>
"<tabs><tab-list><tab data-tabname=\\"english\\">English</tab><tab data-tabname=\\"french\\">French</tab><tab data-tabname=\\"italian\\">Italian</tab></tab-list><tab-panel data-tabname=\\"english\\">
<p>Hello!</p>
</tab-panel><tab-panel>
</tab-panel><tab-panel data-tabname=\\"french\\">
<p>Bonjour!</p>
</tab-panel><tab-panel>
</tab-panel><tab-panel data-tabname=\\"italian\\">
<p>Bonjour!</p>
<!-- tabs:end --></tab-panel></tabs>"
`);
@@ -51,7 +51,7 @@ test("sub-headers are not tabified", (done) => {
.use(remarkParse)
.use(remarkStringify)
.use(remarkToRehype, { allowDangerousHtml: true })
.use(rehypeTabs)
.use(rehypeTabs, {})
.use(rehypeStringify, { allowDangerousHtml: true })
.process(
`
@@ -78,13 +78,13 @@ Bonjour!
function (err, file) {
expect(err).toBeNull();
expect(file?.value).toMatchInlineSnapshot(`
"<tabs><tab-list><tab>English</tab><tab>French</tab><tab>Italian</tab></tab-list><tab-panel>
"<tabs><tab-list><tab data-tabname=\\"english\\">English</tab><tab data-tabname=\\"french\\">French</tab><tab data-tabname=\\"italian\\">Italian</tab></tab-list><tab-panel data-tabname=\\"english\\">
<p>Hello!</p>
<h5>A note about English</h5>
<p>Something!</p>
</tab-panel><tab-panel>
</tab-panel><tab-panel data-tabname=\\"french\\">
<p>Bonjour!</p>
</tab-panel><tab-panel>
</tab-panel><tab-panel data-tabname=\\"italian\\">
<p>Bonjour!</p>
<!-- tabs:end --></tab-panel></tabs>"
`);

View File

@@ -1,6 +1,8 @@
import { Root } from "hast";
import replaceAllBetween from "unist-util-replace-all-between";
import { Node } from "unist";
import { Plugin } from "unified";
import { getHeaderNodeId, slugs } from "rehype-slug-custom-id";
type ElementNode = Node & HTMLElement;
@@ -20,6 +22,11 @@ const findLargestHeading = (nodes: ElementNode[]) => {
const isNodeLargestHeading = (n: ElementNode, largestSize: number) =>
isNodeHeading(n) && parseInt(n.tagName.substr(1), 10) === largestSize;
export interface RehypeTabsProps {
injectSubheaderProps?: boolean;
tabSlugifyProps?: Parameters<typeof getHeaderNodeId>[1];
}
/**
* Plugin to add Docsify's tab support.
* @see https://jhildenbiddle.github.io/docsify-tabs/
@@ -37,8 +44,11 @@ const isNodeLargestHeading = (n: ElementNode, largestSize: number) =>
* To align with React Tabs package:
* @see https://github.com/reactjs/react-tabs
*/
export const rehypeTabs = () => {
return (tree: Root) => {
export const rehypeTabs: Plugin<[RehypeTabsProps | never], Root> = ({
injectSubheaderProps = false,
tabSlugifyProps = {},
}) => {
return (tree) => {
replaceAllBetween(
tree,
{ type: "raw", value: "<!-- tabs:start -->" } as never,
@@ -70,16 +80,29 @@ export const rehypeTabs = () => {
sectionStarted = true;
if (isNodeLargestHeading(localNode, largestSize)) {
// Make sure that all tabs labeled "thing" aren't also labeled "thing2"
slugs.reset();
const { id: headerSlug } = getHeaderNodeId(
localNode,
tabSlugifyProps
);
const header = {
type: "element",
tagName: "tab",
children: localNode.children,
properties: {
"data-tabname": headerSlug,
},
};
const contents = {
type: "element",
tagName: "tab-panel",
children: [],
properties: {
"data-tabname": headerSlug,
},
};
tabsContainer.children[0].children.push(header);
@@ -88,6 +111,10 @@ export const rehypeTabs = () => {
continue;
}
if (isNodeHeading(localNode) && injectSubheaderProps) {
// localNode
}
// Push into last `tab-panel`
tabsContainer.children[
tabsContainer.children.length - 1

View File

@@ -17,11 +17,11 @@ test("tabs should render", () => {
markdownHTML: `
<tabs>
<tab-list>
<tab>Header</tab>
<tab>Header2</tab>
<tab data-tabname="header">Header</tab>
<tab data-tabname="header2">Header2</tab>
</tab-list>
<tab-panel>Hello</tab-panel>
<tab-panel>Goodbye</tab-panel>
<tab-panel data-tabname="header">Hello</tab-panel>
<tab-panel data-tabname="header2">Goodbye</tab-panel>
</tabs>
`,
}}
@@ -45,11 +45,11 @@ test("tabs should persist", () => {
markdownHTML: `
<tabs>
<tab-list>
<tab>Header</tab>
<tab>Header2</tab>
<tab data-tabname="header">Header</tab>
<tab data-tabname="header2">Header2</tab>
</tab-list>
<tab-panel>Hello</tab-panel>
<tab-panel>Goodbye</tab-panel>
<tab-panel data-tabname="header">Hello</tab-panel>
<tab-panel data-tabname="header2">Goodbye</tab-panel>
</tabs>
`,
}}
@@ -76,20 +76,20 @@ test("tabs should sync values", () => {
markdownHTML: `
<tabs>
<tab-list>
<tab>Header</tab>
<tab>Header2</tab>
<tab data-tabname="header">Header</tab>
<tab data-tabname="header2">Header2</tab>
</tab-list>
<tab-panel>Hello</tab-panel>
<tab-panel>Goodbye</tab-panel>
<tab-panel data-tabname="header">Hello</tab-panel>
<tab-panel data-tabname="header2">Goodbye</tab-panel>
</tabs>
<tabs>
<tab-list>
<tab>Header</tab>
<tab>Header2</tab>
<tab data-tabname="header">Header</tab>
<tab data-tabname="header2">Header2</tab>
</tab-list>
<tab-panel>Hello2</tab-panel>
<tab-panel>Goodbye2</tab-panel>
<tab-panel data-tabname="header">Hello2</tab-panel>
<tab-panel data-tabname="header2">Goodbye2</tab-panel>
</tabs>
`,
}}