Files
supabase/apps/docs/lib/mdx/plugins/remarkTabs.ts
Charis cf3ecc93eb chore(docs): turn on strictNullChecks (#36180)
strictNullChecks was off for docs, which lets errors slip through and
leads to incorrect required/optional typing on Zod-inferred types. This
PR enables strictNullChecks and fixes all the existing violations.
2025-06-04 17:05:37 -04:00

142 lines
4.2 KiB
TypeScript

import type { Content, Paragraph, Root } from 'mdast'
import type { MdxJsxFlowElement } from 'mdast-util-mdx'
import type { Node } from 'unist'
import { visit } from 'unist-util-visit'
/**
* Transforms PyMdown Tabs to Supabase Tabs.
*
* https://facelessuser.github.io/pymdown-extensions/extensions/tabbed/
*/
const remarkPyMdownTabs = function () {
return function transformer(root: Root) {
visit(root, 'paragraph', (node: Paragraph, nodeIndex: number, parent: Root) => {
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i]
if (child.type !== 'text') {
continue
}
// Look for 3 '=', followed by an optionally quoted title,
// followed by optional newlines of text
const match = child.value.match(/^\n*=== ("?)(.+)\1\n?((?:.|\n)*)/)
if (!match) {
continue
}
// Extract the tab title along with the remaining text
const [, , title, value] = match
// Rewrite the node's value to remove the tab syntax
child.value = value
// Extract sibling nodes that should be linked to this tab
const siblingsToNest = extractLinkedSiblings(parent, node, nodeIndex)
const children: any[] = [...node.children.slice(i), ...siblingsToNest]
const tabPanelElement: MdxJsxFlowElement = {
type: 'mdxJsxFlowElement',
name: 'TabPanel',
attributes: [
{
type: 'mdxJsxAttribute',
name: 'id',
value: title,
},
{
type: 'mdxJsxAttribute',
name: 'label',
value: title,
},
],
children,
}
let nodesAdded = 0
const previousNode = parent.children[nodeIndex - 1]
if (previousNode?.type === 'mdxJsxFlowElement' && previousNode.name === 'Tabs') {
// Add TabPanel to existing Tabs component
previousNode.children.push(tabPanelElement)
// Remove this node
parent.children.splice(nodeIndex, 1)
nodesAdded--
} else {
// Create new Tabs components and add TabPanel
const tabsElement: MdxJsxFlowElement = {
type: 'mdxJsxFlowElement',
name: 'Tabs',
attributes: [
{
type: 'mdxJsxAttribute',
name: 'scrollable',
},
{
type: 'mdxJsxAttribute',
name: 'size',
value: 'small',
},
{
type: 'mdxJsxAttribute',
name: 'type',
value: 'underlined',
},
],
children: [tabPanelElement],
}
// Overwrite this paragraph node with Tabs component
parent.children.splice(nodeIndex, 1, tabsElement)
}
// If this wasn't the first child of the paragraph, create
// a new paragraph before this with the previous text children
if (i > 0) {
const previousChildren = node.children.slice(0, i)
const paragraph: Paragraph = {
type: 'paragraph',
children: previousChildren,
}
parent.children.splice(nodeIndex, 0, paragraph)
nodesAdded++
}
// Return the correct index for the next visit, since
// we may have added or removed an element in the array
return nodeIndex + nodesAdded
}
})
return root
}
}
/**
* Identifies sibling nodes that should be linked to this admonition
* based on their indent level (ie. 4 spaces).
*
* Iterates through proceeding siblings until one is found that is
* not indented relative to the original node.
*
* Splices the discovered siblings out of the original parent and returns them.
*/
function extractLinkedSiblings(parent: Root, node: Node, index: number, indentAmount = 4) {
if (!node.position) return []
const { column } = node.position.start
let nextSibling: Content
let i = index
do {
nextSibling = parent.children[++i]
} while (nextSibling?.position && nextSibling.position.start.column === column + indentAmount)
return parent.children.splice(index + 1, i - index - 1)
}
export default remarkPyMdownTabs