Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | 113x 112x 144x 144x 230x 190x 15x 190x 190x 113x 113x 144x 113x 124x 124x 113x 32x 32x 32x 32x 176x 176x 64x 64x 112x 112x 112x 112x 16x 16x 32x 96x 24x | import * as React from "react"; /** * Check if the first prop of a */ const isRawMd = (childProps: { mdxType?: string; children?: { props?: { className?: string } }; }): boolean => childProps.mdxType === "pre" && childProps.children?.props?.className === "language-md"; /** Rendered child, with props */ export type RenderedChild = any; /** mdx component as it exists after importing via `import someMdx from "./some.mdx"` */ export type Mdx = (props: unknown) => RenderedChild; /** Object gathered for each header */ export type ParsedMdx = { /** Full content of this segment of markdown including the header */ full: RenderedChild[]; /** Content of this segment of markdown excluding header */ body: RenderedChild[]; /** Raw string extracted from 'md' code blocks */ md: string; }; /** Function returned for each header, with properties assigned for more specific use cases */ export type MdxSegment = { /** Function which is useful passed to parameters.docs.page directly */ (): RenderedChild[]; } & ParsedMdx; /** Header in kebab case as key to object of markdown segments */ export type HeaderKeyedMdxSegment = { [id: string]: MdxSegment }; /** * quick and dirty fifo * @private */ export class Fifo<T, U> { limit: number; _cache: Map<T, U>; // TODO: babel was getting upset with `private` keyword constructor(limit = 50, _cache = new Map<T, U>()) { this.limit = limit; this._cache = _cache; } get(key: T) { return this._cache.get(key); } set(key: T, val: U) { if (this._cache.size === this.limit) { this._cache.delete(this._cache.keys().next().value); } this._cache.set(key, val); return val; } } const cache = new Fifo<Mdx, HeaderKeyedMdxSegment>(); /** * simple kebab-case converter for space separated text, * returns undefined if str is undefined or null * @private */ export const safeKebabCase = (str?: string | null) => typeof str === "string" ? str.toLowerCase().replace(/ /g, "-") : null; /** * Split up an MDX files into headers for easy use in multiple parts of documentation * or in multiple files, with some added perks. * * Currently, this breaks on any header such that a file like * ~~~md * # First Component * * Something * * ## Second Component * * ```md * # Second header description * This second component does stuff * ``` * ~~~ * becomes essentially * ```ts * { * "first-component": { * full: [<h1>First Component</h1>,<p>Something</p>], * body: [<p>Something</p>], * md: "", * }, * "second-component": { * full: [<h1>Second Component</h1>,<p>Other</p>], * body: [<code>....</code>], * md: "# Second header description\nThis second component does stuff", * }, * } * ``` * Although actually they'll be functions at those locations that also have those properties, * but is `() => full` at invocation. Note how it picks up md code blocks as raw text, suitable * for story descriptions. * * * Then you can use it like * ```ts * import mdx from "./some.mdx"; * const mdxObject = segmentMdx(mdx); * // define FirstComponent... * FirstComponent.parameters = { * docs: { * page: mdxObject['first-component'], * } * }; * // define SecondComponent... * SecondComponent.parameters = { * docs: { * story: { * description: mdxObject['second-component'].md, * } * } * }; * ``` * * And if you needed to combine them you could do something like * ```ts * docs: { * page: () => [ * ...mdxObject["first-component"].full, * ...mdxObject["second-component"].full, * ] * } * ``` * * Or, in an mdx file like so (real example): * ```md * import { Meta } from "@storybook/addon-docs"; * import readme from "../../README.md"; * import { segmentMdx } from "orphic-cypress"; * * <Meta title="MockRequests/Overview" /> * * <>{segmentMdx(readme)["intercepting-api-requests"].full}</> * * <-- more markdown --> * # Further afield * ``` * * Uses a dead simple FIFO cache of size 50 just to avoid thinking about memory consumption issues. */ export const segmentMdx = ( mdx: Mdx, /** force skipping the cache */ force?: boolean ): HeaderKeyedMdxSegment => { const fromCache = !force && cache.get(mdx); if (fromCache) return fromCache; if (typeof mdx !== "function") return cache.set(mdx, {}); const rendered = mdx({}); let currentId = "file"; const collection: { [id: string]: ParsedMdx } = { file: { full: [], body: [], md: "" }, }; React.Children.forEach(rendered.props.children, (child) => { const childrenOfChild = child.props.children; if (/^h\d$/.test(child.props.mdxType)) { // not sure why exactly the id is sometimes already present currentId = child.props.id || safeKebabCase(childrenOfChild) || "unknown"; collection[currentId] = { full: [child], body: [], md: "" }; } else if (collection[currentId]) { collection[currentId].full.push(child); collection[currentId].body.push(child); if (isRawMd(child.props)) { const rawMd = childrenOfChild.props.children; collection[currentId].md += rawMd; } } }); return cache.set( mdx, Object.fromEntries( Object.entries(collection).map(([k, v]): [string, MdxSegment] => [ k, Object.assign(() => v.full, v), ]) ) ); }; |