@soleil-se/config-svelte 1.30.4 → 1.31.0
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.
- package/CHANGELOG.md +7 -0
- package/components/ImageSelector/Component.svelte +35 -20
- package/components/ImageSelector/i18n.js +9 -0
- package/components/RadioGroup/Component.svelte +1 -1
- package/components/RepositoryNodeSelector/Component.svelte +26 -21
- package/components/RepositoryNodeSelector/i18n.js +6 -3
- package/components/TextList/Component.svelte +1 -0
- package/jsconfig.json +3 -0
- package/package.json +14 -13
- package/svelte.config.cjs +6 -0
- package/types/components/RadioGroup/Component.svelte.d.ts +1 -1
- package/types/components/TextList/Component.svelte.d.ts +1 -1
- package/types/utils/fetchRestApi.d.ts +50 -0
- package/types/utils/index.d.ts +1 -0
- package/utils/fetchRestApi.js +123 -0
- package/utils/index.js +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,13 @@ All notable changes to this project will be documented in this file.
|
|
|
8
8
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
9
9
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
10
10
|
|
|
11
|
+
## [1.31.0] - 2026-04-10
|
|
12
|
+
|
|
13
|
+
- Add error messages for when fetching data for `ImageSelector` and `RepositoryNodeSelector` fails due to lack of permissions or REST-API being disabled or restricted.
|
|
14
|
+
- New `fetchRestApi` utility function for fetching data from the REST-API with proper error handling.
|
|
15
|
+
- Add missing default slot for `TextList` component.
|
|
16
|
+
- Set label as `undefined` correclty in `RadioGroup`.
|
|
17
|
+
|
|
11
18
|
## [1.30.4] - 2026-02-26
|
|
12
19
|
|
|
13
20
|
- Do not set focus on hidden inputs in `Modal` component.
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
<!-- prettier-ignore -->
|
|
2
1
|
<script>
|
|
3
2
|
import { createEventDispatcher } from 'svelte';
|
|
4
3
|
|
|
5
|
-
import { triggerInput } from '../../utils';
|
|
4
|
+
import { triggerInput, fetchRestApi } from '../../utils';
|
|
6
5
|
import i18n from './i18n';
|
|
7
6
|
import Panel from '../Panel';
|
|
8
7
|
import NodeSelector from '../NodeSelector';
|
|
@@ -21,7 +20,7 @@
|
|
|
21
20
|
export let node = undefined;
|
|
22
21
|
/** @type {string} */
|
|
23
22
|
export let alt;
|
|
24
|
-
alt = name ? values[`${name}Alt`] ?? alt : alt;
|
|
23
|
+
alt = name ? (values[`${name}Alt`] ?? alt) : alt;
|
|
25
24
|
/** @type {boolean} */
|
|
26
25
|
export let hideDecorative = false;
|
|
27
26
|
/** @type {boolean} */
|
|
@@ -34,17 +33,27 @@
|
|
|
34
33
|
let useCustomAlt = !!alt;
|
|
35
34
|
let customAlt = alt;
|
|
36
35
|
let validationEl;
|
|
36
|
+
let fetchError = null;
|
|
37
37
|
|
|
38
38
|
const dispatch = createEventDispatcher();
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
function setCustomValidity() {
|
|
41
41
|
validationEl?.setCustomValidity(imageArchiveAlt ? '' : i18n('validation'));
|
|
42
|
-
}
|
|
42
|
+
}
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
async function fetchImageAlt() {
|
|
45
|
+
fetchError = null;
|
|
46
|
+
if (node && !decorative && !useCustomAlt) {
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetchRestApi(`${node}/properties`, {
|
|
49
|
+
data: { properties: ['alt'] },
|
|
50
|
+
});
|
|
51
|
+
imageArchiveAlt = response?.alt || '';
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(error);
|
|
54
|
+
fetchError = error;
|
|
55
|
+
imageArchiveAlt = i18n('couldNotGetAlt');
|
|
56
|
+
}
|
|
48
57
|
|
|
49
58
|
setCustomValidity();
|
|
50
59
|
} else {
|
|
@@ -52,12 +61,13 @@
|
|
|
52
61
|
validationEl?.setCustomValidity('');
|
|
53
62
|
}
|
|
54
63
|
triggerInput(validationEl);
|
|
55
|
-
|
|
56
64
|
dispatch('input', { node, alt });
|
|
57
65
|
dispatch('change', { node, alt });
|
|
58
|
-
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function onUseCustomToggle() {
|
|
69
|
+
fetchImageAlt();
|
|
59
70
|
|
|
60
|
-
const onUseCustomToggle = () => {
|
|
61
71
|
if (useCustomAlt) {
|
|
62
72
|
alt = customAlt || '';
|
|
63
73
|
} else {
|
|
@@ -65,8 +75,7 @@
|
|
|
65
75
|
alt = undefined;
|
|
66
76
|
setCustomValidity();
|
|
67
77
|
}
|
|
68
|
-
}
|
|
69
|
-
|
|
78
|
+
}
|
|
70
79
|
</script>
|
|
71
80
|
|
|
72
81
|
<!--
|
|
@@ -77,6 +86,12 @@
|
|
|
77
86
|
-->
|
|
78
87
|
|
|
79
88
|
<Panel {heading} {show}>
|
|
89
|
+
{#if fetchError}
|
|
90
|
+
<p class="alert alert-danger" role="alert">
|
|
91
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
92
|
+
{@html i18n(fetchError.status === 403 ? 'fetchError403' : 'fetchError')}
|
|
93
|
+
</p>
|
|
94
|
+
{/if}
|
|
80
95
|
<NodeSelector
|
|
81
96
|
name="{name}Node"
|
|
82
97
|
label={i18n('selectImage')}
|
|
@@ -84,36 +99,36 @@
|
|
|
84
99
|
selectable="sv:image"
|
|
85
100
|
type="image"
|
|
86
101
|
bind:value={node}
|
|
87
|
-
on:change={
|
|
102
|
+
on:change={fetchImageAlt}
|
|
88
103
|
/>
|
|
89
104
|
<div class="form-group">
|
|
90
105
|
{#if !hideDecorative}
|
|
91
106
|
<Checkbox
|
|
92
107
|
name="{name}Decorative"
|
|
93
|
-
class="checkbox-group"
|
|
94
108
|
label={i18n('imageIsDecorative')}
|
|
109
|
+
class="checkbox-group"
|
|
95
110
|
bind:value={decorative}
|
|
96
|
-
on:change={
|
|
111
|
+
on:change={fetchImageAlt}
|
|
97
112
|
/>
|
|
98
113
|
{/if}
|
|
99
114
|
{#if !decorative}
|
|
100
115
|
{#if useCustomAlt}
|
|
101
116
|
<InputField name="{name}Alt" label={i18n('altText')} required bind:value={alt} />
|
|
102
117
|
{:else}
|
|
103
|
-
<InputField
|
|
118
|
+
<InputField label={i18n('altFromCaption')} disabled readonly value={imageArchiveAlt}>
|
|
104
119
|
<input bind:this={validationEl} class="sr-only" />
|
|
105
120
|
</InputField>
|
|
106
121
|
{/if}
|
|
107
122
|
|
|
108
123
|
<Checkbox
|
|
109
|
-
class="checkbox-group"
|
|
110
124
|
label={i18n('customAltText')}
|
|
125
|
+
class="checkbox-group"
|
|
111
126
|
bind:value={useCustomAlt}
|
|
112
127
|
on:change={onUseCustomToggle}
|
|
113
128
|
/>
|
|
114
129
|
{/if}
|
|
115
130
|
</div>
|
|
116
|
-
<slot
|
|
131
|
+
<slot></slot>
|
|
117
132
|
</Panel>
|
|
118
133
|
|
|
119
134
|
<style>
|
|
@@ -8,7 +8,10 @@ export default createI18n({
|
|
|
8
8
|
altFromCaption: 'Alt-text (från bildbeskrivning)',
|
|
9
9
|
customAltText: 'Ange egen alt-text',
|
|
10
10
|
validation: 'Bilden saknar beskrivning (alt-text). Klicka på bilden ovan och skriv texten i fältet Bildbeskrivning.',
|
|
11
|
+
couldNotGetAlt: 'Det gick inte att hämta bildens alt-text...',
|
|
11
12
|
imageIsDecorative: 'Bilden är dekorativ',
|
|
13
|
+
fetchError: '<strong>Fel:</strong> Det gick inte att hämta bildens egenskaper.',
|
|
14
|
+
fetchError403: '<strong>Fel:</strong> Det gick inte att hämta bildens egenskaper, antingen har du inte behörighet till bilden eller så är REST-API:et inaktiverat eller begränsat. Kontrollera behörigheter och webbplatsinställningar.',
|
|
12
15
|
},
|
|
13
16
|
no: {
|
|
14
17
|
heading: 'Bilde',
|
|
@@ -17,7 +20,10 @@ export default createI18n({
|
|
|
17
20
|
altFromCaption: 'Alt-tekst (fra bildebeskrivelse)',
|
|
18
21
|
customAltText: 'Skriv inn din egen alt-tekst',
|
|
19
22
|
validation: 'Bildet mangler beskrivelse (alt-tekst). Klikk på bildet over og skriv inn teksten i feltet Bildebeskrivelse.',
|
|
23
|
+
couldNotGetAlt: 'Det gikk ikke å hente bildets alt-tekst...',
|
|
20
24
|
imageIsDecorative: 'Bildet er dekorativt',
|
|
25
|
+
fetchError: '<strong>Feil:</strong> Det gikk ikke å hente bildets egenskaper.',
|
|
26
|
+
fetchError403: '<strong>Feil:</strong> Det gikk ikke å hente bildets egenskaper, enten har du ikke behørighet til bildet eller så er REST-API:et inaktivert eller begrenset. Kontroller behørigheter og nettstedsinnstillinger.',
|
|
21
27
|
},
|
|
22
28
|
en: {
|
|
23
29
|
heading: 'Image',
|
|
@@ -26,6 +32,9 @@ export default createI18n({
|
|
|
26
32
|
altFromCaption: 'Alt text (from image caption)',
|
|
27
33
|
customAltText: 'Use custom alt text',
|
|
28
34
|
validation: 'The image lacks description (alt-text). Click on the image above and enter the text in the Image description field.',
|
|
35
|
+
couldNotGetAlt: 'Could not fetch the image alt text...',
|
|
29
36
|
imageIsDecorative: 'Image is decorative',
|
|
37
|
+
fetchError: '<strong>Error:</strong> An error occurred while fetching the image properties.',
|
|
38
|
+
fetchError403: '<strong>Error:</strong> An error occurred while fetching the image properties, you either do not have permission to see the image or the REST-API is not enabled or restricted. Check permissions and site settings.',
|
|
30
39
|
},
|
|
31
40
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
<!-- prettier-ignore -->
|
|
2
1
|
<script>
|
|
3
2
|
import { onMount } from 'svelte';
|
|
4
|
-
import { getAppContext } from '../../utils';
|
|
3
|
+
import { getAppContext, fetchRestApi } from '../../utils';
|
|
5
4
|
import CustomSelector from '../CustomSelector';
|
|
6
5
|
import i18n from './i18n';
|
|
7
6
|
|
|
@@ -29,7 +28,7 @@
|
|
|
29
28
|
export let type;
|
|
30
29
|
|
|
31
30
|
let options = [];
|
|
32
|
-
let
|
|
31
|
+
let fetchError = null;
|
|
33
32
|
|
|
34
33
|
function toTitleCase(str) {
|
|
35
34
|
return str
|
|
@@ -43,23 +42,27 @@
|
|
|
43
42
|
repository = !repository.endsWith('Repository') ? `${repository} Repository` : repository;
|
|
44
43
|
const endpoint = repository.includes('Alias') ? 'aliases' : 'nodes';
|
|
45
44
|
|
|
46
|
-
return
|
|
45
|
+
return `${siteName}/${repository}/${endpoint}`;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
onMount(async () => {
|
|
50
49
|
const endpoint = getEndpoint();
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
50
|
+
try {
|
|
51
|
+
fetchError = null;
|
|
52
|
+
const result = await fetchRestApi(endpoint);
|
|
53
|
+
options = (result || []).map((item) => ({ label: item.name, value: item.id }));
|
|
54
|
+
} catch (error) {
|
|
55
|
+
fetchError = error;
|
|
56
|
+
if (error.status === 403) {
|
|
57
|
+
console.error(
|
|
58
|
+
`Error (${error.status}) - Unable to fetch nodes, check permissions and if REST-API is enabled.`,
|
|
57
59
|
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
} else {
|
|
61
|
+
console.error(
|
|
62
|
+
`Error (${error.status}) - Unable to fetch nodes, is site name and repository correct?${error.message ? ` ${error.message}` : ''}`,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
63
66
|
});
|
|
64
67
|
</script>
|
|
65
68
|
|
|
@@ -73,9 +76,9 @@
|
|
|
73
76
|
<CustomSelector
|
|
74
77
|
{id}
|
|
75
78
|
{name}
|
|
79
|
+
{label}
|
|
76
80
|
{description}
|
|
77
81
|
{disabled}
|
|
78
|
-
{label}
|
|
79
82
|
{multiple}
|
|
80
83
|
{options}
|
|
81
84
|
{required}
|
|
@@ -85,11 +88,13 @@
|
|
|
85
88
|
on:input
|
|
86
89
|
on:change
|
|
87
90
|
>
|
|
88
|
-
<slot name="label"
|
|
89
|
-
<slot
|
|
90
|
-
{#if
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
<slot name="label"></slot>
|
|
92
|
+
<slot></slot>
|
|
93
|
+
{#if fetchError}
|
|
94
|
+
<p class="alert alert-danger" role="alert">
|
|
95
|
+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
|
96
|
+
{@html i18n(fetchError.status === 403 ? 'fetchError403' : 'fetchError')}
|
|
97
|
+
</p>
|
|
93
98
|
{/if}
|
|
94
99
|
</CustomSelector>
|
|
95
100
|
|
|
@@ -2,12 +2,15 @@ import createI18n from '../../utils/createI18n';
|
|
|
2
2
|
|
|
3
3
|
export default createI18n({
|
|
4
4
|
sv: {
|
|
5
|
-
|
|
5
|
+
fetchError: '<strong>Fel:</strong> Det gick inte att hämta noder, se konsolen för mer information.',
|
|
6
|
+
fetchError403: '<strong>Fel:</strong> Det gick inte att hämta noder, antingen har du inte rätt behörighet eller så är REST-API:et inaktiverat eller begränsat. Kontrollera behörigheter och webbplatsinställningar.',
|
|
6
7
|
},
|
|
7
8
|
no: {
|
|
8
|
-
|
|
9
|
+
fetchError: '<strong>Feil:</strong> Noder kunne ikke hentes, se konsollen for mer informasjon.',
|
|
10
|
+
fetchError403: '<strong>Feil:</strong> Noder kunne ikke hentes, enten har du ikke riktig behørighet eller så er REST-API:et inaktivert eller begrenset. Kontroller behørigheter og nettstedsinnstillinger.',
|
|
9
11
|
},
|
|
10
12
|
en: {
|
|
11
|
-
|
|
13
|
+
fetchError: '<strong>Error:</strong> Nodes could not be fetched, see console for more information.',
|
|
14
|
+
fetchError403: '<strong>Error:</strong> Nodes could not be fetched, you either do not have the correct permissions or the REST-API is not enabled or restricted. Check permissions and site settings.',
|
|
12
15
|
},
|
|
13
16
|
});
|
package/jsconfig.json
ADDED
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"svelte"
|
|
10
10
|
],
|
|
11
11
|
"homepage": "https://docs.soleil.se/packages/config-svelte",
|
|
12
|
-
"version": "1.
|
|
12
|
+
"version": "1.31.0",
|
|
13
13
|
"main": "./index.js",
|
|
14
14
|
"module": "./index.js",
|
|
15
15
|
"svelte": "./index.js",
|
|
@@ -39,23 +39,24 @@
|
|
|
39
39
|
"svelte": "^3.46.0 || ^4.0.0 || ^5.0.0-next || ^5.0.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@soleil-se/eslint-config": "^6.
|
|
43
|
-
"@soleil-se/stylelint-config": "^4.
|
|
44
|
-
"eslint": "^9.
|
|
45
|
-
"glob": "^11.
|
|
46
|
-
"sass": "^1.
|
|
47
|
-
"stylelint": "^16.
|
|
48
|
-
"sveld": "^0.22.
|
|
49
|
-
"svelte": "^4.2.
|
|
42
|
+
"@soleil-se/eslint-config": "^6.3.1",
|
|
43
|
+
"@soleil-se/stylelint-config": "^4.4.0",
|
|
44
|
+
"eslint": "^9.39.4",
|
|
45
|
+
"glob": "^11.1.0",
|
|
46
|
+
"sass": "^1.99.0",
|
|
47
|
+
"stylelint": "^16.26.1",
|
|
48
|
+
"sveld": "^0.22.5",
|
|
49
|
+
"svelte": "^4.2.20",
|
|
50
50
|
"svelte-preprocess": "^6.0.3"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@soleil-se/config-validate": "^1.
|
|
54
|
-
"a11y-dialog": "8.1.
|
|
55
|
-
"svelte-dnd-action": "^0.9.
|
|
53
|
+
"@soleil-se/config-validate": "^1.3.0",
|
|
54
|
+
"a11y-dialog": "8.1.5",
|
|
55
|
+
"svelte-dnd-action": "^0.9.69"
|
|
56
56
|
},
|
|
57
57
|
"scripts": {
|
|
58
58
|
"create-type-definitions": "node ./createTypeDefinitions.js"
|
|
59
59
|
},
|
|
60
|
-
"private": false
|
|
60
|
+
"private": false,
|
|
61
|
+
"gitHead": "eb052ae65db1ed6a372ee25894719d587368e80d"
|
|
61
62
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetches JSON data from the REST-API.
|
|
3
|
+
* @template T
|
|
4
|
+
* @param {string} uri - The URI to fetch JSON data from.
|
|
5
|
+
* @param {Options} [options] - The options for the fetch request with some extensions.
|
|
6
|
+
* @returns {Promise<T>} A promise that resolves to the JSON response.
|
|
7
|
+
* @throws {FetchError} If the response is not successful or cannot be parsed as JSON.
|
|
8
|
+
*/
|
|
9
|
+
export default function fetchRestApi<T>(uri: string, options?: Options): Promise<T>;
|
|
10
|
+
/**
|
|
11
|
+
* @typedef {Object} ExtensionOptions
|
|
12
|
+
* @property {{ [key: string]: unknown }} [data] - The data to be included in the request as JSON.
|
|
13
|
+
* @property {'offline' | 'online'} [model] - Selects which REST API model path to use.
|
|
14
|
+
*
|
|
15
|
+
* @typedef {RequestInit & ExtensionOptions} Options
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Custom error class for fetch-related errors with additional HTTP context.
|
|
19
|
+
*/
|
|
20
|
+
export class FetchError extends Error {
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} message - The error message.
|
|
23
|
+
* @param {Object} [options] - Error options.
|
|
24
|
+
* @param {number} [options.status] - HTTP status code.
|
|
25
|
+
* @param {boolean} [options.aborted=false] - Whether the request was aborted.
|
|
26
|
+
* @param {Object.<string, unknown>} [options.additionalProps] - Additional properties.
|
|
27
|
+
*/
|
|
28
|
+
constructor(message: string, { status, aborted, ...additionalProps }?: {
|
|
29
|
+
status?: number;
|
|
30
|
+
aborted?: boolean;
|
|
31
|
+
additionalProps?: {
|
|
32
|
+
[x: string]: unknown;
|
|
33
|
+
};
|
|
34
|
+
});
|
|
35
|
+
status: number;
|
|
36
|
+
aborted: boolean;
|
|
37
|
+
}
|
|
38
|
+
export type ExtensionOptions = {
|
|
39
|
+
/**
|
|
40
|
+
* - The data to be included in the request as JSON.
|
|
41
|
+
*/
|
|
42
|
+
data?: {
|
|
43
|
+
[key: string]: unknown;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* - Selects which REST API model path to use.
|
|
47
|
+
*/
|
|
48
|
+
model?: "offline" | "online";
|
|
49
|
+
};
|
|
50
|
+
export type Options = RequestInit & ExtensionOptions;
|
package/types/utils/index.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export { default as triggerInput } from "./triggerInput";
|
|
|
6
6
|
export { default as createI18n } from "./createI18n";
|
|
7
7
|
export { default as getAppProps } from "./getAppProps";
|
|
8
8
|
export { default as getAppContext } from "./getAppContext";
|
|
9
|
+
export { default as fetchRestApi } from "./fetchRestApi";
|
|
9
10
|
export { generateId, addGeneratedIds } from "./generateId";
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} ExtensionOptions
|
|
3
|
+
* @property {{ [key: string]: unknown }} [data] - The data to be included in the request as JSON.
|
|
4
|
+
* @property {'offline' | 'online'} [model] - Selects which REST API model path to use.
|
|
5
|
+
*
|
|
6
|
+
* @typedef {RequestInit & ExtensionOptions} Options
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Custom error class for fetch-related errors with additional HTTP context.
|
|
11
|
+
*/
|
|
12
|
+
export class FetchError extends Error {
|
|
13
|
+
/**
|
|
14
|
+
* @param {string} message - The error message.
|
|
15
|
+
* @param {Object} [options] - Error options.
|
|
16
|
+
* @param {number} [options.status] - HTTP status code.
|
|
17
|
+
* @param {boolean} [options.aborted=false] - Whether the request was aborted.
|
|
18
|
+
* @param {Object.<string, unknown>} [options.additionalProps] - Additional properties.
|
|
19
|
+
*/
|
|
20
|
+
constructor(message, { status, aborted = false, ...additionalProps } = {}) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'FetchError';
|
|
23
|
+
this.status = status;
|
|
24
|
+
this.aborted = aborted;
|
|
25
|
+
|
|
26
|
+
// Add any additional properties from the error response
|
|
27
|
+
Object.entries(additionalProps).forEach(([key, value]) => {
|
|
28
|
+
if (key !== 'message' && key !== 'status' && key !== 'aborted') {
|
|
29
|
+
this[key] = value;
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Attempts to parse response text as JSON.
|
|
37
|
+
* @param {Response} response - The fetch Response object.
|
|
38
|
+
* @returns {Promise<unknown | undefined>} The parsed JSON object or undefined if parsing fails.
|
|
39
|
+
*/
|
|
40
|
+
async function toJson(response) {
|
|
41
|
+
const text = await response.text();
|
|
42
|
+
try {
|
|
43
|
+
return JSON.parse(text);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Creates a FetchError object from a failed response, including JSON error details.
|
|
51
|
+
* @param {Response} response - The failed fetch Response object.
|
|
52
|
+
* @returns {Promise<FetchError>} A FetchError with additional properties from the response JSON.
|
|
53
|
+
*/
|
|
54
|
+
async function responseError(response) {
|
|
55
|
+
const json = await toJson(response) || {};
|
|
56
|
+
const message = json?.message || response?.statusText;
|
|
57
|
+
return new FetchError(message, {
|
|
58
|
+
status: response.status,
|
|
59
|
+
aborted: false,
|
|
60
|
+
...json,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Handles the fetch response, throwing errors for non-OK responses and parsing JSON.
|
|
66
|
+
* @param {Response} response - The fetch Response object.
|
|
67
|
+
* @returns {Promise<unknown>} The parsed JSON response.
|
|
68
|
+
* @throws {FetchError} If the response is not OK or cannot be parsed as JSON.
|
|
69
|
+
*/
|
|
70
|
+
async function handleResponse(response) {
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw await responseError(response);
|
|
73
|
+
}
|
|
74
|
+
const json = await toJson(response);
|
|
75
|
+
if (json === undefined) {
|
|
76
|
+
throw new FetchError('Response could not be parsed as JSON.');
|
|
77
|
+
}
|
|
78
|
+
return json;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Maps the model option to the corresponding query parameter value for the REST-API.
|
|
83
|
+
* @param {string} model - The model type, either 'offline' or 'online'.
|
|
84
|
+
* @returns {string} The corresponding path parameter value for the REST-API.
|
|
85
|
+
* @throws {Error} If an unknown model type is provided.
|
|
86
|
+
*/
|
|
87
|
+
function getModelPathParameter(model) {
|
|
88
|
+
switch (model) {
|
|
89
|
+
case 'offline':
|
|
90
|
+
return '0';
|
|
91
|
+
case 'online':
|
|
92
|
+
return '1';
|
|
93
|
+
default:
|
|
94
|
+
throw new Error(`Unknown model: ${model}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Fetches JSON data from the REST-API.
|
|
100
|
+
* @template T
|
|
101
|
+
* @param {string} uri - The URI to fetch JSON data from.
|
|
102
|
+
* @param {Options} [options] - The options for the fetch request with some extensions.
|
|
103
|
+
* @returns {Promise<T>} A promise that resolves to the JSON response.
|
|
104
|
+
* @throws {FetchError} If the response is not successful or cannot be parsed as JSON.
|
|
105
|
+
*/
|
|
106
|
+
export default function fetchRestApi(uri, options = {}) {
|
|
107
|
+
const { data = {}, model = 'offline', ...rest } = options;
|
|
108
|
+
|
|
109
|
+
const qs = Object.keys(data).length
|
|
110
|
+
? `${uri.includes('?') ? '&' : '?'}${`format=json&json=${encodeURIComponent(JSON.stringify(data))}`}`
|
|
111
|
+
: '';
|
|
112
|
+
|
|
113
|
+
return fetch(`/rest-api/1/${getModelPathParameter(model)}/${uri}${qs}`, rest)
|
|
114
|
+
.then(handleResponse)
|
|
115
|
+
.catch((error) => {
|
|
116
|
+
// Re-throw FetchError as-is, convert other errors
|
|
117
|
+
if (error instanceof FetchError) {
|
|
118
|
+
return Promise.reject(error);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return Promise.reject(new FetchError(error.message, { status: error.status || 500 }));
|
|
122
|
+
});
|
|
123
|
+
}
|
package/utils/index.js
CHANGED
|
@@ -7,3 +7,4 @@ export { default as triggerInput } from './triggerInput';
|
|
|
7
7
|
export { default as createI18n } from './createI18n';
|
|
8
8
|
export { default as getAppProps } from './getAppProps';
|
|
9
9
|
export { default as getAppContext } from './getAppContext';
|
|
10
|
+
export { default as fetchRestApi } from './fetchRestApi';
|