@andersundsehr/storybook-typo3 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/entry-preview-docs.d.ts +17 -0
  2. package/dist/entry-preview-docs.js +29 -0
  3. package/dist/entry-preview.d.ts +10 -0
  4. package/dist/entry-preview.js +14 -0
  5. package/dist/functions/convertComponentToSource.d.ts +3 -0
  6. package/dist/functions/convertComponentToSource.js +118 -0
  7. package/dist/functions/convertComponentToSource.test.d.ts +1 -0
  8. package/{src/functions/convertComponentToSource.test.ts → dist/functions/convertComponentToSource.test.js} +44 -50
  9. package/dist/functions/error.d.ts +1 -0
  10. package/dist/functions/error.js +5 -0
  11. package/dist/functions/fetchComponent.d.ts +2 -0
  12. package/dist/functions/fetchComponent.js +17 -0
  13. package/dist/functions/fetchPreviewConfig.d.ts +8 -0
  14. package/dist/functions/fetchPreviewConfig.js +65 -0
  15. package/dist/functions/fetchRenderAction.d.ts +2 -0
  16. package/dist/functions/fetchRenderAction.js +20 -0
  17. package/dist/functions/fetchWithUserRetry.d.ts +1 -0
  18. package/dist/functions/fetchWithUserRetry.js +23 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.js +19 -0
  21. package/dist/preset.d.ts +10 -0
  22. package/{src/preset.ts → dist/preset.js} +15 -21
  23. package/{src/types/types.ts → dist/types/types.d.ts} +19 -51
  24. package/dist/types/types.js +0 -0
  25. package/package.json +17 -7
  26. package/src/entry-preview-docs.ts +0 -31
  27. package/src/entry-preview.ts +0 -21
  28. package/src/functions/convertComponentToSource.ts +0 -151
  29. package/src/functions/fetchComponent.ts +0 -30
  30. package/src/functions/fetchPreviewConfig.ts +0 -86
  31. package/src/functions/fetchRenderAction.ts +0 -19
  32. package/src/functions/fetchWithUserRetry.ts +0 -24
  33. package/src/index.ts +0 -23
