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
JavaScript
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 };