diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 01:14:29 +0000 |
commit | fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8 (patch) | |
tree | 4c1ccaf5486d4f2009f9a338a98a83e886e29c97 /browser/components/storybook/.storybook/markdown-story-utils.js | |
parent | Releasing progress-linux version 124.0.1-1~progress7.99u1. (diff) | |
download | firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.tar.xz firefox-fbaf0bb26397aa498eb9156f06d5a6fe34dd7dd8.zip |
Merging upstream version 125.0.1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'browser/components/storybook/.storybook/markdown-story-utils.js')
-rw-r--r-- | browser/components/storybook/.storybook/markdown-story-utils.js | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/browser/components/storybook/.storybook/markdown-story-utils.js b/browser/components/storybook/.storybook/markdown-story-utils.js new file mode 100644 index 0000000000..1cc78164ad --- /dev/null +++ b/browser/components/storybook/.storybook/markdown-story-utils.js @@ -0,0 +1,197 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +/* eslint-env node */ + +const path = require("path"); +const fs = require("fs"); + +const projectRoot = path.resolve(__dirname, "../../../../"); + +/** + * Takes a file path and returns a string to use as the story title, capitalized + * and split into multiple words. The file name gets transformed into the story + * name, which will be visible in the Storybook sidebar. For example, either: + * + * /stories/hello-world.stories.md or /stories/helloWorld.md + * + * will result in a story named "Hello World". + * + * @param {string} filePath - path of the file being processed. + * @returns {string} The title of the story. + */ +function getTitleFromPath(filePath) { + let fileName = path.basename(filePath, ".stories.md"); + if (fileName != "README") { + try { + let relatedFilePath = path.resolve( + "../../../", + filePath.replace(".md", ".mjs") + ); + let relatedFile = fs.readFileSync(relatedFilePath).toString(); + let relatedTitle = relatedFile.match(/title: "(.*)"/)[1]; + if (relatedTitle) { + return relatedTitle + "/README"; + } + } catch {} + } + return separateWords(fileName); +} + +/** + * Splits a string into multiple capitalized words e.g. hello-world, helloWorld, + * and hello.world all become "Hello World." + * @param {string} str - String in any case. + * @returns {string} The string split into multiple words. + */ +function separateWords(str) { + return ( + str + .match(/[A-Z]?[a-z0-9]+/g) + ?.map(text => text[0].toUpperCase() + text.substring(1)) + .join(" ") || str + ); +} + +/** + * Enables rendering code in our markdown docs by parsing the source for + * annotated code blocks and replacing them with Storybook's Canvas component. + * @param {string} source - Stringified markdown source code. + * @returns {string} Source with code blocks replaced by Canvas components. + */ +function parseStoriesFromMarkdown(source) { + let storiesRegex = /```(?:js|html) story\n(?<code>[\s\S]*?)```/g; + // $code comes from the <code> capture group in the regex above. It consists + // of any code in between backticks and gets run when used in a Canvas component. + return source.replace( + storiesRegex, + "<Canvas withSource='none'><with-common-styles>\n$<code></with-common-styles></Canvas>" + ); +} + +/** + * Finds the name of the component for files in toolkit widgets. + * @param {string} resourcePath - Path to the file being processed. + * @returns The component name e.g. "moz-toggle" + */ +function getComponentName(resourcePath) { + let componentName = ""; + if (resourcePath.includes("toolkit/content/widgets")) { + let storyNameRegex = /(?<=\/widgets\/)(?<name>.*?)(?=\/)/g; + componentName = storyNameRegex.exec(resourcePath)?.groups?.name; + } + return componentName; +} + +/** + * Figures out where a markdown based story should live in Storybook i.e. + * whether it belongs under "Docs" or "UI Widgets" as well as what name to + * display in the sidebar. + * @param {string} resourcePath - Path to the file being processed. + * @returns {string} The title of the story. + */ +function getStoryTitle(resourcePath) { + // Currently we sort docs only stories under "Docs" by default. + let storyPath = "Docs"; + + let relativePath = path + .relative(projectRoot, resourcePath) + .replaceAll(path.sep, "/"); + + let componentName = getComponentName(relativePath); + if (componentName) { + // Get the common name for a component e.g. Toggle for moz-toggle + storyPath = + "UI Widgets/" + separateWords(componentName).replace(/^Moz/g, ""); + } + + let storyTitle = getTitleFromPath(relativePath); + let title = storyTitle.includes("/") + ? storyTitle + : `${storyPath}/${storyTitle}`; + return title; +} + +/** + * Figures out the path to import a component for cases where we have + * interactive examples in the docs that require the component to have been + * loaded. This wasn't necessary prior to Storybook V7 since everything was + * loaded up front; now stories are loaded on demand. + * @param {string} resourcePath - Path to the file being processed. + * @returns Path used to import a component into a story. + */ +function getImportPath(resourcePath) { + // Limiting this to toolkit widgets for now since we don't have any + // interactive examples in other docs stories. + if (!resourcePath.includes("toolkit/content/widgets")) { + return ""; + } + let componentName = getComponentName(resourcePath); + let fileExtension = ""; + if (componentName) { + let mjsPath = resourcePath.replace( + "README.stories.md", + `${componentName}.mjs` + ); + let jsPath = resourcePath.replace( + "README.stories.md", + `${componentName}.js` + ); + + if (fs.existsSync(mjsPath)) { + fileExtension = "mjs"; + } else if (fs.existsSync(jsPath)) { + fileExtension = "js"; + } else { + return ""; + } + } + return `"toolkit-widgets/${componentName}/${componentName}.${fileExtension}"`; +} + +/** + * Takes markdown and re-writes it to MDX. Conditionally includes a table of + * arguments when we're documenting a component. + * @param {string} source - The markdown source to rewrite to MDX. + * @param {string} title - The title of the story. + * @param {string} resourcePath - Path to the file being processed. + * @returns The markdown source converted to MDX. + */ +function getMDXSource(source, title, resourcePath = "") { + let importPath = getImportPath(resourcePath); + let componentName = getComponentName(resourcePath); + + // Unfortunately the indentation/spacing here seems to be important for the + // MDX parser to know what to do in the next step of the Webpack process. + let mdxSource = ` +import { Meta, Canvas, ArgTypes } from "@storybook/addon-docs"; +${importPath ? `import ${importPath};` : ""} + +<Meta + title="${title}" + parameters={{ + previewTabs: { + canvas: { hidden: true }, + }, + viewMode: "docs", + }} +/> + +${parseStoriesFromMarkdown(source)} + +${ + importPath && + ` +## Args Table + +<ArgTypes of={"${componentName}"} /> +` +}`; + + return mdxSource; +} + +module.exports = { + getMDXSource, + getStoryTitle, +}; |