1 | const _excluded = ["properties"];
|
2 |
|
3 | function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
|
4 |
|
5 | import React from 'react';
|
6 | import { useParameter } from '@storybook/api';
|
7 | import { styled } from '@storybook/theming';
|
8 | import { Link } from '@storybook/router';
|
9 | import { SyntaxHighlighter } from '@storybook/components';
|
10 |
|
11 | import { createElement as createSyntaxHighlighterElement } from 'react-syntax-highlighter';
|
12 | const StyledStoryLink = styled(Link)(({
|
13 | theme
|
14 | }) => ({
|
15 | display: 'block',
|
16 | textDecoration: 'none',
|
17 | borderRadius: theme.appBorderRadius,
|
18 | color: 'inherit',
|
19 | '&:hover': {
|
20 | background: theme.background.hoverable
|
21 | }
|
22 | }));
|
23 | const SelectedStoryHighlight = styled.div(({
|
24 | theme
|
25 | }) => ({
|
26 | background: theme.background.hoverable,
|
27 | borderRadius: theme.appBorderRadius
|
28 | }));
|
29 | const StyledSyntaxHighlighter = styled(SyntaxHighlighter)(({
|
30 | theme
|
31 | }) => ({
|
32 | fontSize: theme.typography.size.s2 - 1
|
33 | }));
|
34 |
|
35 | const areLocationsEqual = (a, b) => a.startLoc.line === b.startLoc.line && a.startLoc.col === b.startLoc.col && a.endLoc.line === b.endLoc.line && a.endLoc.col === b.endLoc.col;
|
36 |
|
37 | export const StoryPanel = ({
|
38 | api
|
39 | }) => {
|
40 | const story = api.getCurrentStoryData();
|
41 | const selectedStoryRef = React.useRef(null);
|
42 | const {
|
43 | source,
|
44 | locationsMap
|
45 | } = useParameter('storySource', {
|
46 | source: 'loading source...'
|
47 | });
|
48 | const currentLocation = locationsMap ? locationsMap[Object.keys(locationsMap).find(key => {
|
49 | const sourceLoaderId = key.split('--');
|
50 | return story.id.endsWith(sourceLoaderId[sourceLoaderId.length - 1]);
|
51 | })] : undefined;
|
52 | React.useEffect(() => {
|
53 | if (selectedStoryRef.current) {
|
54 | selectedStoryRef.current.scrollIntoView();
|
55 | }
|
56 | }, [selectedStoryRef.current]);
|
57 |
|
58 | const createPart = ({
|
59 | rows,
|
60 | stylesheet,
|
61 | useInlineStyles
|
62 | }) => rows.map((node, i) => createSyntaxHighlighterElement({
|
63 | node,
|
64 | stylesheet,
|
65 | useInlineStyles,
|
66 | key: `code-segment${i}`
|
67 | }));
|
68 |
|
69 | const createStoryPart = ({
|
70 | rows,
|
71 | stylesheet,
|
72 | useInlineStyles,
|
73 | location,
|
74 | id,
|
75 | refId
|
76 | }) => {
|
77 | const first = location.startLoc.line - 1;
|
78 | const last = location.endLoc.line;
|
79 | const storyRows = rows.slice(first, last);
|
80 | const storySource = createPart({
|
81 | rows: storyRows,
|
82 | stylesheet,
|
83 | useInlineStyles
|
84 | });
|
85 | const storyKey = `${first}-${last}`;
|
86 |
|
87 | if (currentLocation && areLocationsEqual(location, currentLocation)) {
|
88 | return React.createElement(SelectedStoryHighlight, {
|
89 | key: storyKey,
|
90 | ref: selectedStoryRef
|
91 | }, storySource);
|
92 | }
|
93 |
|
94 | return React.createElement(StyledStoryLink, {
|
95 | to: refId ? `/story/${refId}_${id}` : `/story/${id}`,
|
96 | key: storyKey
|
97 | }, storySource);
|
98 | };
|
99 |
|
100 | const createParts = ({
|
101 | rows,
|
102 | stylesheet,
|
103 | useInlineStyles
|
104 | }) => {
|
105 | const parts = [];
|
106 | let lastRow = 0;
|
107 | Object.keys(locationsMap).forEach(key => {
|
108 | const location = locationsMap[key];
|
109 | const first = location.startLoc.line - 1;
|
110 | const last = location.endLoc.line;
|
111 | const {
|
112 | kind,
|
113 | refId
|
114 | } = story;
|
115 |
|
116 | const sourceIdParts = key.split('--');
|
117 | const id = api.storyId(kind, sourceIdParts[sourceIdParts.length - 1]);
|
118 | const start = createPart({
|
119 | rows: rows.slice(lastRow, first),
|
120 | stylesheet,
|
121 | useInlineStyles
|
122 | });
|
123 | const storyPart = createStoryPart({
|
124 | rows,
|
125 | stylesheet,
|
126 | useInlineStyles,
|
127 | location,
|
128 | id,
|
129 | refId
|
130 | });
|
131 | parts.push(start);
|
132 | parts.push(storyPart);
|
133 | lastRow = last;
|
134 | });
|
135 | const lastPart = createPart({
|
136 | rows: rows.slice(lastRow),
|
137 | stylesheet,
|
138 | useInlineStyles
|
139 | });
|
140 | parts.push(lastPart);
|
141 | return parts;
|
142 | };
|
143 |
|
144 | const lineRenderer = ({
|
145 | rows,
|
146 | stylesheet,
|
147 | useInlineStyles
|
148 | }) => {
|
149 |
|
150 |
|
151 |
|
152 |
|
153 | const myrows = rows.map(_ref => {
|
154 | let rest = _objectWithoutPropertiesLoose(_ref, _excluded);
|
155 |
|
156 | return Object.assign({}, rest, {
|
157 | properties: {
|
158 | className: []
|
159 | }
|
160 | });
|
161 | });
|
162 |
|
163 | if (!locationsMap || !Object.keys(locationsMap).length) {
|
164 | return createPart({
|
165 | rows: myrows,
|
166 | stylesheet,
|
167 | useInlineStyles
|
168 | });
|
169 | }
|
170 |
|
171 | const parts = createParts({
|
172 | rows: myrows,
|
173 | stylesheet,
|
174 | useInlineStyles
|
175 | });
|
176 | return React.createElement("span", null, parts);
|
177 | };
|
178 |
|
179 | return story ? React.createElement(StyledSyntaxHighlighter, {
|
180 | language: "jsx",
|
181 | showLineNumbers: true,
|
182 | renderer: lineRenderer,
|
183 | format: false,
|
184 | copyable: false,
|
185 | padded: true
|
186 | }, source) : null;
|
187 | }; |
\ | No newline at end of file |