@soleil-se/config-svelte 1.16.2 → 1.17.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 CHANGED
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.17.0] - 2022-12-01
9
+
10
+ - New component: [RepositoryNodeSelector](./components/RepositoryNodeSelector).
11
+ - New utility API: [getAppContext](./#getappcontext).
12
+ - New server API: [createAppContext](./#context)
13
+
8
14
  ## [1.16.2] - 2022-11-24
9
15
 
10
16
  - [ListSelector](./components/ListSelector) now works properly in [CustomList](./components/CustomList).
@@ -2,12 +2,13 @@
2
2
  import { createEventDispatcher, onMount } from 'svelte';
3
3
  import { generateId, addGeneratedIds } from '../../utils';
4
4
  import { resizer } from '../../actions';
5
+ import i18n from './i18n';
5
6
 
6
7
  import Sortable from './internal/Sortable.svelte';
7
8
  import SortableItem from './internal/SortableItem.svelte';
8
9
  import DataHolder from './internal/DataHolder.svelte';
9
10
  import EditModal from './internal/EditModal.svelte';
10
- import CreateItem from './internal/CreateItem.svelte';
11
+ import AddModal from './internal/AddModal.svelte';
11
12
 
12
13
  const values = window.CONFIG_VALUES || {};
13
14
  const dispatch = createEventDispatcher();
@@ -29,6 +30,15 @@
29
30
  let items = [];
30
31
  let editId;
31
32
  let dragDisabled = disabled;
33
+ let addModalOpen = false;
34
+
35
+ const onAddOpen = () => {
36
+ addModalOpen = true;
37
+ };
38
+
39
+ const onAddClose = () => {
40
+ addModalOpen = false;
41
+ };
32
42
 
33
43
  const updateItems = () => {
34
44
  items = value.map((id) => ({
@@ -43,7 +53,7 @@
43
53
  dispatch('change', value);
44
54
  };
45
55
 
46
- const onCreate = ({ detail }) => {
56
+ const onAddSave = ({ detail }) => {
47
57
  items = items.concat(detail);
48
58
  modals = modals.concat(detail.id);
49
59
  onChange();
@@ -97,6 +107,9 @@
97
107
  <slot {id} {index} />
98
108
  </EditModal>
99
109
  {/each}
110
+ <AddModal {name} open={addModalOpen} on:save={onAddSave} on:close={onAddClose} let:id>
111
+ <slot {id} index={items.length} />
112
+ </AddModal>
100
113
 
101
114
  <div class="form-group" class:hidden={!show}>
102
115
  <label for={domId} class="control-label {required ? 'control-label--required' : ''}">
@@ -116,10 +129,11 @@
116
129
  </Sortable>
117
130
  <div use:resizer class="ui-resizable-handle ui-resizable-s" />
118
131
  </div>
132
+ <button class="list-component__add-item btn btn-link" on:click={onAddOpen} {disabled}>
133
+ <i class="glyphicons plus" aria-hidden="true" />
134
+ {i18n('add')}
135
+ </button>
119
136
 
120
- <CreateItem {name} {disabled} on:create={onCreate} let:id>
121
- <slot {id} index={items.length} />
122
- </CreateItem>
123
137
  {#if disabled}
124
138
  <div class="disabled-overlay" />
125
139
  {/if}
@@ -148,4 +162,16 @@
148
162
  cursor: not-allowed;
149
163
  background-color: rgb(255 255 255 / 30%);
150
164
  }
165
+
166
+ button {
167
+ &:focus,
168
+ &:active {
169
+ color: #585858;
170
+ outline: none;
171
+ }
172
+
173
+ &:focus-visible {
174
+ outline: 2px solid #66afe9;
175
+ }
176
+ }
151
177
  </style>
@@ -3,17 +3,17 @@ import createI18n from '../../utils/createI18n';
3
3
  export default createI18n({
4
4
  sv: {
5
5
  add: 'Lägg till...',
6
- validation: 'Lägg till minst en post i listan',
6
+ validation: 'Lägg till minst en post i listan.',
7
7
  edit: 'Redigera',
8
8
  },
9
9
  no: {
10
10
  add: 'Legg till...',
11
- validation: 'Legg til minst ett element i listen',
11
+ validation: 'Legg til minst ett element i listen.',
12
12
  edit: 'Redigere',
13
13
  },
14
14
  en: {
15
15
  add: 'Add...',
16
- validation: 'Add at least one item to the list',
16
+ validation: 'Add at least one item to the list.',
17
17
  edit: 'Edit',
18
18
  },
19
19
  });
@@ -6,26 +6,22 @@
6
6
  import Modal from '../../Modal';
7
7
 
8
8
  export let name;
9
- export let disabled;
9
+ export let open = false;
10
10
 
11
11
  let id;
12
12
  let element;
13
- let open = false;
14
13
 
15
14
  const dispatch = createEventDispatcher();
16
15
 
17
- const addItem = async () => {
18
- open = true;
19
- id = generateId(name || 'item');
20
- };
16
+ $: if (open) id = generateId(name || 'item');
21
17
 
22
18
  const onClose = () => {
23
- open = false;
19
+ dispatch('close');
24
20
  setTimeout(() => (id = undefined), 300);
25
21
  };
26
22
 
27
23
  const onSave = async () => {
28
- dispatch('create', {
24
+ dispatch('save', {
29
25
  id,
30
26
  name: element.querySelector('input')?.value || id,
31
27
  });
@@ -38,22 +34,3 @@
38
34
  <slot {id} />
39
35
  {/if}
40
36
  </Modal>
41
-
42
- <button class="list-component__add-item btn btn-link" on:click={addItem} {disabled}>
43
- <i class="glyphicons plus" aria-hidden="true" />
44
- {i18n('add')}
45
- </button>
46
-
47
- <style lang="scss">
48
- button {
49
- &:focus,
50
- &:active {
51
- color: #585858;
52
- outline: none;
53
- }
54
-
55
- &:focus-visible {
56
- outline: 2px solid #66afe9;
57
- }
58
- }
59
- </style>
@@ -1,5 +1,5 @@
1
1
  <script>
2
- import { createEventDispatcher, onMount } from 'svelte';
2
+ import { createEventDispatcher, onMount, tick } from 'svelte';
3
3
  import { generateId, setupComponent } from '../../utils';
4
4
 
5
5
  const values = window.CONFIG_VALUES || {};
@@ -10,7 +10,6 @@
10
10
  export let name = undefined;
11
11
  export let options = [];
12
12
  export let searchable = false;
13
- export let placeholder;
14
13
  export let required = false;
15
14
  export let disabled = false;
16
15
  export let multiple = false;
@@ -18,21 +17,29 @@
18
17
 
19
18
  export let value = multiple ? [] : '';
20
19
 
21
- value = name ? values[name] ?? value : value;
22
- // value = value === Object(value) ? value.value : value;
20
+ value = name ? values[name]?.value ?? values[name] ?? value : value;
21
+ value = Array.isArray(value) ? value.map((v) => v.value ?? v) : value;
23
22
 
24
23
  let className = '';
25
24
  export { className as class };
26
25
 
26
+ let element;
27
+
28
+ async function triggerChange() {
29
+ await tick();
30
+ jQuery(element).val(value).trigger('change');
31
+ }
32
+
33
+ $: if (element && options) triggerChange();
34
+
27
35
  onMount(() => {
28
- const [el] = setupComponent(id)
29
- .on('change', (e) => {
30
- value = e.val || value;
36
+ [element] = setupComponent(id).on('change', (e) => {
37
+ if (e.val !== undefined && value !== e.val) {
38
+ value = e.val;
31
39
  dispatch('input', value);
32
40
  dispatch('change', value);
33
- });
34
-
35
- jQuery(el).val(value).trigger('change');
41
+ }
42
+ });
36
43
  });
37
44
  </script>
38
45
 
@@ -54,7 +61,6 @@
54
61
  data-component="custom-selector"
55
62
  data-removable
56
63
  data-searchable={searchable ? '' : undefined}
57
- data-placeholder={placeholder ? '' : undefined}
58
64
  >
59
65
  {#each options as option}
60
66
  <option value={option.value ?? option}>
@@ -13,7 +13,6 @@ export let label; // Required
13
13
  export let name = undefined;
14
14
  export let options = [];
15
15
  export let searchable = false;
16
- export let placeholder;
17
16
  export let required = false;
18
17
  export let disabled = false;
19
18
  export let multiple = false;
@@ -0,0 +1,79 @@
1
+ <!-- prettier-ignore -->
2
+ <script>
3
+ import { onMount } from 'svelte';
4
+ import { getAppContext } from '../../utils';
5
+ import CustomSelector from '../CustomSelector';
6
+ import i18n from './i18n';
7
+
8
+ export let id;
9
+ export let label;
10
+ export let name;
11
+ export let searchable;
12
+ export let required;
13
+ export let disabled;
14
+ export let multiple;
15
+ export let show;
16
+ export let value;
17
+
18
+ export let type;
19
+
20
+ let options = [];
21
+ let error = false;
22
+
23
+ function toTitleCase(str) {
24
+ return str
25
+ .replace(/^[-_]*(.)/, (_, c) => c.toUpperCase())
26
+ .replace(/[-_]+(.)/g, (_, c) => ` ${c.toUpperCase()}`);
27
+ }
28
+
29
+ function getEndpoint() {
30
+ const { siteName } = getAppContext();
31
+ let repository = toTitleCase(type);
32
+ repository = !repository.endsWith('Repository') ? `${repository} Repository` : repository;
33
+ const endpoint = repository.includes('Alias') ? 'aliases' : 'nodes';
34
+
35
+ return `/rest-api/1/0/${siteName}/${repository}/${endpoint}`;
36
+ }
37
+
38
+ onMount(async () => {
39
+ const endpoint = getEndpoint();
40
+ const result = await fetch(endpoint)
41
+ .then((res) => {
42
+ if (res.ok) return res;
43
+ error = true;
44
+ throw new Error(
45
+ `Error (${res.status}) - Unable to fetch nodes, is site name and repository correct?`,
46
+ );
47
+ })
48
+ .then((res) => res.json())
49
+ .catch((e) => console.error(e.message));
50
+
51
+ options = (result || []).map((item) => ({ label: item.name, value: item.id }));
52
+ });
53
+ </script>
54
+
55
+ <CustomSelector
56
+ {id}
57
+ {label}
58
+ {name}
59
+ {searchable}
60
+ {required}
61
+ {disabled}
62
+ {multiple}
63
+ {show}
64
+ {options}
65
+ {value}
66
+ on:input
67
+ on:change
68
+ >
69
+ <slot />
70
+ {#if error}
71
+ <p class="alert alert-danger">{@html i18n('error')}</p>
72
+ {/if}
73
+ </CustomSelector>
74
+
75
+ <style>
76
+ p.alert {
77
+ margin-top: 10px;
78
+ }
79
+ </style>
@@ -0,0 +1,135 @@
1
+ # RepositoryNodeSelector
2
+
3
+ Component for selecting one or multiple nodes from a repository, is using [CustomSelector](../CustomSelector).
4
+
5
+ ![RepositoryNodeSelector1](../../images/RepositoryNodeSelector1.png)
6
+ ![RepositoryNodeSelector2](../../images/RepositoryNodeSelector2.png)
7
+
8
+ ## Props
9
+
10
+ ```javascript
11
+ export let id = generateId();
12
+ export let label; // Required
13
+ export let name = undefined;
14
+ export let options = [];
15
+ export let searchable = false;
16
+ export let required = false;
17
+ export let disabled = false;
18
+ export let multiple = false;
19
+ export let show = true;
20
+ export let value = '';
21
+ export let type;
22
+ ```
23
+
24
+ ### Types
25
+
26
+ All repositories that are children to the site are valid types, some are more useful than others.
27
+
28
+ ::: details Available types
29
+
30
+ Following types are available, not all are useful though.
31
+
32
+ #### Useful
33
+
34
+ * `alias` - Alias Repository.
35
+ * `named-reference` - Named Reference Repository.
36
+ * `rss-feed` - RSS Feed Repository.
37
+ * `rating-type` - Rating Type Repository.
38
+ * `responsive-breakpoint` - Responsive Breakpoint Repository.
39
+ * `role` - Role Repository.
40
+ * `site-cookie` - Site Cookie Repository.
41
+ * `tag-group` - Tag Group Repository.
42
+ * `tag` - Tag Repository.
43
+ * `user-field` - User Field Repository.
44
+
45
+ #### Do not use
46
+
47
+ * `color` - Color Repository, use `<DropdownSelector type="color" />` instead.
48
+ * `file` - File Repository, use `<NodeSelector type="file" />` instead.
49
+ * `font` - Font Repository, use `<DropdownSelector type="font" />` instead.
50
+ * `image` - Image Repository, use `<NodeSelector type="image" />` instead.
51
+ * `index` - Index Repository, use `<DropdownSelector type="search-index" />` instead.
52
+ * `metadata` - Metadata Definition Template Repository, use `<DropdownSelector type="metadata" />` instead.
53
+ * `oAuth2` - OAuth2 Configuration Repository, use `<DropdownSelector type="oauth2-configuration" />` instead.
54
+ * `page` - Page Repository, use `<NodeSelector type="page" />` instead.
55
+ * `principal` - Principal Repository, use `<NodeSelector type="user" />` instead.
56
+ * `template` - Template Repository, use `<DropdownSelector type="template" />` instead.
57
+
58
+ #### Other
59
+
60
+ * `addon` - Addon Repository.
61
+ * `data-store` - Data Store Repository.
62
+ * `decoration` - Decoration Repository.
63
+ * `default-image` - Default Image Repository.
64
+ * `externaltopicintegration` - ExternalTopicIntegration Repository.
65
+ * `icon` - Icon Repository.
66
+ * `list-style` - List Style Repository.
67
+ * `module-element-draft` - Module Element Draft Repository.
68
+ * `module-element` - Module Element Repository.
69
+ * `restApp` - RestApp Repository.
70
+ * `topic` - Topic Repository.
71
+ * `webApp` - WebApp Repository.
72
+
73
+ :::
74
+
75
+ ## App context
76
+
77
+ To be able to use this component app context must be created in `config/index.js`.
78
+ The options are fetched using the REST-API and the site name is needed for building the URL.
79
+
80
+ ```js
81
+ import router from '@sitevision/api/common/router';
82
+ import { createAppContext } from '@soleil-se/svelte-config/server';
83
+
84
+ router.get('/', (req, res) => {
85
+ createAppContext(res);
86
+ });
87
+ ```
88
+
89
+ ## Example
90
+
91
+ ### Standard
92
+
93
+ ```svelte
94
+ <script>
95
+ import { Panel, RepositoryNodeSelector } from '@soleil-se/svelte-config';
96
+ </script>
97
+
98
+ <Panel heading="Inställningar">
99
+ <RepositoryNodeSelector name="tagGroup" label="Tag group" type="tag-group" />
100
+ <RepositoryNodeSelector name="cookies" label="Cookies" type="site-cookie" multiple />
101
+ </Panel>
102
+ ```
103
+
104
+ ### Advanced
105
+
106
+ ```svelte
107
+ <script>
108
+ import { Panel, RepositoryNodeSelector } from '@soleil-se/svelte-config';
109
+ import { onSave } from '@soleil-se/svelte-config/utils';
110
+
111
+ export let values;
112
+
113
+ values = {
114
+ tagGroup: '',
115
+ ...values
116
+ }
117
+
118
+ onSave(() => values);
119
+
120
+ </script>
121
+
122
+ <Panel heading="Inställningar">
123
+ <RepositoryNodeSelector label="Tag group" type="tag-group" bind:value={values.tagGroup} />
124
+ </Panel>
125
+ ```
126
+
127
+ ## Slots
128
+
129
+ Default slot after selector.
130
+
131
+ ```svelte
132
+ <RepositoryNodeSelector name="tagGroup" label="Tag group" type="tag-group">
133
+ <p class="help-block">Some helpful text.</p>
134
+ </RepositoryNodeSelector>
135
+ ```
@@ -0,0 +1,13 @@
1
+ import createI18n from '../../utils/createI18n';
2
+
3
+ export default createI18n({
4
+ sv: {
5
+ error: '<strong>Fel:</strong> Det gick inte att hämta noder, se konsolen för mer information.',
6
+ },
7
+ no: {
8
+ error: '<strong>Feil:</strong> Noder kunne ikke hentes, se konsollen for mer informasjon.',
9
+ },
10
+ en: {
11
+ error: '<strong>Error:</strong> Nodes could not be fetched, see console for more information.',
12
+ },
13
+ });
@@ -0,0 +1 @@
1
+ export { default } from './Component.svelte';
package/index.js CHANGED
@@ -15,6 +15,7 @@ export { default as TagSelector } from './components/TagSelector';
15
15
  export { default as CustomSelector } from './components/CustomSelector';
16
16
  export { default as Modal } from './components/Modal';
17
17
  export { default as CustomList } from './components/CustomList';
18
+ export { default as RepositoryNodeSelector } from './components/RepositoryNodeSelector';
18
19
 
19
20
  export { default as createConfigApp } from './createConfigApp';
20
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soleil-se/config-svelte",
3
- "version": "1.16.2",
3
+ "version": "1.17.0",
4
4
  "main": "./index.js",
5
5
  "module": "./index.js",
6
6
  "svelte": "./index.js",
@@ -8,13 +8,14 @@
8
8
  "license": "UNLICENSED",
9
9
  "sideEffects": false,
10
10
  "peerDependencies": {
11
+ "@sitevision/api": "^1.0.20",
11
12
  "svelte": "^3.46.0"
12
13
  },
13
14
  "devDependencies": {
14
15
  "svelte": "^3.46.4"
15
16
  },
16
17
  "dependencies": {
17
- "@soleil-se/config-validate": "^1.1.1",
18
+ "@soleil-se/config-validate": "^1.1.2",
18
19
  "a11y-dialog": "7.4.0",
19
20
  "focus-visible": "^5.2.0",
20
21
  "svelte-dnd-action": "^0.9.18"
package/server/index.js CHANGED
@@ -1,3 +1,7 @@
1
+ import { getCurrentPage } from '@sitevision/api/server/PortletContextUtil';
2
+ import appInfo from '@sitevision/api/server/appInfo';
3
+ import { getSite } from '@sitevision/api/server/ResourceLocatorUtil';
4
+
1
5
  /**
2
6
  * Creates a HTML string for passing data to the app.
3
7
  * @param {object} props Props to be passed to the app.
@@ -5,7 +9,7 @@
5
9
  */
6
10
  export function createAppProps(props) {
7
11
  return `
8
- <script>
12
+ <script>
9
13
  window.CONFIG_APP_PROPS = ${JSON.stringify(props)};
10
14
  </script>
11
15
  <div id="app_root"></div>
@@ -21,3 +25,24 @@ export function createAppProps(props) {
21
25
  export function createAppData(data) {
22
26
  return createAppProps(data);
23
27
  }
28
+
29
+ /**
30
+ * Creates app context global variable.
31
+ * @param {Response} res Response from @sitevision/common/router.
32
+ */
33
+ export function createAppContext(res) {
34
+ const siteNode = getSite();
35
+
36
+ const context = {
37
+ ...appInfo,
38
+ pageId: getCurrentPage().getIdentifier(),
39
+ siteId: siteNode.getIdentifier(),
40
+ siteName: siteNode.toString(),
41
+ };
42
+
43
+ res.send(`
44
+ <script>
45
+ window.CONFIG_APP_CONTEXT = ${JSON.stringify(context)};
46
+ </script>
47
+ `);
48
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Get app context value or object that is created for app when rendering.
3
+ * @export
4
+ * @param {string} [key] - Key for value.
5
+ * @return {*|object} - Value or object.
6
+ */
7
+ export default function getAppContext(key) {
8
+ if (!window.CONFIG_APP_CONTEXT) {
9
+ console.warn(`App context is not created, add the following in config/index.js:
10
+
11
+ import router from '@sitevision/api/common/router';
12
+ import { createAppContext } from '@soleil-se/svelte-config/server';
13
+
14
+ router.get('/', (req, res) => {
15
+ createAppContext(res);
16
+ });`);
17
+ return {};
18
+ }
19
+ const context = { ...window?.sv?.context, ...window.CONFIG_APP_CONTEXT };
20
+ return key ? context[key] : context;
21
+ }
package/utils/index.js CHANGED
@@ -6,3 +6,4 @@ export { default as pluckPrefix } from './pluckPrefix';
6
6
  export { default as triggerInput } from './triggerInput';
7
7
  export { default as createI18n } from './createI18n';
8
8
  export { default as getAppProps } from './getAppProps';
9
+ export { default as getAppContext } from './getAppContext';