@@ -0,0 +1,17 @@
1
+ import { SourceType } from 'storybook/internal/docs-tools';
2
+ import { type FluidRenderer } from '@andersundsehr/storybook-typo3';
3
+ import { DecoratorFunction } from 'storybook/internal/types';
4
+ export declare const tags: string[];
5
+ export declare const decorators: DecoratorFunction<FluidRenderer>[];
6
+ export declare const parameters: {
7
+ docs: {
8
+ story: {
9
+ inline: boolean;
10
+ };
11
+ codePanel: boolean;
12
+ source: {
13
+ type: SourceType;
14
+ };
15
+ toc: boolean;
16
+ };
17
+ };
@@ -0,0 +1,29 @@
1
+ import { SNIPPET_RENDERED, SourceType } from 'storybook/internal/docs-tools';
2
+ import { addons, useEffect } from 'storybook/preview-api';
3
+ import { convertComponentToSource } from './functions/convertComponentToSource';
4
+ import { error } from './functions/error';
5
+ const sourceDecorator = (storyFn, storyContext) => {
6
+ useEffect(() => {
7
+ const { id, args, component } = storyContext;
8
+ if (!component) {
9
+ error('No component found in story context. This decorator requires a Fluid component to render.', 4123764578913);
10
+ }
11
+ const source = convertComponentToSource(component, args);
12
+ addons.getChannel().emit(SNIPPET_RENDERED, { id, args, source });
13
+ });
14
+ return storyFn();
15
+ };
16
+ export const tags = ['autodocs'];
17
+ export const decorators = [sourceDecorator];
18
+ export const parameters = {
19
+ docs: {
20
+ story: {
21
+ inline: true,
22
+ },
23
+ codePanel: true,
24
+ source: {
25
+ type: SourceType.DYNAMIC,
26
+ },
27
+ toc: true,
28
+ },
29
+ };
@@ -0,0 +1,10 @@
1
+ import { fetchRenderAction } from '@andersundsehr/storybook-typo3';
2
+ import type { ArgTypesEnhancer, GlobalTypes } from 'storybook/internal/types';
3
+ export declare const globalTypes: GlobalTypes;
4
+ export declare const initialGlobals: Record<string, string>;
5
+ export declare const parameters: {
6
+ server: {
7
+ fetchStoryHtml: typeof fetchRenderAction;
8
+ };
9
+ };
10
+ export declare const argTypesEnhancers: ArgTypesEnhancer[];
@@ -0,0 +1,14 @@
1
+ import { fetchPreviewConfig, fetchRenderAction } from '@andersundsehr/storybook-typo3';
2
+ import { initGlobalsHandling } from './functions/fetchPreviewConfig';
3
+ const previewConfig = await fetchPreviewConfig();
4
+ initGlobalsHandling(previewConfig.globalTypes);
5
+ export const globalTypes = previewConfig.globalTypes;
6
+ export const initialGlobals = previewConfig.initialGlobals;
7
+ export const parameters = {
8
+ server: {
9
+ // url: url + '/_storybook/',
10
+ fetchStoryHtml: fetchRenderAction,
11
+ },
12
+ };
13
+ const enhanceArgTypes = storyContext => storyContext.component?.argTypes || {};
14
+ export const argTypesEnhancers = [enhanceArgTypes];
@@ -0,0 +1,3 @@
1
+ import type { FluidComponent } from '../types/types';
2
+ import type { StrictArgs } from 'storybook/internal/csf';
3
+ export declare function convertComponentToSource(component: FluidComponent, args: StrictArgs): string;
@@ -0,0 +1,118 @@
1
+ function convertValueToString(key, argType, value, inline) {
2
+ const createValue = () => {
3
+ if ('variableName' in value) {
4
+ if (inline) {
5
+ return value.variableName; // For inline code, just return the variable name
6
+ }
7
+ return '{' + value.variableName + '}'; // For attributes, return the variable name wrapped in curly braces
8
+ }
9
+ const valueAsString = String(value.value);
10
+ if (typeof value.value === 'boolean') {
11
+ if (inline) {
12
+ return valueAsString;
13
+ }
14
+ return '{' + valueAsString + '}';
15
+ }
16
+ if (typeof value.value === 'number') {
17
+ if (argType?.type?.name === 'int') {
18
+ return String(Number.parseInt(valueAsString, 10)); // Convert to integer
19
+ }
20
+ return String(Number.parseFloat(valueAsString)); // Convert to float
21
+ }
22
+ if (inline) {
23
+ return '\'' + valueAsString.replace(/'/g, '\\\'') + '\''; // Escape single quotes for inline code
24
+ }
25
+ return valueAsString.replace(/"/g, '\\"'); // Escape double quotes for attributes
26
+ };
27
+ if (inline) {
28
+ return key + ': ' + createValue();
29
+ }
30
+ return key + '="' + createValue() + '"';
31
+ }
32
+ function inlineCode(viewHelperArguments, component, defaultSlotContent) {
33
+ const argsStrings = Object.entries(viewHelperArguments)
34
+ .map(([argumentName, argumentValue]) => convertValueToString(argumentName, component.argTypes[argumentName], argumentValue, true));
35
+ const argsString = argsStrings.join(', ');
36
+ let source = '{';
37
+ if (defaultSlotContent) {
38
+ defaultSlotContent = defaultSlotContent.replace(/'/g, '\\\''); // Escape single quotes for inline code
39
+ source += `'${defaultSlotContent}' -> `;
40
+ }
41
+ source += `${component.fullName}(`;
42
+ if ((source.length + argsString.length) > 80) {
43
+ source += `\n ${argsStrings.join(',\n ')}\n`;
44
+ }
45
+ else {
46
+ source += argsString;
47
+ }
48
+ source += ')}';
49
+ return source;
50
+ }
51
+ function generateOpenTag(viewHelperArguments, component) {
52
+ const argStrings = Object.entries(viewHelperArguments)
53
+ .map(([argumentName, argumentValue]) => convertValueToString(argumentName, component.argTypes[argumentName], argumentValue, false));
54
+ let argsString = argStrings.join(' ');
55
+ let openTag = `<${component.fullName}`;
56
+ if (argsString.length > 0) {
57
+ openTag += ' ' + argsString;
58
+ }
59
+ if (openTag.length > 80) {
60
+ argsString = argStrings.join('\n ');
61
+ openTag = `<${component.fullName} \n ${argsString}\n`;
62
+ }
63
+ return openTag;
64
+ }
65
+ const SLOT_PREFIX = 'slot____';
66
+ function createComponentData(component, args) {
67
+ const viewHelperArguments = {};
68
+ const slots = {};
69
+ for (const [key, value] of Object.entries(args)) {
70
+ if (key.startsWith(SLOT_PREFIX)) {
71
+ // This is a slot
72
+ const slotName = key.replace(SLOT_PREFIX, '');
73
+ if (value === null || value === undefined || value === '') {
74
+ continue;
75
+ }
76
+ slots[slotName] = value;
77
+ continue;
78
+ }
79
+ if (!key.includes('__')) {
80
+ // This is a normal argument without virtual subkey
81
+ viewHelperArguments[key] = { value };
82
+ continue;
83
+ }
84
+ // This is a virtual argument
85
+ const [variableName] = key.split('__');
86
+ viewHelperArguments[variableName] = { variableName };
87
+ }
88
+ return { viewHelperArguments, slots };
89
+ }
90
+ export function convertComponentToSource(component, args) {
91
+ const { viewHelperArguments, slots } = createComponentData(component, args);
92
+ let source = '';
93
+ source += `<html\n xmlns:${component.namespace}="http://typo3.org/ns/${component.collection.replace(/\\/g, '/')}"\n data-namespace-typo3-fluid="true"\n>\n\n`;
94
+ const usedArgs = [...new Set(Object.keys(args).filter(key => !key.startsWith(SLOT_PREFIX)).map(key => key.split('__')[0]))];
95
+ source += generateOpenTag(viewHelperArguments, component);
96
+ if (Object.keys(slots).length <= 0) {
97
+ source += `/>\n`;
98
+ source += `\n<!-- or -->\n\n`;
99
+ source += `{namespace ${component.namespace}=${component.collection}}\n\n`;
100
+ source += inlineCode(viewHelperArguments, component);
101
+ return source;
102
+ }
103
+ source += '>\n';
104
+ if (Object.keys(slots).length === 1 && slots.default) {
105
+ // If there is only a default slot, we can use the inline code
106
+ source += ` ${slots.default}\n`;
107
+ source += '</' + component.fullName + '>\n';
108
+ source += `\n<!-- or -->\n\n`;
109
+ source += `{namespace ${component.namespace}=${component.collection}}\n\n`;
110
+ source += inlineCode(viewHelperArguments, component, slots.default);
111
+ return source;
112
+ }
113
+ for (const [slotName, slotValue] of Object.entries(slots)) {
114
+ source += ` <f:fragment name="${slotName}">${slotValue}</f:fragment>\n`;
115
+ }
116
+ source += '</' + component.fullName + '>';
117
+ return source;
118
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,17 +1,12 @@
1
- import test from 'node:test';
2
- import { convertComponentToSource } from './convertComponentToSource.ts';
3
- import type { FluidComponent } from '../types/types.ts';
4
- import type { StrictArgs } from 'storybook/internal/csf';
5
-
6
- interface TestCase { component: FluidComponent; args: StrictArgs; expected: string }
7
-
1
+ import { test } from 'node:test';
2
+ import { convertComponentToSource } from './convertComponentToSource';
8
3
  const component = { name: 'test', fullName: 'c:test', argTypes: {}, collection: 'Class', namespace: 'c' };
9
- const testCases: Record<string, TestCase> = {
10
- ///////////////////////////////////////////
11
- withoutAnyProperties: {
12
- component,
13
- args: {},
14
- expected: `<html
4
+ const testCases = {
5
+ ///////////////////////////////////////////
6
+ withoutAnyProperties: {
7
+ component,
8
+ args: {},
9
+ expected: `<html
15
10
  xmlns:c="http://typo3.org/ns/Class"
16
11
  data-namespace-typo3-fluid="true"
17
12
  >
@@ -23,12 +18,12 @@ const testCases: Record<string, TestCase> = {
23
18
  {namespace c=Class}
24
19
 
25
20
  {c:test()}`,
26
- },
27
- ///////////////////////////////////////////
28
- withSimpleArguments: {
29
- component,
30
- args: { arg1: 'value1', arg2: 'value2' },
31
- expected: `<html
21
+ },
22
+ ///////////////////////////////////////////
23
+ withSimpleArguments: {
24
+ component,
25
+ args: { arg1: 'value1', arg2: 'value2' },
26
+ expected: `<html
32
27
  xmlns:c="http://typo3.org/ns/Class"
33
28
  data-namespace-typo3-fluid="true"
34
29
  >
@@ -40,12 +35,12 @@ const testCases: Record<string, TestCase> = {
40
35
  {namespace c=Class}
41
36
 
42
37
  {c:test(arg1: 'value1', arg2: 'value2')}`,
43
- },
44
- ///////////////////////////////////////////
45
- withSlot: {
46
- component,
47
- args: { arg1: 'value\'"1', arg2: 'value2', slot____default: 'default \'"slot' },
48
- expected: `<html
38
+ },
39
+ ///////////////////////////////////////////
40
+ withSlot: {
41
+ component,
42
+ args: { arg1: 'value\'"1', arg2: 'value2', slot____default: 'default \'"slot' },
43
+ expected: `<html
49
44
  xmlns:c="http://typo3.org/ns/Class"
50
45
  data-namespace-typo3-fluid="true"
51
46
  >
@@ -59,12 +54,12 @@ const testCases: Record<string, TestCase> = {
59
54
  {namespace c=Class}
60
55
 
61
56
  {'default \\'"slot' -> c:test(arg1: 'value\\'"1', arg2: 'value2')}`,
62
- },
63
- ///////////////////////////////////////////
64
- withMultipleSlots: {
65
- component,
66
- args: { slot____a: 'slotA', slot____b: 'slotB', slot____default: 'slotDefault' },
67
- expected: `<html
57
+ },
58
+ ///////////////////////////////////////////
59
+ withMultipleSlots: {
60
+ component,
61
+ args: { slot____a: 'slotA', slot____b: 'slotB', slot____default: 'slotDefault' },
62
+ expected: `<html
68
63
  xmlns:c="http://typo3.org/ns/Class"
69
64
  data-namespace-typo3-fluid="true"
70
65
  >
@@ -74,12 +69,12 @@ const testCases: Record<string, TestCase> = {
74
69
  <f:fragment name="b">slotB</f:fragment>
75
70
  <f:fragment name="default">slotDefault</f:fragment>
76
71
  </c:test>`,
77
- },
78
- ///////////////////////////////////////////
79
- withTransformedArgument: {
80
- component,
81
- args: { arg1__argA: 'value1', arg1__argB: 'value2' },
82
- expected: `<html
72
+ },
73
+ ///////////////////////////////////////////
74
+ withTransformedArgument: {
75
+ component,
76
+ args: { arg1__argA: 'value1', arg1__argB: 'value2' },
77
+ expected: `<html
83
78
  xmlns:c="http://typo3.org/ns/Class"
84
79
  data-namespace-typo3-fluid="true"
85
80
  >
@@ -91,12 +86,12 @@ const testCases: Record<string, TestCase> = {
91
86
  {namespace c=Class}
92
87
 
93
88
  {c:test(arg1: arg1)}`,
94
- },
95
- ///////////////////////////////////////////
96
- longLines: {
97
- component,
98
- args: { arg1: 'veryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValue', arg2: 'veryLongValueveryLongValueveryLongValueveryLongValueveryLongValue' },
99
- expected: `<html
89
+ },
90
+ ///////////////////////////////////////////
91
+ longLines: {
92
+ component,
93
+ args: { arg1: 'veryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValue', arg2: 'veryLongValueveryLongValueveryLongValueveryLongValueveryLongValue' },
94
+ expected: `<html
100
95
  xmlns:c="http://typo3.org/ns/Class"
101
96
  data-namespace-typo3-fluid="true"
102
97
  >
@@ -114,13 +109,12 @@ const testCases: Record<string, TestCase> = {
114
109
  arg1: 'veryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValueveryLongValue',
115
110
  arg2: 'veryLongValueveryLongValueveryLongValueveryLongValueveryLongValue'
116
111
  )}`,
117
- },
118
- ///////////////////////////////////////////
112
+ },
113
+ ///////////////////////////////////////////
119
114
  };
120
-
121
115
  for (const [name, { component, args, expected }] of Object.entries(testCases)) {
122
- test(`convertComponentToSource - ${name}`, (t) => {
123
- const result = convertComponentToSource(component, args);
124
- t.assert.strictEqual(result, expected);
125
- });
116
+ test(`convertComponentToSource - ${name}`, (t) => {
117
+ const result = convertComponentToSource(component, args);
118
+ t.assert.strictEqual(result, expected);
119
+ });
126
120
  }
@@ -0,0 +1 @@
1
+ export declare function error(message: string, code: number): never;
@@ -0,0 +1,5 @@
1
+ export function error(message, code) {
2
+ message += ` (code: ${code})`;
3
+ alert(message);
4
+ throw new Error(message);
5
+ }
@@ -0,0 +1,2 @@
1
+ import { type FluidComponent } from '@andersundsehr/storybook-typo3';
2
+ export declare function fetchComponent(component: string): Promise<FluidComponent>;
@@ -0,0 +1,17 @@
1
+ import { url } from '@andersundsehr/storybook-typo3';
2
+ import { fetchWithUserRetry } from './fetchWithUserRetry';
3
+ export async function fetchComponent(component) {
4
+ if (!component.includes(':')) {
5
+ const message = 'Component name must be in the format "namespace:name"';
6
+ alert(message);
7
+ throw new Error(message);
8
+ }
9
+ const data = await fetchWithUserRetry(url + '/_storybook/componentMeta?viewHelper=' + component, {}, 'metadata for component `' + component + '` from TYPO3');
10
+ return {
11
+ fullName: component,
12
+ name: component.split(':')[1],
13
+ namespace: component.split(':')[0],
14
+ collection: data.collectionClassName,
15
+ argTypes: data.argTypes,
16
+ };
17
+ }
@@ -0,0 +1,8 @@
1
+ import type { GlobalTypes } from 'storybook/internal/types';
2
+ interface PreviewConfig {
3
+ globalTypes: GlobalTypes;
4
+ initialGlobals: Record<string, string>;
5
+ }
6
+ export declare function fetchPreviewConfig(currentGlobals?: Record<string, string>): Promise<PreviewConfig>;
7
+ export declare function initGlobalsHandling(initalGlobalTypes: GlobalTypes): void;
8
+ export {};
@@ -0,0 +1,65 @@
1
+ import { url } from '@andersundsehr/storybook-typo3';
2
+ import { fetchWithUserRetry } from './fetchWithUserRetry';
3
+ import { GLOBALS_UPDATED, SET_GLOBALS } from 'storybook/internal/core-events';
4
+ import { addons } from 'storybook/preview-api';
5
+ function getLocation() {
6
+ let location = window.location;
7
+ if (window.parent?.location && window.parent !== window) {
8
+ location = window.parent.location;
9
+ }
10
+ return location;
11
+ }
12
+ function getGlobalsFromUrl() {
13
+ const url = new URL(getLocation().href);
14
+ const globalsString = url.searchParams.get('globals') || '';
15
+ const globalsArray = globalsString.split(';').filter(Boolean);
16
+ const entries = globalsArray.map(global => global.split(':'));
17
+ return Object.fromEntries(entries);
18
+ }
19
+ function setGlobalsToUrl(globals) {
20
+ const location = getLocation();
21
+ const url = new URL(location.href);
22
+ const globalString = Object.entries(globals).map(([key, value]) => `${key}:${value}`).join(';');
23
+ url.searchParams.set('globals', globalString);
24
+ location.href = url.toString();
25
+ }
26
+ export async function fetchPreviewConfig(currentGlobals) {
27
+ const apiEndpoint = url + '/_storybook/preview';
28
+ const globals = currentGlobals || getGlobalsFromUrl();
29
+ return await fetchWithUserRetry(apiEndpoint, {
30
+ method: 'POST',
31
+ headers: {
32
+ 'Content-Type': 'application/json',
33
+ },
34
+ body: JSON.stringify({ globals }),
35
+ }, 'preview config from TYPO3');
36
+ }
37
+ export function initGlobalsHandling(initalGlobalTypes) {
38
+ const channel = addons.getChannel();
39
+ let oldGlobalTypes = initalGlobalTypes;
40
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
41
+ channel.on(GLOBALS_UPDATED, async ({ globals }) => {
42
+ const previewConfig = await fetchPreviewConfig(globals);
43
+ oldGlobalTypes = previewConfig.globalTypes;
44
+ const newGlobals = getGlobalsFromUrl();
45
+ let changed = false;
46
+ for (const key in previewConfig.globalTypes) {
47
+ const inputType = previewConfig.globalTypes[key];
48
+ if (!inputType.toolbar?.items?.some((item) => item.value === globals[key])) {
49
+ newGlobals[key] = inputType.toolbar?.items?.[0]?.value || '';
50
+ changed = true;
51
+ }
52
+ }
53
+ if (changed) {
54
+ // change URL search params and reload page (this is the only way to set the globals, that worked)!!
55
+ setGlobalsToUrl(newGlobals);
56
+ return;
57
+ }
58
+ if (JSON.stringify(oldGlobalTypes) !== JSON.stringify(previewConfig.globalTypes)) {
59
+ // this only sets the globalTypes and not the globals :(
60
+ // see https://github.com/storybookjs/storybook/blob/3ac2fece4c41955e7349f6f825b7d21dd18ff9a9/code/core/src/manager-api/modules/globals.ts#L141-L149
61
+ channel.emit(SET_GLOBALS, { globals, globalTypes: previewConfig.globalTypes });
62
+ return;
63
+ }
64
+ });
65
+ }
@@ -0,0 +1,2 @@
1
+ import { type StoryContext } from '@andersundsehr/storybook-typo3';
2
+ export declare function fetchRenderAction(urlA: string, id: string, params: unknown, storyContext: StoryContext): Promise<string>;
@@ -0,0 +1,20 @@
1
+ import { url } from '@andersundsehr/storybook-typo3';
2
+ import { fetchWithUserRetry } from './fetchWithUserRetry';
3
+ import { error } from './error';
4
+ export async function fetchRenderAction(urlA, id, params, storyContext) {
5
+ if (!storyContext.component) {
6
+ error('No component found in story context. This function requires a Fluid component to render.', 4123764578913);
7
+ }
8
+ const viewHelper = (typeof storyContext.component === 'string') ? storyContext.component : storyContext.component.fullName;
9
+ const body = {
10
+ viewHelper: viewHelper,
11
+ arguments: params,
12
+ site: storyContext.globals?.site || 'default',
13
+ siteLanguage: storyContext.globals?.language || 'default',
14
+ };
15
+ return await fetchWithUserRetry(url + '/_storybook/render', {
16
+ method: 'POST',
17
+ body: JSON.stringify(body),
18
+ }, 'rendering component in TYPO3', 'text');
19
+ }
20
+ ;
@@ -0,0 +1 @@
1
+ export declare function fetchWithUserRetry<T>(url: string, options: RequestInit, message: string, result?: 'json' | 'text'): Promise<T>;
@@ -0,0 +1,23 @@
1
+ export async function fetchWithUserRetry(url, options, message, result = 'json') {
2
+ options = { ...options }; // Clone options to avoid mutating the original
3
+ options.signal = options.signal || AbortSignal.timeout(5000);
4
+ try {
5
+ const response = await fetch(url, options);
6
+ if (result === 'text') {
7
+ return await response.text();
8
+ }
9
+ return await response.json();
10
+ }
11
+ catch (error) {
12
+ console.error('Fetch failed:', { error });
13
+ const retry = confirm('Error while fetching ' + message + '. \n\n'
14
+ + 'fetch: ' + url + '\n'
15
+ + 'ERROR: ' + String(error) + '\n\n'
16
+ + 'look at the console for more details.\n\n'
17
+ + 'Do you want to retry?');
18
+ if (retry) {
19
+ return fetchWithUserRetry(url, options, message);
20
+ }
21
+ throw error; // Re-throw the error if the user does not want to retry
22
+ }
23
+ }
@@ -0,0 +1,7 @@
1
+ export * from './functions/fetchRenderAction';
2
+ export * from './functions/fetchPreviewConfig';
3
+ export * from './functions/fetchComponent';
4
+ export * from './functions/fetchWithUserRetry';
5
+ export * from './types/types';
6
+ declare const url: string;
7
+ export { url };
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ /// <reference types="vite/client" />
2
+ export * from './functions/fetchRenderAction';
3
+ export * from './functions/fetchPreviewConfig';
4
+ export * from './functions/fetchComponent';
5
+ export * from './functions/fetchWithUserRetry';
6
+ export * from './types/types';
7
+ const url = (() => {
8
+ let url = import.meta.env.STORYBOOK_TYPO3_ENDPOINT;
9
+ if (typeof url !== 'string' || !url) {
10
+ throw new Error('env STORYBOOK_TYPO3_ENDPOINT is not set or is not a string');
11
+ }
12
+ url = url.replace(/_storybook\/?$/, '');
13
+ url = url.replace(/\/$/, '');
14
+ if (typeof url !== 'string' || !url) {
15
+ throw new Error('env STORYBOOK_TYPO3_ENDPOINT is not set or is not a string');
16
+ }
17
+ return url;
18
+ })();
19
+ export { url };
@@ -0,0 +1,10 @@
1
+ import type { PresetProperty } from 'storybook/internal/types';
2
+ export declare const addons: string[];
3
+ export declare const core: PresetProperty<'core'>;
4
+ export declare const previewAnnotations: PresetProperty<'previewAnnotations'>;
5
+ export declare const tags: string[];
6
+ /**
7
+ * BUGFIX for chromium based browsers and windows
8
+ * @see https://github.com/talkjs/country-flag-emoji-polyfill?tab=readme-ov-file
9
+ */
10
+ export declare const managerHead = "<style>\n body {\n font-family: \"Twemoji Country Flags\", \"Nunito Sans\", -apple-system, \".SFNSText-Regular\", \"San Francisco\", BlinkMacSystemFont, \"Segoe UI\", \"Helvetica Neue\", Helvetica, Arial, sans-serif !important;\n }\n @font-face {\n font-family: \"Twemoji Country Flags\";\n unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F;\n src: url('https://cdn.jsdelivr.net/npm/country-flag-emoji-polyfill@0.1/dist/TwemojiCountryFlags.woff2') format('woff2');\n font-display: swap;\n }\n</style>";
@@ -1,31 +1,25 @@
1
1
  import { dirname, join } from 'node:path';
2
- import type { Entry, PresetProperty } from 'storybook/internal/types';
3
-
4
- function getAbsolutePath<I extends string>(value: I): I {
5
- return dirname(require.resolve(join(value, 'package.json'))) as any;
2
+ import { createRequire } from 'module';
3
+ const require = createRequire(import.meta.url);
4
+ function getAbsolutePath(value) {
5
+ return dirname(require.resolve(join(value, 'package.json')));
6
6
  }
7
-
8
7
  export const addons = [
9
- '@storybook/addon-docs',
10
- '@storybook/addon-a11y',
8
+ '@storybook/addon-docs',
9
+ '@storybook/addon-a11y',
11
10
  ];
12
-
13
- export const core: PresetProperty<'core'> = {
14
- builder: getAbsolutePath('@storybook/builder-vite'),
15
- renderer: getAbsolutePath('@storybook/server'),
16
- disableTelemetry: true,
11
+ export const core = {
12
+ builder: getAbsolutePath('@storybook/builder-vite'),
13
+ renderer: getAbsolutePath('@storybook/server'),
14
+ disableTelemetry: true,
17
15
  };
18
-
19
- export const previewAnnotations: PresetProperty<'previewAnnotations'> = async (entry: Entry[] = [], options) => {
20
- const docsEnabled = Object.keys(await options.presets.apply('docs', {}, options)).length > 0;
21
-
22
- return entry
23
- .concat(require.resolve('./entry-preview'))
24
- .concat(docsEnabled ? [require.resolve('./entry-preview-docs')] : []);
16
+ export const previewAnnotations = async (entry = [], options) => {
17
+ const docsEnabled = Object.keys(await options.presets.apply('docs', {}, options)).length > 0;
18
+ return entry
19
+ .concat(require.resolve('./entry-preview'))
20
+ .concat(docsEnabled ? [require.resolve('./entry-preview-docs')] : []);
25
21
  };
26
-
27
22
  export const tags = ['autodocs'];
28
-
29
23
  /**
30
24
  * BUGFIX for chromium based browsers and windows
31
25
  * @see https://github.com/talkjs/country-flag-emoji-polyfill?tab=readme-ov-file
@@ -1,67 +1,37 @@
1
- // export type { StoryObj } from '@storybook/server';
2
1
  import type { Args, StrictArgs } from '@storybook/server';
3
- import {
4
- AnnotatedStoryFn,
5
- CompatibleString,
6
- ComponentAnnotations,
7
- DecoratorFunction,
8
- LoaderFunction,
9
- ProjectAnnotations,
10
- StoryAnnotations,
11
- StorybookConfig as StorybookConfigBase,
12
- StoryContext as StoryContext$1,
13
- WebRenderer,
14
- } from 'storybook/internal/types';
15
-
2
+ import { AnnotatedStoryFn, CompatibleString, ComponentAnnotations, DecoratorFunction, LoaderFunction, ProjectAnnotations, StoryAnnotations, StorybookConfig as StorybookConfigBase, StoryContext as StoryContext$1, WebRenderer } from 'storybook/internal/types';
16
3
  import type { BuilderOptions, StorybookConfigVite } from '@storybook/builder-vite';
17
-
18
4
  type FrameworkName = CompatibleString<'@andersundsehr/storybook-typo3'>;
19
5
  type BuilderName = CompatibleString<'@storybook/builder-vite'>;
20
-
21
6
  export interface FrameworkOptions {
22
- builder?: BuilderOptions;
7
+ builder?: BuilderOptions;
23
8
  }
24
-
25
9
  interface StorybookConfigFramework {
26
- framework:
27
- | FrameworkName
28
- | {
29
- name: FrameworkName;
30
- options: FrameworkOptions;
10
+ framework: FrameworkName | {
11
+ name: FrameworkName;
12
+ options: FrameworkOptions;
13
+ };
14
+ core?: StorybookConfigBase['core'] & {
15
+ builder?: BuilderName | {
16
+ name: BuilderName;
17
+ options: BuilderOptions;
18
+ };
31
19
  };
32
- core?: StorybookConfigBase['core'] & {
33
- builder?:
34
- | BuilderName
35
- | {
36
- name: BuilderName;
37
- options: BuilderOptions;
38
- };
39
- };
40
20
  }
41
-
42
21
  /** The interface for Storybook configuration in `main.ts` files. */
43
- export type StorybookConfig = Omit<
44
- StorybookConfigBase,
45
- keyof StorybookConfigVite | keyof StorybookConfigFramework
46
- > &
47
- StorybookConfigVite &
48
- StorybookConfigFramework;
49
-
22
+ export type StorybookConfig = Omit<StorybookConfigBase, keyof StorybookConfigVite | keyof StorybookConfigFramework> & StorybookConfigVite & StorybookConfigFramework;
50
23
  type StoryFnServerReturnType = any;
51
-
52
24
  export interface FluidComponent {
53
- fullName: string;
54
- name: string;
55
- namespace: string;
56
- collection: string;
57
- argTypes: Record<string, any>;
25
+ fullName: string;
26
+ name: string;
27
+ namespace: string;
28
+ collection: string;
29
+ argTypes: Record<string, any>;
58
30
  }
59
-
60
31
  interface FluidRenderer extends WebRenderer {
61
- component: FluidComponent;
62
- storyResult: StoryFnServerReturnType;
32
+ component: FluidComponent;
33
+ storyResult: StoryFnServerReturnType;
63
34
  }
64
-
65
35
  /**
66
36
  * Metadata to configure the stories for a component.
67
37
  *
@@ -80,10 +50,8 @@ type StoryFn<TArgs = Args> = AnnotatedStoryFn<FluidRenderer, TArgs>;
80
50
  * @see [Named Story exports](https://storybook.js.org/docs/api/csf#named-story-exports)
81
51
  */
82
52
  type StoryObj<TArgs = Args> = StoryAnnotations<FluidRenderer, TArgs>;
83
-
84
53
  type Decorator<TArgs = StrictArgs> = DecoratorFunction<FluidRenderer, TArgs>;
85
54
  type Loader<TArgs = StrictArgs> = LoaderFunction<FluidRenderer, TArgs>;
86
55
  type StoryContext<TArgs = StrictArgs> = StoryContext$1<FluidRenderer, TArgs>;
87
56
  type Preview = ProjectAnnotations<FluidRenderer>;
88
-
89
57
  export { Decorator, Loader, Meta, Preview, FluidRenderer, StoryContext, StoryFn, StoryObj };
File without changes
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@andersundsehr/storybook-typo3",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
5
6
  "exports": {
6
7
  ".": {
7
- "import": "./src/index.ts",
8
- "types": "./src/index.ts"
8
+ "import": "./dist/index.js",
9
+ "types": "./dist/index.js"
9
10
  },
10
11
  "./preset": {
11
- "require": "./src/preset.ts",
12
- "types": "./src/preset.ts"
12
+ "require": "./dist/preset.js",
13
+ "types": "./dist/preset.js"
13
14
  }
14
15
  },
15
16
  "dependencies": {
@@ -19,8 +20,17 @@
19
20
  "@storybook/addon-a11y": "^9.0.0",
20
21
  "storybook": "^9.0.0"
21
22
  },
23
+ "devDependencies": {
24
+ "typescript": "^5.8.3"
25
+ },
22
26
  "scripts": {
23
27
  "test": "node --test --experimental-strip-types",
24
- "test:watch": "node --test --experimental-strip-types --watch"
25
- }
28
+ "test:watch": "node --test --experimental-strip-types --watch",
29
+ "build": "tsc",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "version": "0.1.2"
26
36
  }
@@ -1,31 +0,0 @@
1
- import { SNIPPET_RENDERED, SourceType } from 'storybook/internal/docs-tools';
2
- import { type FluidRenderer } from '@andersundsehr/storybook-typo3';
3
- import { DecoratorFunction } from 'storybook/internal/types';
4
- import { addons, useEffect } from 'storybook/preview-api';
5
- import { convertComponentToSource } from './functions/convertComponentToSource';
6
-
7
- const sourceDecorator: DecoratorFunction<FluidRenderer> = (storyFn, storyContext) => {
8
- useEffect(() => {
9
- const { id, args, component } = storyContext;
10
- const source = convertComponentToSource(component, args);
11
- addons.getChannel().emit(SNIPPET_RENDERED, { id, args, source });
12
- });
13
-
14
- return storyFn();
15
- };
16
-
17
- export const tags = ['autodocs'];
18
-
19
- export const decorators: DecoratorFunction<FluidRenderer>[] = [sourceDecorator];
20
- export const parameters = {
21
- docs: {
22
- story: {
23
- inline: true,
24
- },
25
- codePanel: true,
26
- source: {
27
- type: SourceType.DYNAMIC,
28
- },
29
- toc: true,
30
- },
31
- };
@@ -1,21 +0,0 @@
1
- import { fetchPreviewConfig, fetchRenderAction, type StoryContext } from '@andersundsehr/storybook-typo3';
2
- import type { ArgTypesEnhancer, GlobalTypes } from 'storybook/internal/types';
3
- import { initGlobalsHandling } from './functions/fetchPreviewConfig.ts';
4
-
5
- const previewConfig = await fetchPreviewConfig();
6
-
7
- initGlobalsHandling(previewConfig.globalTypes);
8
-
9
- export const globalTypes: GlobalTypes = previewConfig.globalTypes;
10
-
11
- export const initialGlobals: Record<string, string> = previewConfig.initialGlobals;
12
-
13
- export const parameters = {
14
- server: {
15
- // url: url + '/_storybook/',
16
- fetchStoryHtml: fetchRenderAction,
17
- },
18
- };
19
-
20
- const enhanceArgTypes: ArgTypesEnhancer = (storyContext: StoryContext) => storyContext.component?.argTypes || {};
21
- export const argTypesEnhancers: ArgTypesEnhancer[] = [enhanceArgTypes];
@@ -1,151 +0,0 @@
1
- import type { FluidComponent } from '../types/types';
2
- import type { StrictArgs } from 'storybook/internal/csf';
3
-
4
- function convertValueToString(key: string, argType: { type?: { name?: 'int' } }, value: ArgumentValue, inline: boolean): string {
5
- const createValue = () => {
6
- if ('variableName' in value) {
7
- if (inline) {
8
- return value.variableName; // For inline code, just return the variable name
9
- }
10
- return '{' + value.variableName + '}'; // For attributes, return the variable name wrapped in curly braces
11
- }
12
- const valueAsString = String(value.value);
13
- if (typeof value.value === 'boolean') {
14
- if (inline) {
15
- return valueAsString;
16
- }
17
- return '{' + valueAsString + '}';
18
- }
19
- if (typeof value.value === 'number') {
20
- if (argType?.type?.name === 'int') {
21
- return String(Number.parseInt(valueAsString, 10)); // Convert to integer
22
- }
23
- return String(Number.parseFloat(valueAsString)); // Convert to float
24
- }
25
- if (inline) {
26
- return '\'' + valueAsString.replace(/'/g, '\\\'') + '\''; // Escape single quotes for inline code
27
- }
28
- return valueAsString.replace(/"/g, '\\"'); // Escape double quotes for attributes
29
- };
30
-
31
- if (inline) {
32
- return key + ': ' + createValue();
33
- }
34
- return key + '="' + createValue() + '"';
35
- }
36
-
37
- function inlineCode(viewHelperArguments: Arguments, component: FluidComponent, defaultSlotContent?: string) {
38
- const argsStrings: string[] = Object.entries(viewHelperArguments)
39
- .map(([argumentName, argumentValue]) => convertValueToString(argumentName, component.argTypes[argumentName], argumentValue, true));
40
- const argsString = argsStrings.join(', ');
41
-
42
- let source = '{';
43
-
44
- if (defaultSlotContent) {
45
- defaultSlotContent = defaultSlotContent.replace(/'/g, '\\\''); // Escape single quotes for inline code
46
- source += `'${defaultSlotContent}' -> `;
47
- }
48
- source += `${component.fullName}(`;
49
- if ((source.length + argsString.length) > 80) {
50
- source += `\n ${argsStrings.join(',\n ')}\n`;
51
- } else {
52
- source += argsString;
53
- }
54
-
55
- source += ')}';
56
- return source;
57
- }
58
-
59
- function generateOpenTag(viewHelperArguments: Arguments, component: FluidComponent) {
60
- const argStrings: string[] = Object.entries(viewHelperArguments)
61
- .map(([argumentName, argumentValue]) => convertValueToString(argumentName, component.argTypes[argumentName], argumentValue, false));
62
- let argsString = argStrings.join(' ');
63
- let openTag = `<${component.fullName}`;
64
- if (argsString.length > 0) {
65
- openTag += ' ' + argsString;
66
- }
67
- if (openTag.length > 80) {
68
- argsString = argStrings.join('\n ');
69
- openTag = `<${component.fullName} \n ${argsString}\n`;
70
- }
71
- return openTag;
72
- }
73
-
74
- const SLOT_PREFIX = 'slot____';
75
-
76
- type ArgumentValue = {
77
- value: unknown;
78
- } | {
79
- variableName: string;
80
- };
81
-
82
- type Arguments = Record<string, ArgumentValue>;
83
- type Slots = Record<string, string>;
84
- interface ComponentData { viewHelperArguments: Arguments; slots: Slots }
85
-
86
- function createComponentData(component: FluidComponent, args: StrictArgs): ComponentData {
87
- const viewHelperArguments: Arguments = {};
88
- const slots: Slots = {};
89
-
90
- for (const [key, value] of Object.entries(args)) {
91
- if (key.startsWith(SLOT_PREFIX)) {
92
- // This is a slot
93
- const slotName = key.replace(SLOT_PREFIX, '');
94
- if (value === null || value === undefined || value === '') {
95
- continue;
96
- }
97
-
98
- slots[slotName] = value as string;
99
- continue;
100
- }
101
-
102
- if (!key.includes('__')) {
103
- // This is a normal argument without virtual subkey
104
- viewHelperArguments[key] = { value };
105
- continue;
106
- }
107
-
108
- // This is a virtual argument
109
- const [variableName] = key.split('__');
110
- viewHelperArguments[variableName] = { variableName };
111
- }
112
-
113
- return { viewHelperArguments, slots };
114
- }
115
-
116
- export function convertComponentToSource(component: FluidComponent, args: StrictArgs): string {
117
- const { viewHelperArguments, slots } = createComponentData(component, args);
118
-
119
- let source = '';
120
- source += `<html\n xmlns:${component.namespace}="http://typo3.org/ns/${component.collection.replace(/\\/g, '/')}"\n data-namespace-typo3-fluid="true"\n>\n\n`;
121
-
122
- const usedArgs: string[] = [...new Set(Object.keys(args).filter(key => !key.startsWith(SLOT_PREFIX)).map(key => key.split('__')[0]))];
123
-
124
- source += generateOpenTag(viewHelperArguments, component);
125
-
126
- if (Object.keys(slots).length <= 0) {
127
- source += `/>\n`;
128
- source += `\n<!-- or -->\n\n`;
129
- source += `{namespace ${component.namespace}=${component.collection}}\n\n`;
130
- source += inlineCode(viewHelperArguments, component);
131
- return source;
132
- }
133
- source += '>\n';
134
-
135
- if (Object.keys(slots).length === 1 && slots.default) {
136
- // If there is only a default slot, we can use the inline code
137
- source += ` ${slots.default}\n`;
138
- source += '</' + component.fullName + '>\n';
139
- source += `\n<!-- or -->\n\n`;
140
- source += `{namespace ${component.namespace}=${component.collection}}\n\n`;
141
- source += inlineCode(viewHelperArguments, component, slots.default);
142
- return source;
143
- }
144
-
145
- for (const [slotName, slotValue] of Object.entries(slots)) {
146
- source += ` <f:fragment name="${slotName}">${slotValue}</f:fragment>\n`;
147
- }
148
- source += '</' + component.fullName + '>';
149
-
150
- return source;
151
- }
@@ -1,30 +0,0 @@
1
- import { type FluidComponent, url } from '@andersundsehr/storybook-typo3';
2
-
3
- import { fetchWithUserRetry } from './fetchWithUserRetry.ts';
4
-
5
- interface ComponentMetaData {
6
- collectionClassName: string;
7
- argTypes: Record<string, any>;
8
- }
9
-
10
- export async function fetchComponent(component: string): Promise<FluidComponent> {
11
- if (!component.includes(':')) {
12
- const message = 'Component name must be in the format "namespace:name"';
13
- alert(message);
14
- throw new Error(message);
15
- }
16
-
17
- const data = await fetchWithUserRetry<ComponentMetaData>(
18
- url + '/_storybook/componentMeta?viewHelper=' + component,
19
- {},
20
- 'metadata for component `' + component + '` from TYPO3',
21
- );
22
-
23
- return {
24
- fullName: component,
25
- name: component.split(':')[1],
26
- namespace: component.split(':')[0],
27
- collection: data.collectionClassName,
28
- argTypes: data.argTypes,
29
- };
30
- }
@@ -1,86 +0,0 @@
1
- import { url } from '@andersundsehr/storybook-typo3';
2
- import { fetchWithUserRetry } from './fetchWithUserRetry.ts';
3
- import type { GlobalTypes, InputType } from 'storybook/internal/types';
4
- import { GLOBALS_UPDATED, SET_GLOBALS } from 'storybook/internal/core-events';
5
- import { addons } from 'storybook/preview-api';
6
-
7
- function getLocation(): Location {
8
- let location = window.location;
9
- if (window.parent?.location && window.parent !== window) {
10
- location = window.parent.location;
11
- }
12
- return location;
13
- }
14
-
15
- function getGlobalsFromUrl(): Record<string, string> {
16
- const url = new URL(getLocation().href);
17
- const globalsString = url.searchParams.get('globals') || '';
18
- const globalsArray = globalsString.split(';').filter(Boolean);
19
- const entries = globalsArray.map(global => global.split(':'));
20
- return Object.fromEntries(entries);
21
- }
22
-
23
- function setGlobalsToUrl(globals: Record<string, string>) {
24
- const location = getLocation();
25
- const url = new URL(location.href);
26
- const globalString = Object.entries(globals).map(([key, value]) => `${key}:${value}`).join(';');
27
- url.searchParams.set('globals', globalString);
28
- location.href = url.toString();
29
- }
30
-
31
- interface PreviewConfig {
32
- globalTypes: GlobalTypes;
33
- initialGlobals: Record<string, string>;
34
- }
35
-
36
- export async function fetchPreviewConfig(currentGlobals?: Record<string, string>): Promise<PreviewConfig> {
37
- const apiEndpoint = url + '/_storybook/preview';
38
-
39
- const globals = currentGlobals || getGlobalsFromUrl();
40
-
41
- return await fetchWithUserRetry<PreviewConfig>(
42
- apiEndpoint,
43
- {
44
- method: 'POST',
45
- headers: {
46
- 'Content-Type': 'application/json',
47
- },
48
- body: JSON.stringify({ globals }),
49
- },
50
- 'preview config from TYPO3',
51
- );
52
- }
53
-
54
- export function initGlobalsHandling(initalGlobalTypes: GlobalTypes) {
55
- const channel = addons.getChannel();
56
- let oldGlobalTypes = initalGlobalTypes;
57
- channel.on(GLOBALS_UPDATED, async ({ globals }: { globals: Record<string, string> }) => {
58
- const previewConfig = await fetchPreviewConfig(globals);
59
-
60
- oldGlobalTypes = previewConfig.globalTypes;
61
-
62
- const newGlobals: Record<string, string> = getGlobalsFromUrl();
63
-
64
- let changed = false;
65
- for (const key in previewConfig.globalTypes) {
66
- const inputType: InputType = previewConfig.globalTypes[key];
67
- if (!inputType.toolbar?.items?.some(item => item.value === globals[key])) {
68
- newGlobals[key] = inputType.toolbar?.items?.[0]?.value || '';
69
- changed = true;
70
- }
71
- }
72
-
73
- if (changed) {
74
- // change URL search params and reload page (this is the only way to set the globals, that worked)!!
75
- setGlobalsToUrl(newGlobals);
76
- return;
77
- }
78
-
79
- if (JSON.stringify(oldGlobalTypes) !== JSON.stringify(previewConfig.globalTypes)) {
80
- // this only sets the globalTypes and not the globals :(
81
- // see https://github.com/storybookjs/storybook/blob/3ac2fece4c41955e7349f6f825b7d21dd18ff9a9/code/core/src/manager-api/modules/globals.ts#L141-L149
82
- channel.emit(SET_GLOBALS, { globals, globalTypes: previewConfig.globalTypes });
83
- return;
84
- }
85
- });
86
- }
@@ -1,19 +0,0 @@
1
- import { url, type StoryContext } from '@andersundsehr/storybook-typo3';
2
-
3
- import { fetchWithUserRetry } from './fetchWithUserRetry.ts';
4
-
5
- export async function fetchRenderAction(urlA: string, id: string, params: any, storyContext: StoryContext): Promise<string> {
6
- const viewHelper = (typeof storyContext.component === 'string') ? storyContext.component : storyContext.component.fullName;
7
-
8
- const body = {
9
- viewHelper: viewHelper,
10
- arguments: params,
11
- site: storyContext.globals?.site || 'default',
12
- siteLanguage: storyContext.globals?.language || 'default',
13
- };
14
-
15
- return await fetchWithUserRetry<string>(url + '/_storybook/render', {
16
- method: 'POST',
17
- body: JSON.stringify(body),
18
- }, 'rendering component in TYPO3', 'text');
19
- };
@@ -1,24 +0,0 @@
1
- export async function fetchWithUserRetry<T>(url: string, options: RequestInit, message: string, result: 'json' | 'text' = 'json'): Promise<T> {
2
- options = { ...options }; // Clone options to avoid mutating the original
3
- options.signal = options.signal || AbortSignal.timeout(5000);
4
- try {
5
- const response = await fetch(url, options);
6
- if (result === 'text') {
7
- return await response.text() as T;
8
- }
9
- return await response.json() as T;
10
- } catch (error) {
11
- console.error('Fetch failed:', { error });
12
- const retry = confirm(
13
- 'Error while fetching ' + message + '. \n\n'
14
- + 'fetch: ' + url + '\n'
15
- + 'ERROR: ' + String(error) + '\n\n'
16
- + 'look at the console for more details.\n\n'
17
- + 'Do you want to retry?',
18
- );
19
- if (retry) {
20
- return fetchWithUserRetry(url, options, message);
21
- }
22
- throw error; // Re-throw the error if the user does not want to retry
23
- }
24
- }
package/src/index.ts DELETED
@@ -1,23 +0,0 @@
1
- export * from './functions/fetchRenderAction.ts';
2
- export * from './functions/fetchPreviewConfig.ts';
3
- export * from './functions/fetchComponent';
4
- export * from './functions/fetchWithUserRetry.ts';
5
- export * from './types/types';
6
-
7
- const url = (() => {
8
- let url = import.meta.env.STORYBOOK_TYPO3_ENDPOINT;
9
-
10
- if (typeof url !== 'string' || !url) {
11
- throw new Error('env STORYBOOK_TYPO3_ENDPOINT is not set or is not a string');
12
- }
13
-
14
- url = url.replace(/_storybook\/?$/, '');
15
- url = url.replace(/\/$/, '');
16
-
17
- if (typeof url !== 'string' || !url) {
18
- throw new Error('env STORYBOOK_TYPO3_ENDPOINT is not set or is not a string');
19
- }
20
- return url;
21
- })();
22
-
23
- export { url };