UNPKG

storybook-addon-source-link

Version:

Add a button to the Storybook toolbar that opens the file containing the Story in an IDE like VSCode.

229 lines (213 loc) 7.35 kB
import { DocsContainer } from '@storybook/addon-docs/blocks'; import React, { useEffect } from 'react'; import { addons } from 'storybook/preview-api'; // src/preview.tsx // ../../node_modules/.pnpm/@jsr+std__path@1.1.2/node_modules/@jsr/std__path/_common/assert_path.js function assertPath(path) { if (typeof path !== "string") { throw new TypeError(`Path must be a string, received "${JSON.stringify(path)}"`); } } // ../../node_modules/.pnpm/@jsr+std__path@1.1.2/node_modules/@jsr/std__path/_common/from_file_url.js function assertArg(url) { url = url instanceof URL ? url : new URL(url); if (url.protocol !== "file:") { throw new TypeError(`URL must be a file URL: received "${url.protocol}"`); } return url; } // ../../node_modules/.pnpm/@jsr+std__path@1.1.2/node_modules/@jsr/std__path/posix/from_file_url.js function fromFileUrl(url) { url = assertArg(url); return decodeURIComponent(url.pathname.replace(/%(?![0-9A-Fa-f]{2})/g, "%25")); } // ../../node_modules/.pnpm/@jsr+std__path@1.1.2/node_modules/@jsr/std__path/_common/constants.js var CHAR_DOT = 46; var CHAR_FORWARD_SLASH = 47; // ../../node_modules/.pnpm/@jsr+std__path@1.1.2/node_modules/@jsr/std__path/posix/_util.js function isPosixPathSeparator(code) { return code === CHAR_FORWARD_SLASH; } // ../../node_modules/.pnpm/@jsr+std__path@1.1.2/node_modules/@jsr/std__path/_common/normalize.js function assertArg4(path) { assertPath(path); if (path.length === 0) return "."; } // ../../node_modules/.pnpm/@jsr+std__path@1.1.2/node_modules/@jsr/std__path/_common/normalize_string.js function normalizeString(path, allowAboveRoot, separator, isPathSeparator) { let res = ""; let lastSegmentLength = 0; let lastSlash = -1; let dots = 0; let code; for (let i = 0; i <= path.length; ++i) { if (i < path.length) code = path.charCodeAt(i); else if (isPathSeparator(code)) break; else code = CHAR_FORWARD_SLASH; if (isPathSeparator(code)) { if (lastSlash === i - 1 || dots === 1) ; else if (lastSlash !== i - 1 && dots === 2) { if (res.length < 2 || lastSegmentLength !== 2 || res.charCodeAt(res.length - 1) !== CHAR_DOT || res.charCodeAt(res.length - 2) !== CHAR_DOT) { if (res.length > 2) { const lastSlashIndex = res.lastIndexOf(separator); if (lastSlashIndex === -1) { res = ""; lastSegmentLength = 0; } else { res = res.slice(0, lastSlashIndex); lastSegmentLength = res.length - 1 - res.lastIndexOf(separator); } lastSlash = i; dots = 0; continue; } else if (res.length === 2 || res.length === 1) { res = ""; lastSegmentLength = 0; lastSlash = i; dots = 0; continue; } } if (allowAboveRoot) { if (res.length > 0) res += `${separator}..`; else res = ".."; lastSegmentLength = 2; } } else { if (res.length > 0) res += separator + path.slice(lastSlash + 1, i); else res = path.slice(lastSlash + 1, i); lastSegmentLength = i - lastSlash - 1; } lastSlash = i; dots = 0; } else if (code === CHAR_DOT && dots !== -1) { ++dots; } else { dots = -1; } } return res; } // ../../node_modules/.pnpm/@jsr+std__path@1.1.2/node_modules/@jsr/std__path/posix/normalize.js function normalize(path) { if (path instanceof URL) { path = fromFileUrl(path); } assertArg4(path); const isAbsolute2 = isPosixPathSeparator(path.charCodeAt(0)); const trailingSeparator = isPosixPathSeparator(path.charCodeAt(path.length - 1)); path = normalizeString(path, !isAbsolute2, "/", isPosixPathSeparator); if (path.length === 0 && !isAbsolute2) path = "."; if (path.length > 0 && trailingSeparator) path += "/"; if (isAbsolute2) return `/${path}`; return path; } // ../../node_modules/.pnpm/@jsr+std__path@1.1.2/node_modules/@jsr/std__path/posix/join.js function join(path, ...paths) { if (path === void 0) return "."; if (path instanceof URL) { path = fromFileUrl(path); } paths = path ? [ path, ...paths ] : paths; paths.forEach((path2) => assertPath(path2)); const joined = paths.filter((path2) => path2.length > 0).join("/"); return joined === "" ? "." : normalize(joined); } // src/linkUtil.ts var getFileUrl = (rootPath, importPath) => { return new URL(`file:///${join(rootPath, importPath)}`); }; // src/constants.ts var ADDON_ID = "storybook-addon-source-link"; var EVENTS = { REQUEST_RESOLVABLE_PARAM: `${ADDON_ID}/request-resolve`, RESPONSE_RESOLVABLE_PARAM: `${ADDON_ID}/response-resolve` }; // src/preview/parameterResolver.ts var useParameterResolver = (parameter, disabled) => { useEffect(() => { const channel = addons.getChannel(); if (disabled) return; const handler = (context) => { const resolvedParameters = Object.entries(parameter.links).map(([id, entry]) => { const resolvedEntry = resolveLinkEntry(entry, context); if (!resolvedEntry) return void 0; return { id, ...resolvedEntry }; }).filter((entry) => !!entry); channel.emit(EVENTS.RESPONSE_RESOLVABLE_PARAM, resolvedParameters); }; channel.on(EVENTS.REQUEST_RESOLVABLE_PARAM, handler); return () => { channel.off(EVENTS.REQUEST_RESOLVABLE_PARAM, handler); }; }); }; var withParameterResolver = (StoryFn, ctx) => { useParameterResolver(ctx.parameters.sourceLink, ctx.viewMode === "docs"); return StoryFn(); }; var ParameterResolver = ({ parameter }) => { useParameterResolver(parameter); return null; }; var resolveLinkEntry = (value, params) => { if (value instanceof Function) { return value(params); } return value; }; // src/preview.tsx var parameters = { sourceLink: { links: { "component-editor": ({ importPath, rootPath }) => { if (!rootPath) return void 0; const componentPath = importPath.replace(/\.stories\.tsx?$/, ".tsx"); const componentFileUrl = getFileUrl(rootPath, componentPath); return { type: "editor", label: componentPath, href: componentFileUrl.href, icon: "VSCodeIcon" }; }, "story-editor": ({ importPath, rootPath }) => { if (!rootPath) return void 0; const fileUrl = getFileUrl(rootPath, importPath); return { type: "editor", label: importPath, href: fileUrl.href, icon: "VSCodeIcon" }; }, "addon-powered-by": { label: "Powered by addon-source-link", href: "https://github.com/elecdeer/storybook-addon-source-link", order: Number.MAX_SAFE_INTEGER, icon: "InfoIcon" } } }, docs: { container: ({ children, ...props }) => { const { projectAnnotations } = props.context; return /* @__PURE__ */ React.createElement(DocsContainer, { ...props }, children, projectAnnotations.parameters && /* @__PURE__ */ React.createElement( ParameterResolver, { parameter: projectAnnotations.parameters.sourceLink } )); } } }; var decorators = [withParameterResolver]; export { decorators, parameters };