@crombolo/backoffice-host 0.0.0-stage → 0.10.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/LICENSE +21 -0
- package/NOTICE +69 -0
- package/assets/login.js +352 -0
- package/package.json +35 -4
- package/plugin/assistant/client.js +53 -0
- package/plugin/assistant/drawer.js +865 -0
- package/plugin/assistant/umbraco-package.json +16 -0
- package/plugin/branding/backdrop.svg +1 -0
- package/plugin/branding/branding.js +100 -0
- package/plugin/branding/bundle-builder-labels.js +63 -0
- package/plugin/branding/client-credentials.js +49 -0
- package/plugin/branding/components-tree.js +40 -0
- package/plugin/branding/content-type-labels.js +27 -0
- package/plugin/branding/favicon.svg +1 -0
- package/plugin/branding/help-menu.js +46 -0
- package/plugin/branding/icon-mark.js +15 -0
- package/plugin/branding/icon-registry.js +40 -0
- package/plugin/branding/localization-en.js +115 -0
- package/plugin/branding/log-viewer-menu.js +70 -0
- package/plugin/branding/logo-header.svg +1 -0
- package/plugin/branding/logo-surface.svg +1 -0
- package/plugin/branding/query-builder.js +28 -0
- package/plugin/branding/sign-in.js +80 -0
- package/plugin/branding/sysinfo.js +89 -0
- package/plugin/branding/theme.css +102 -0
- package/plugin/bundles-carries.js +16 -0
- package/plugin/bundles-client.js +40 -0
- package/plugin/bundles-created-overview.js +244 -0
- package/plugin/bundles-created.js +70 -0
- package/plugin/bundles-installed.js +176 -0
- package/plugin/bundles-marketplace.js +236 -0
- package/plugin/changes-dashboard.js +169 -0
- package/plugin/element-type-hint.js +58 -0
- package/plugin/form-picker.js +116 -0
- package/plugin/forms-client.js +52 -0
- package/plugin/forms-entries.js +244 -0
- package/plugin/forms-overview.js +380 -0
- package/plugin/localizations/ar.js +97 -0
- package/plugin/localizations/bs.js +86 -0
- package/plugin/localizations/cs.js +73 -0
- package/plugin/localizations/cy.js +92 -0
- package/plugin/localizations/da.js +101 -0
- package/plugin/localizations/de.js +79 -0
- package/plugin/localizations/en.js +71 -0
- package/plugin/localizations/es.js +52 -0
- package/plugin/localizations/fr.js +78 -0
- package/plugin/localizations/he.js +24 -0
- package/plugin/localizations/hr.js +89 -0
- package/plugin/localizations/it.js +86 -0
- package/plugin/localizations/ja.js +43 -0
- package/plugin/localizations/ko.js +24 -0
- package/plugin/localizations/nb.js +44 -0
- package/plugin/localizations/nl.js +85 -0
- package/plugin/localizations/pl.js +44 -0
- package/plugin/localizations/pt-br.js +25 -0
- package/plugin/localizations/pt.js +100 -0
- package/plugin/localizations/ro.js +9 -0
- package/plugin/localizations/ru.js +46 -0
- package/plugin/localizations/sv.js +46 -0
- package/plugin/localizations/tr.js +80 -0
- package/plugin/localizations/uk.js +46 -0
- package/plugin/localizations/umbraco-package.json +279 -0
- package/plugin/localizations/vi.js +104 -0
- package/plugin/localizations/zh-tw.js +38 -0
- package/plugin/localizations/zh.js +38 -0
- package/plugin/read-only-banner.js +48 -0
- package/plugin/report-client.js +13 -0
- package/plugin/seo/client.js +35 -0
- package/plugin/seo/dashboard.js +242 -0
- package/plugin/seo/document-view.js +161 -0
- package/plugin/seo/type-hint.js +45 -0
- package/plugin/seo/umbraco-package.json +61 -0
- package/plugin/settings-dashboard.js +123 -0
- package/plugin/tsx-editors.js +337 -0
- package/plugin/tsx-snippets.js +63 -0
- package/plugin/umbraco-package.json +298 -0
- package/plugin/welcome-dashboard.js +106 -0
- package/src/branding.ts +59 -0
- package/src/bundle-package.ts +266 -0
- package/src/extensions.ts +189 -0
- package/src/graphics.ts +87 -0
- package/src/index.ts +8 -0
- package/src/login-assets.ts +18 -0
- package/src/manifests.ts +154 -0
- package/src/paths.ts +117 -0
- package/src/shell.ts +139 -0
- package/src/static.ts +107 -0
- package/src/text-imports.d.ts +23 -0
- package/README.md +0 -3
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Form Picker property editor.
|
|
3
|
+
*
|
|
4
|
+
* A form is a file in `schema/forms/`, so the list comes from the server rather
|
|
5
|
+
* than from a tree of nodes: there is nothing in the database to browse. The
|
|
6
|
+
* stored value is the form's key, which is what a render resolves back to the
|
|
7
|
+
* definition.
|
|
8
|
+
*
|
|
9
|
+
* Shows the alias beside the name because a form is referred to by alias
|
|
10
|
+
* everywhere else — in a view, in a workflow's settings, in a diff.
|
|
11
|
+
*/
|
|
12
|
+
import { css, html, nothing, repeat } from '@umbraco-cms/backoffice/external/lit'
|
|
13
|
+
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
|
|
14
|
+
|
|
15
|
+
const forms = async () => {
|
|
16
|
+
const href = document.querySelector('base')?.getAttribute('href') ?? '/umbraco/'
|
|
17
|
+
const response = await fetch(`${href.replace(/\/$/, '')}/crombolo/api/forms`, {
|
|
18
|
+
credentials: 'include',
|
|
19
|
+
headers: { accept: 'application/json' },
|
|
20
|
+
})
|
|
21
|
+
if (!response.ok) throw new Error(`forms responded ${response.status}`)
|
|
22
|
+
return (await response.json()).items ?? []
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export default class CromboloFormPickerElement extends UmbLitElement {
|
|
26
|
+
static properties = {
|
|
27
|
+
value: { type: String },
|
|
28
|
+
_items: { state: true },
|
|
29
|
+
_failed: { state: true },
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
constructor() {
|
|
33
|
+
super()
|
|
34
|
+
this.value = ''
|
|
35
|
+
this._items = undefined
|
|
36
|
+
this._failed = false
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
connectedCallback() {
|
|
40
|
+
super.connectedCallback()
|
|
41
|
+
forms().then(
|
|
42
|
+
(items) => {
|
|
43
|
+
this._items = items
|
|
44
|
+
},
|
|
45
|
+
() => {
|
|
46
|
+
this._failed = true
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A plain `change` on the host, which is what `umb-property` listens for.
|
|
53
|
+
*
|
|
54
|
+
* `UmbPropertyValueChangeEvent` is deprecated upstream and is not exported by
|
|
55
|
+
* the vendored client at all, so importing it would fail at runtime. The
|
|
56
|
+
* inner select's own event is stopped so the host dispatches exactly one.
|
|
57
|
+
*/
|
|
58
|
+
#pick(event) {
|
|
59
|
+
event.stopPropagation()
|
|
60
|
+
this.value = event.target.value || undefined
|
|
61
|
+
this.dispatchEvent(new Event('change'))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
render() {
|
|
65
|
+
if (this._failed) return html`<uui-box><p>The list of forms could not be loaded.</p></uui-box>`
|
|
66
|
+
if (this._items === undefined) return html`<uui-loader></uui-loader>`
|
|
67
|
+
if (this._items.length === 0)
|
|
68
|
+
return html`
|
|
69
|
+
<p class="empty">
|
|
70
|
+
This site has no forms. Add one as a TOML file in <code>schema/forms/</code>.
|
|
71
|
+
</p>
|
|
72
|
+
`
|
|
73
|
+
return html`
|
|
74
|
+
<uui-select
|
|
75
|
+
.value=${this.value ?? ''}
|
|
76
|
+
@change=${this.#pick}
|
|
77
|
+
label="Form"
|
|
78
|
+
placeholder="Choose a form"
|
|
79
|
+
>
|
|
80
|
+
<option value="">(none)</option>
|
|
81
|
+
${repeat(
|
|
82
|
+
this._items,
|
|
83
|
+
(form) => form.key,
|
|
84
|
+
(form) => html`
|
|
85
|
+
<option value=${form.key} ?selected=${form.key === this.value}>
|
|
86
|
+
${form.name} (${form.alias})
|
|
87
|
+
</option>
|
|
88
|
+
`,
|
|
89
|
+
)}
|
|
90
|
+
</uui-select>
|
|
91
|
+
${this.value ? html`<p class="detail">${this.#describe()}</p>` : nothing}
|
|
92
|
+
`
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
#describe() {
|
|
96
|
+
const form = this._items?.find((item) => item.key === this.value)
|
|
97
|
+
if (!form) return 'This form is no longer in schema/forms/.'
|
|
98
|
+
const fields = `${form.fieldCount} field${form.fieldCount === 1 ? '' : 's'}`
|
|
99
|
+
const pages = `${form.pageCount} page${form.pageCount === 1 ? '' : 's'}`
|
|
100
|
+
return `${fields} over ${pages}${form.storeEntries ? '' : '; entries are not stored'}`
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
static styles = css`
|
|
104
|
+
:host {
|
|
105
|
+
display: block;
|
|
106
|
+
}
|
|
107
|
+
.empty,
|
|
108
|
+
.detail {
|
|
109
|
+
color: var(--uui-color-text-alt, #666);
|
|
110
|
+
font-size: var(--uui-type-small-size, 0.8rem);
|
|
111
|
+
margin: var(--uui-size-space-2, 6px) 0 0;
|
|
112
|
+
}
|
|
113
|
+
`
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
customElements.define('crombolo-form-picker', CromboloFormPickerElement)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads and drives the Forms endpoints the server keeps at
|
|
3
|
+
* <backoffice>/crombolo/api/forms/*. The session cookie carries the auth.
|
|
4
|
+
*/
|
|
5
|
+
const base = () => {
|
|
6
|
+
const href = document.querySelector('base')?.getAttribute('href') ?? '/umbraco/'
|
|
7
|
+
return `${href.replace(/\/$/, '')}/crombolo/api/forms`
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const call = async (path, init) => {
|
|
11
|
+
const response = await fetch(`${base()}${path}`, {
|
|
12
|
+
credentials: 'include',
|
|
13
|
+
headers: {
|
|
14
|
+
accept: 'application/json',
|
|
15
|
+
...(init?.body ? { 'content-type': 'application/json' } : {}),
|
|
16
|
+
},
|
|
17
|
+
...init,
|
|
18
|
+
})
|
|
19
|
+
if (response.status === 403) throw new Error('You do not have permission for that.')
|
|
20
|
+
const body = await response.json().catch(() => ({}))
|
|
21
|
+
if (!response.ok && response.status !== 400 && response.status !== 409)
|
|
22
|
+
throw new Error(`forms responded ${response.status}`)
|
|
23
|
+
return { ok: response.ok, status: response.status, body }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const listForms = async () => (await call('')).body.items ?? []
|
|
27
|
+
|
|
28
|
+
export const readForm = async (key) => (await call(`/${encodeURIComponent(key)}`)).body
|
|
29
|
+
|
|
30
|
+
export const saveForm = (definition) =>
|
|
31
|
+
call('', { method: 'PUT', body: JSON.stringify(definition) })
|
|
32
|
+
|
|
33
|
+
export const deleteForm = (key) => call(`/${encodeURIComponent(key)}`, { method: 'DELETE' })
|
|
34
|
+
|
|
35
|
+
export const listEntries = async (key, query = {}) => {
|
|
36
|
+
const params = new URLSearchParams()
|
|
37
|
+
for (const [name, value] of Object.entries(query))
|
|
38
|
+
if (value !== undefined && value !== '') params.set(name, String(value))
|
|
39
|
+
const suffix = params.toString() ? `?${params}` : ''
|
|
40
|
+
return (await call(`/${encodeURIComponent(key)}/entries${suffix}`)).body
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const setEntryState = (id, state) =>
|
|
44
|
+
call(`/entries/${encodeURIComponent(id)}/state`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
body: JSON.stringify({ state }),
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
export const deleteEntry = (id) => call(`/entries/${encodeURIComponent(id)}`, { method: 'DELETE' })
|
|
50
|
+
|
|
51
|
+
/** The CSV is a download, so it is a link rather than a fetch. */
|
|
52
|
+
export const entriesCsvUrl = (key) => `${base()}/${encodeURIComponent(key)}/entries.csv`
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Forms section's Entries view.
|
|
3
|
+
*
|
|
4
|
+
* What a person actually does here: read what was submitted, approve or reject
|
|
5
|
+
* it, delete it, and take the lot away as CSV.
|
|
6
|
+
*
|
|
7
|
+
* A sensitive field arrives already redacted — the server withholds it rather
|
|
8
|
+
* than the UI hiding it — so this shows that it is withheld rather than
|
|
9
|
+
* pretending the answer was blank.
|
|
10
|
+
*/
|
|
11
|
+
import { css, html, nothing, repeat } from '@umbraco-cms/backoffice/external/lit'
|
|
12
|
+
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
|
|
13
|
+
import {
|
|
14
|
+
deleteEntry,
|
|
15
|
+
entriesCsvUrl,
|
|
16
|
+
listEntries,
|
|
17
|
+
listForms,
|
|
18
|
+
setEntryState,
|
|
19
|
+
} from './forms-client.js'
|
|
20
|
+
|
|
21
|
+
export default class CromboloFormsEntriesElement extends UmbLitElement {
|
|
22
|
+
static properties = {
|
|
23
|
+
_forms: { state: true },
|
|
24
|
+
_selected: { state: true },
|
|
25
|
+
_entries: { state: true },
|
|
26
|
+
_total: { state: true },
|
|
27
|
+
_state: { state: true },
|
|
28
|
+
_search: { state: true },
|
|
29
|
+
_includeSpam: { state: true },
|
|
30
|
+
_notice: { state: true },
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
constructor() {
|
|
34
|
+
super()
|
|
35
|
+
this._forms = undefined
|
|
36
|
+
this._selected = ''
|
|
37
|
+
this._entries = []
|
|
38
|
+
this._total = 0
|
|
39
|
+
this._state = ''
|
|
40
|
+
this._search = ''
|
|
41
|
+
this._includeSpam = false
|
|
42
|
+
this._notice = ''
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
connectedCallback() {
|
|
46
|
+
super.connectedCallback()
|
|
47
|
+
listForms().then(
|
|
48
|
+
(forms) => {
|
|
49
|
+
this._forms = forms
|
|
50
|
+
this._selected = forms[0]?.key ?? ''
|
|
51
|
+
if (this._selected) this.#load()
|
|
52
|
+
},
|
|
53
|
+
(error) => {
|
|
54
|
+
this._forms = []
|
|
55
|
+
this._notice = error.message
|
|
56
|
+
},
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async #load() {
|
|
61
|
+
this._notice = ''
|
|
62
|
+
try {
|
|
63
|
+
const page = await listEntries(this._selected, {
|
|
64
|
+
state: this._state,
|
|
65
|
+
search: this._search,
|
|
66
|
+
includeSpam: this._includeSpam ? 'true' : '',
|
|
67
|
+
take: 200,
|
|
68
|
+
})
|
|
69
|
+
this._entries = page.items ?? []
|
|
70
|
+
this._total = page.total ?? 0
|
|
71
|
+
} catch (error) {
|
|
72
|
+
this._entries = []
|
|
73
|
+
this._notice = error.message
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async #setState(entry, state) {
|
|
78
|
+
try {
|
|
79
|
+
const result = await setEntryState(entry.id, state)
|
|
80
|
+
if (!result.ok) this._notice = 'That entry could not be changed.'
|
|
81
|
+
await this.#load()
|
|
82
|
+
} catch (error) {
|
|
83
|
+
this._notice = error.message
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async #remove(entry) {
|
|
88
|
+
if (!confirm('Delete this entry? It cannot be undone.')) return
|
|
89
|
+
try {
|
|
90
|
+
await deleteEntry(entry.id)
|
|
91
|
+
await this.#load()
|
|
92
|
+
} catch (error) {
|
|
93
|
+
this._notice = error.message
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
render() {
|
|
98
|
+
if (this._forms === undefined) return html`<uui-loader></uui-loader>`
|
|
99
|
+
if (this._forms.length === 0)
|
|
100
|
+
return html`<uui-box headline="Entries"><p>There are no forms yet.</p></uui-box>`
|
|
101
|
+
|
|
102
|
+
return html`
|
|
103
|
+
<uui-box headline="Entries">
|
|
104
|
+
<div class="filters">
|
|
105
|
+
<select
|
|
106
|
+
@change=${(event) => {
|
|
107
|
+
this._selected = event.target.value
|
|
108
|
+
this.#load()
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
${this._forms.map(
|
|
112
|
+
(form) =>
|
|
113
|
+
html`<option value=${form.key} ?selected=${form.key === this._selected}>${form.name}</option>`,
|
|
114
|
+
)}
|
|
115
|
+
</select>
|
|
116
|
+
<select
|
|
117
|
+
@change=${(event) => {
|
|
118
|
+
this._state = event.target.value
|
|
119
|
+
this.#load()
|
|
120
|
+
}}
|
|
121
|
+
>
|
|
122
|
+
<option value="">Any state</option>
|
|
123
|
+
<option value="submitted">Submitted</option>
|
|
124
|
+
<option value="approved">Approved</option>
|
|
125
|
+
<option value="rejected">Rejected</option>
|
|
126
|
+
</select>
|
|
127
|
+
<uui-input
|
|
128
|
+
placeholder="Search the answers"
|
|
129
|
+
.value=${this._search}
|
|
130
|
+
@change=${(event) => {
|
|
131
|
+
this._search = event.target.value
|
|
132
|
+
this.#load()
|
|
133
|
+
}}
|
|
134
|
+
></uui-input>
|
|
135
|
+
<uui-toggle
|
|
136
|
+
label="Include spam"
|
|
137
|
+
.checked=${this._includeSpam}
|
|
138
|
+
@change=${(event) => {
|
|
139
|
+
this._includeSpam = event.target.checked
|
|
140
|
+
this.#load()
|
|
141
|
+
}}
|
|
142
|
+
></uui-toggle>
|
|
143
|
+
<a class="csv" href=${entriesCsvUrl(this._selected)} download>Export CSV</a>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
${this._notice ? html`<p class="notice" role="alert">${this._notice}</p>` : nothing}
|
|
147
|
+
<p>${this._total} entr${this._total === 1 ? 'y' : 'ies'}.</p>
|
|
148
|
+
|
|
149
|
+
${
|
|
150
|
+
this._entries.length === 0
|
|
151
|
+
? html`<p>Nothing to show.</p>`
|
|
152
|
+
: repeat(
|
|
153
|
+
this._entries,
|
|
154
|
+
(entry) => entry.id,
|
|
155
|
+
(entry) => html`
|
|
156
|
+
<div class="entry">
|
|
157
|
+
<div class="meta">
|
|
158
|
+
<strong>${new Date(entry.createDate).toLocaleString()}</strong>
|
|
159
|
+
<span class="state">${entry.state}</span>
|
|
160
|
+
${entry.spam ? html`<span class="spam">spam</span>` : nothing}
|
|
161
|
+
</div>
|
|
162
|
+
<dl>
|
|
163
|
+
${entry.values.map(
|
|
164
|
+
(value) => html`
|
|
165
|
+
<dt>${value.fieldAlias}</dt>
|
|
166
|
+
<dd>
|
|
167
|
+
${
|
|
168
|
+
value.redacted
|
|
169
|
+
? html`<em>withheld — needs sensitive-data access</em>`
|
|
170
|
+
: value.values.join(', ') || html`<em>not answered</em>`
|
|
171
|
+
}
|
|
172
|
+
</dd>
|
|
173
|
+
`,
|
|
174
|
+
)}
|
|
175
|
+
</dl>
|
|
176
|
+
<div class="actions">
|
|
177
|
+
<uui-button look="secondary" label="Approve" @click=${() => this.#setState(entry, 'approved')}></uui-button>
|
|
178
|
+
<uui-button look="secondary" label="Reject" @click=${() => this.#setState(entry, 'rejected')}></uui-button>
|
|
179
|
+
<uui-button look="secondary" color="danger" label="Delete" @click=${() => this.#remove(entry)}></uui-button>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
`,
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
</uui-box>
|
|
186
|
+
`
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
static styles = css`
|
|
190
|
+
:host {
|
|
191
|
+
display: block;
|
|
192
|
+
padding: var(--uui-size-layout-1, 24px);
|
|
193
|
+
}
|
|
194
|
+
.filters {
|
|
195
|
+
display: flex;
|
|
196
|
+
gap: var(--uui-size-space-4, 12px);
|
|
197
|
+
align-items: center;
|
|
198
|
+
flex-wrap: wrap;
|
|
199
|
+
margin-bottom: var(--uui-size-layout-1, 24px);
|
|
200
|
+
}
|
|
201
|
+
.entry {
|
|
202
|
+
border-top: 1px solid var(--uui-color-border, #e5e5e5);
|
|
203
|
+
padding: var(--uui-size-space-4, 12px) 0;
|
|
204
|
+
}
|
|
205
|
+
.meta {
|
|
206
|
+
display: flex;
|
|
207
|
+
gap: var(--uui-size-space-4, 12px);
|
|
208
|
+
align-items: baseline;
|
|
209
|
+
}
|
|
210
|
+
.state,
|
|
211
|
+
.spam {
|
|
212
|
+
font-size: var(--uui-type-small-size, 0.8rem);
|
|
213
|
+
text-transform: uppercase;
|
|
214
|
+
letter-spacing: 0.04em;
|
|
215
|
+
}
|
|
216
|
+
.spam {
|
|
217
|
+
color: var(--uui-color-danger, #d42054);
|
|
218
|
+
}
|
|
219
|
+
dl {
|
|
220
|
+
display: grid;
|
|
221
|
+
grid-template-columns: minmax(8rem, 14rem) 1fr;
|
|
222
|
+
gap: var(--uui-size-space-2, 6px) var(--uui-size-space-4, 12px);
|
|
223
|
+
margin: var(--uui-size-space-4, 12px) 0;
|
|
224
|
+
}
|
|
225
|
+
dt {
|
|
226
|
+
color: var(--uui-color-text-alt, #666);
|
|
227
|
+
}
|
|
228
|
+
dd {
|
|
229
|
+
margin: 0;
|
|
230
|
+
}
|
|
231
|
+
.actions {
|
|
232
|
+
display: flex;
|
|
233
|
+
gap: var(--uui-size-space-4, 12px);
|
|
234
|
+
}
|
|
235
|
+
.notice {
|
|
236
|
+
color: var(--uui-color-danger, #d42054);
|
|
237
|
+
}
|
|
238
|
+
.csv {
|
|
239
|
+
margin-left: auto;
|
|
240
|
+
}
|
|
241
|
+
`
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
customElements.define('crombolo-forms-entries', CromboloFormsEntriesElement)
|