@crombolo/backoffice-host 0.13.2 → 0.14.4

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 (45) hide show
  1. package/assets/device.js +180 -0
  2. package/assets/login.js +89 -2
  3. package/assets/remote-approval.js +134 -0
  4. package/package.json +2 -2
  5. package/plugin/assistant/umbraco-package.json +1 -1
  6. package/plugin/branding/branding.js +9 -14
  7. package/plugin/branding/localization-en.js +11 -0
  8. package/plugin/branding/theme.css +2 -0
  9. package/plugin/localizations/ar.js +3 -39
  10. package/plugin/localizations/bs.js +3 -29
  11. package/plugin/localizations/cs.js +3 -24
  12. package/plugin/localizations/cy.js +3 -32
  13. package/plugin/localizations/da.js +3 -41
  14. package/plugin/localizations/de.js +3 -27
  15. package/plugin/localizations/es.js +3 -19
  16. package/plugin/localizations/fr.js +3 -26
  17. package/plugin/localizations/hr.js +3 -29
  18. package/plugin/localizations/it.js +3 -29
  19. package/plugin/localizations/ja.js +3 -11
  20. package/plugin/localizations/ko.js +3 -11
  21. package/plugin/localizations/nb.js +3 -18
  22. package/plugin/localizations/nl.js +3 -29
  23. package/plugin/localizations/pl.js +3 -11
  24. package/plugin/localizations/pt-br.js +0 -10
  25. package/plugin/localizations/pt.js +3 -39
  26. package/plugin/localizations/ru.js +3 -9
  27. package/plugin/localizations/sv.js +3 -20
  28. package/plugin/localizations/tr.js +3 -26
  29. package/plugin/localizations/uk.js +3 -9
  30. package/plugin/localizations/umbraco-package.json +22 -2
  31. package/plugin/localizations/vi.js +3 -42
  32. package/plugin/localizations/zh-tw.js +0 -6
  33. package/plugin/localizations/zh.js +0 -6
  34. package/plugin/seo/umbraco-package.json +1 -1
  35. package/plugin/sign-in/entry.js +20 -0
  36. package/plugin/sign-in/sign-in-dialog.js +127 -0
  37. package/plugin/sign-in/umbraco-package.json +15 -0
  38. package/plugin/signed-in-devices.js +90 -0
  39. package/plugin/two-factor-setup.js +147 -0
  40. package/plugin/umbraco-package.json +79 -27
  41. package/plugin/welcome-dashboard.js +2 -5
  42. package/plugin/workflow-designer.js +245 -170
  43. package/src/login-assets.ts +5 -2
  44. package/src/shell.ts +65 -1
  45. package/plugin/branding/sysinfo.js +0 -89
@@ -0,0 +1,180 @@
1
+ /**
2
+ * The page a person approves a command-line sign-in on (story 107, RFC 8628).
3
+ * The `crombolo` command shows a code and opens this page; the person, already
4
+ * signed in here, checks the code matches and allows it — or declines.
5
+ */
6
+ import { css, html, LitElement, nothing } from '@umbraco-cms/backoffice/external/lit'
7
+ import '@umbraco-cms/backoffice/external/uui'
8
+
9
+ const API = '/umbraco/management/api/v1/security/back-office'
10
+
11
+ class CromboloDeviceElement extends LitElement {
12
+ static properties = {
13
+ userCode: { type: String, attribute: 'user-code' },
14
+ logoImage: { type: String, attribute: 'logo-image' },
15
+ _pending: { state: true },
16
+ _error: { state: true },
17
+ _done: { state: true },
18
+ _busy: { state: true },
19
+ }
20
+
21
+ constructor() {
22
+ super()
23
+ this.userCode = ''
24
+ this.logoImage = ''
25
+ this._pending = undefined
26
+ this._error = ''
27
+ this._done = ''
28
+ this._busy = false
29
+ }
30
+
31
+ connectedCallback() {
32
+ super.connectedCallback()
33
+ if (this.userCode) this.#look(this.userCode)
34
+ }
35
+
36
+ async #look(code) {
37
+ this._error = ''
38
+ this._busy = true
39
+ try {
40
+ const response = await fetch(`${API}/device/pending?user_code=${encodeURIComponent(code)}`, {
41
+ credentials: 'include',
42
+ })
43
+ if (response.ok) {
44
+ this._pending = await response.json()
45
+ return
46
+ }
47
+ const problem = await response.json().catch(() => ({}))
48
+ this._error = problem.detail ?? problem.title ?? 'That code is not waiting.'
49
+ } catch {
50
+ this._error = 'Could not reach the server.'
51
+ } finally {
52
+ this._busy = false
53
+ }
54
+ }
55
+
56
+ async #decide(approve) {
57
+ this._busy = true
58
+ try {
59
+ const response = await fetch(`${API}/device/decide`, {
60
+ method: 'POST',
61
+ credentials: 'include',
62
+ headers: { 'content-type': 'application/json' },
63
+ body: JSON.stringify({ userCode: this._pending.userCode, approve }),
64
+ })
65
+ if (!response.ok) {
66
+ this._error = 'That code is no longer waiting. Start the sign-in again.'
67
+ this._pending = undefined
68
+ return
69
+ }
70
+ this._done = approve
71
+ ? 'Signed in. You can close this window and go back to your terminal.'
72
+ : 'Declined. Nothing was signed in.'
73
+ } catch {
74
+ this._error = 'Could not reach the server.'
75
+ } finally {
76
+ this._busy = false
77
+ }
78
+ }
79
+
80
+ #submitCode(event) {
81
+ event.preventDefault()
82
+ const code = event.target.elements.code.value.trim()
83
+ if (code) this.#look(code)
84
+ }
85
+
86
+ #body() {
87
+ if (this._done) return html`<p id="notice" role="status">${this._done}</p>`
88
+ if (this._pending)
89
+ return html`
90
+ <p>
91
+ ${this._pending.clientName ?? 'A command line'} is asking to sign in as you. Check that this
92
+ code matches the one it shows:
93
+ </p>
94
+ <p id="code">${this._pending.userCode}</p>
95
+ ${this._error ? html`<p id="error" role="alert">${this._error}</p>` : nothing}
96
+ <div id="actions">
97
+ <uui-button look="primary" color="positive" label="Allow" .state=${this._busy ? 'waiting' : undefined}
98
+ @click=${() => this.#decide(true)}></uui-button>
99
+ <uui-button look="outline" label="Decline" @click=${() => this.#decide(false)}></uui-button>
100
+ </div>`
101
+ return html`
102
+ <form @submit=${this.#submitCode}>
103
+ <uui-form-layout-item>
104
+ <uui-label for="code" slot="label" required>The code your terminal shows</uui-label>
105
+ <uui-input id="code" name="code" autocomplete="off" required .value=${this.userCode ?? ''}></uui-input>
106
+ </uui-form-layout-item>
107
+ ${this._error ? html`<p id="error" role="alert">${this._error}</p>` : nothing}
108
+ <uui-button type="submit" look="primary" label="Continue" .state=${this._busy ? 'waiting' : undefined}></uui-button>
109
+ </form>`
110
+ }
111
+
112
+ render() {
113
+ return html`
114
+ <div id="layout">
115
+ <uui-box id="card">
116
+ ${this.logoImage ? html`<img id="logo" src=${this.logoImage} alt="" />` : nothing}
117
+ <h1 class="uui-h3">Sign in from your terminal</h1>
118
+ ${this.#body()}
119
+ </uui-box>
120
+ </div>
121
+ `
122
+ }
123
+
124
+ static styles = css`
125
+ :host {
126
+ display: block;
127
+ min-height: 100vh;
128
+ background: var(--uui-color-surface-alt, #f4f4f4);
129
+ }
130
+ #layout {
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ min-height: 100vh;
135
+ padding: 16px;
136
+ box-sizing: border-box;
137
+ }
138
+ #card {
139
+ width: 100%;
140
+ max-width: 420px;
141
+ }
142
+ #logo {
143
+ display: block;
144
+ height: 48px;
145
+ margin-bottom: var(--uui-size-space-4, 12px);
146
+ }
147
+ h1 {
148
+ margin: 0 0 var(--uui-size-space-5, 18px);
149
+ }
150
+ form {
151
+ display: flex;
152
+ flex-direction: column;
153
+ gap: var(--uui-size-space-4, 12px);
154
+ }
155
+ uui-input {
156
+ width: 100%;
157
+ }
158
+ #code {
159
+ font-family: monospace;
160
+ font-size: 1.75rem;
161
+ letter-spacing: 0.15em;
162
+ text-align: center;
163
+ margin: var(--uui-size-space-4, 12px) 0;
164
+ }
165
+ #actions {
166
+ display: flex;
167
+ gap: var(--uui-size-space-3, 9px);
168
+ }
169
+ #error {
170
+ margin: 0;
171
+ color: var(--uui-color-danger, #d42054);
172
+ }
173
+ #notice {
174
+ margin: 0;
175
+ color: var(--uui-color-positive, #0b8152);
176
+ }
177
+ `
178
+ }
179
+
180
+ customElements.define('crombolo-device', CromboloDeviceElement)
package/assets/login.js CHANGED
@@ -39,6 +39,8 @@ class CromboloLoginElement extends LitElement {
39
39
  logoImage: { type: String, attribute: 'logo-image-alternative' },
40
40
  usernameIsEmail: { type: Boolean, attribute: 'username-is-email' },
41
41
  allowPasswordReset: { type: Boolean, attribute: 'allow-password-reset' },
42
+ disableLocalLogin: { type: Boolean, attribute: 'disable-local-login' },
43
+ externalProviders: { type: Array, attribute: 'external-providers' },
42
44
  _busy: { state: true },
43
45
  _error: { state: true },
44
46
  _view: { state: true },
@@ -51,6 +53,8 @@ class CromboloLoginElement extends LitElement {
51
53
  this.logoImage = ''
52
54
  this.usernameIsEmail = true
53
55
  this.allowPasswordReset = false
56
+ this.disableLocalLogin = false
57
+ this.externalProviders = []
54
58
  this._busy = false
55
59
  this._error = ''
56
60
  this._notice = ''
@@ -63,6 +67,9 @@ class CromboloLoginElement extends LitElement {
63
67
  ? { kind: 'reset', user: params.get('userId'), code: params.get('resetCode') }
64
68
  : undefined
65
69
  this._view = this._link ? 'checking' : 'login'
70
+ // An identity provider's sign-in that came back refused says why.
71
+ const external = params.get('externalLoginError')
72
+ if (external) this._error = external
66
73
  }
67
74
 
68
75
  connectedCallback() {
@@ -182,7 +189,9 @@ class CromboloLoginElement extends LitElement {
182
189
  return
183
190
  }
184
191
  if (response.status === 402) {
185
- this._error = 'This account requires two-factor authentication, which is not yet supported.'
192
+ const challenge = await response.json().catch(() => ({}))
193
+ this._twoFactorProvider = challenge.enabledTwoFactorProviderNames?.[0]
194
+ this._view = 'two-factor'
186
195
  } else if (response.status === 403) {
187
196
  this._error = 'This account is locked or not approved.'
188
197
  } else if (response.status === 401) {
@@ -197,6 +206,57 @@ class CromboloLoginElement extends LitElement {
197
206
  }
198
207
  }
199
208
 
209
+ async #verifyTwoFactor(event) {
210
+ event.preventDefault()
211
+ if (this._busy) return
212
+ const code = event.target.elements.code.value.trim()
213
+ if (!code) return
214
+ this._busy = true
215
+ this._error = ''
216
+ try {
217
+ const response = await this.#post('/security/back-office/verify-2fa', {
218
+ provider: this._twoFactorProvider,
219
+ code,
220
+ })
221
+ if (response.ok) {
222
+ window.location.href = localTarget(this.returnUrl, '/umbraco')
223
+ return
224
+ }
225
+ if (response.status === 401) {
226
+ this._view = 'login'
227
+ this._error = 'That took too long. Sign in again.'
228
+ } else if (response.status === 403) {
229
+ this._view = 'login'
230
+ this._error = 'This account is locked or not approved.'
231
+ } else {
232
+ this._error = 'That code is not right. Try the next one.'
233
+ }
234
+ } catch {
235
+ this._error = 'Could not reach the server.'
236
+ } finally {
237
+ this._busy = false
238
+ }
239
+ }
240
+
241
+ #twoFactorForm() {
242
+ return html`
243
+ <h1 class="uui-h3">Enter your code</h1>
244
+ <p>Enter the six-digit code from your authenticator app, or one of your recovery codes.</p>
245
+ <form @submit=${this.#verifyTwoFactor}>
246
+ <uui-form-layout-item>
247
+ <uui-label for="code" slot="label" required>Code</uui-label>
248
+ <uui-input id="code" name="code" autocomplete="one-time-code" required ?disabled=${this._busy}></uui-input>
249
+ </uui-form-layout-item>
250
+ ${this.#messages()}
251
+ <uui-button type="submit" look="primary" color="positive" .state=${this._busy ? 'waiting' : undefined} label="Verify"></uui-button>
252
+ <uui-button look="link" label="Back to sign in" @click=${() => {
253
+ this._error = ''
254
+ this._view = 'login'
255
+ }}></uui-button>
256
+ </form>
257
+ `
258
+ }
259
+
200
260
  #messages() {
201
261
  return html`${this._notice ? html`<p id="notice" role="status">${this._notice}</p>` : nothing}
202
262
  ${this._error ? html`<p id="error" role="alert">${this._error}</p>` : nothing}`
@@ -245,7 +305,9 @@ class CromboloLoginElement extends LitElement {
245
305
  ? this.#newPasswordForm()
246
306
  : this._view === 'forgot'
247
307
  ? this.#resetRequestForm()
248
- : this.#loginForm()
308
+ : this._view === 'two-factor'
309
+ ? this.#twoFactorForm()
310
+ : this.#loginForm()
249
311
  return html`
250
312
  <div id="layout">
251
313
  <uui-box id="card">
@@ -256,7 +318,25 @@ class CromboloLoginElement extends LitElement {
256
318
  `
257
319
  }
258
320
 
321
+ /** A button per backoffice identity provider, each returning here signed in. */
322
+ #providers() {
323
+ if (!this.externalProviders?.length) return nothing
324
+ return html`<div id="providers">
325
+ ${this.externalProviders.map(
326
+ (provider) =>
327
+ html`<uui-button look="outline" href=${provider.url} label=${`Sign in with ${provider.name}`}>Sign in with ${provider.name}</uui-button>`,
328
+ )}
329
+ </div>`
330
+ }
331
+
259
332
  #loginForm() {
333
+ // With local sign-in off, the providers are the only way in.
334
+ if (this.disableLocalLogin)
335
+ return html`
336
+ <h1 class="uui-h3">Sign in</h1>
337
+ ${this.#messages()}
338
+ ${this.#providers()}
339
+ `
260
340
  return html`
261
341
  <h1 class="uui-h3">Sign in</h1>
262
342
  <form @submit=${this.#submit}>
@@ -301,6 +381,7 @@ class CromboloLoginElement extends LitElement {
301
381
  : nothing
302
382
  }
303
383
  </form>
384
+ ${this.#providers()}
304
385
  `
305
386
  }
306
387
 
@@ -342,6 +423,12 @@ class CromboloLoginElement extends LitElement {
342
423
  margin: 0;
343
424
  color: var(--uui-color-danger, #d42054);
344
425
  }
426
+ #providers {
427
+ display: flex;
428
+ flex-direction: column;
429
+ gap: var(--uui-size-space-3, 9px);
430
+ margin-top: var(--uui-size-space-5, 18px);
431
+ }
345
432
  #notice {
346
433
  margin: 0;
347
434
  color: var(--uui-color-positive, #0b8152);
@@ -0,0 +1,134 @@
1
+ /**
2
+ * The page a push to a protected environment is approved on (story 107). The
3
+ * person who pushed shares its link; someone else allowed to push here looks at
4
+ * who sent it and when, and approves or declines it.
5
+ */
6
+ import { css, html, LitElement, nothing } from '@umbraco-cms/backoffice/external/lit'
7
+ import '@umbraco-cms/backoffice/external/uui'
8
+
9
+ const API = '/umbraco/remote/v1/jobs'
10
+
11
+ class CromboloRemoteApprovalElement extends LitElement {
12
+ static properties = {
13
+ job: { type: String },
14
+ logoImage: { type: String, attribute: 'logo-image' },
15
+ _job: { state: true },
16
+ _error: { state: true },
17
+ _busy: { state: true },
18
+ }
19
+
20
+ constructor() {
21
+ super()
22
+ this.job = ''
23
+ this.logoImage = ''
24
+ this._job = undefined
25
+ this._error = ''
26
+ this._busy = false
27
+ }
28
+
29
+ connectedCallback() {
30
+ super.connectedCallback()
31
+ this.#load()
32
+ }
33
+
34
+ async #load() {
35
+ const response = await fetch(`${API}/${encodeURIComponent(this.job)}`, {
36
+ credentials: 'include',
37
+ })
38
+ const body = await response.json().catch(() => ({}))
39
+ if (!response.ok) {
40
+ this._error = body.title ?? 'There is no such push.'
41
+ return
42
+ }
43
+ this._job = body
44
+ }
45
+
46
+ async #decide(approve) {
47
+ this._busy = true
48
+ this._error = ''
49
+ const response = await fetch(
50
+ `${API}/${encodeURIComponent(this.job)}/${approve ? 'approve' : 'decline'}`,
51
+ {
52
+ method: 'POST',
53
+ credentials: 'include',
54
+ },
55
+ )
56
+ const body = await response.json().catch(() => ({}))
57
+ this._busy = false
58
+ if (!response.ok) {
59
+ this._error = body.title ?? 'That could not be done.'
60
+ return
61
+ }
62
+ this._job = body
63
+ }
64
+
65
+ #body() {
66
+ const job = this._job
67
+ if (!job)
68
+ return this._error ? html`<p id="error">${this._error}</p>` : html`<uui-loader></uui-loader>`
69
+ const when = new Date(job.createdAt).toLocaleString()
70
+ if (job.state !== 'approval-required')
71
+ return html`<p>
72
+ ${job.state === 'refused' ? (job.problem?.title ?? 'This push was declined.') : 'This push has been decided.'}
73
+ </p>`
74
+ return html`
75
+ <p>
76
+ <strong>${job.requestedBy}</strong> wants to replace <strong>${job.environment}</strong> with a copy of their
77
+ site, sent ${when}. Everything on ${job.environment} that is not in it goes to the recycle bin, and a backup is
78
+ taken first.
79
+ </p>
80
+ ${this._error ? html`<p id="error" role="alert">${this._error}</p>` : nothing}
81
+ <div id="actions">
82
+ <uui-button look="primary" color="positive" label="Approve" .state=${this._busy ? 'waiting' : undefined}
83
+ @click=${() => this.#decide(true)}></uui-button>
84
+ <uui-button look="outline" color="danger" label="Decline" @click=${() => this.#decide(false)}></uui-button>
85
+ </div>`
86
+ }
87
+
88
+ render() {
89
+ return html`<div id="layout">
90
+ <uui-box id="card">
91
+ ${this.logoImage ? html`<img id="logo" src=${this.logoImage} alt="" />` : nothing}
92
+ <h1 class="uui-h3">Approve a push</h1>
93
+ ${this.#body()}
94
+ </uui-box>
95
+ </div>`
96
+ }
97
+
98
+ static styles = css`
99
+ :host {
100
+ display: block;
101
+ min-height: 100vh;
102
+ background: var(--uui-color-surface-alt, #f4f4f4);
103
+ }
104
+ #layout {
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ min-height: 100vh;
109
+ padding: 16px;
110
+ box-sizing: border-box;
111
+ }
112
+ #card {
113
+ width: 100%;
114
+ max-width: 480px;
115
+ }
116
+ #logo {
117
+ display: block;
118
+ height: 48px;
119
+ margin-bottom: var(--uui-size-space-4, 12px);
120
+ }
121
+ h1 {
122
+ margin: 0 0 var(--uui-size-space-5, 18px);
123
+ }
124
+ #actions {
125
+ display: flex;
126
+ gap: var(--uui-size-space-3, 9px);
127
+ }
128
+ #error {
129
+ color: var(--uui-color-danger, #d42054);
130
+ }
131
+ `
132
+ }
133
+
134
+ customElements.define('crombolo-remote-approval', CromboloRemoteApprovalElement)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crombolo/backoffice-host",
3
- "version": "0.13.2",
3
+ "version": "0.14.4",
4
4
  "description": "Serves the Umbraco backoffice SPA: shell HTML, import map and package manifests.",
5
5
  "license": "MIT",
6
6
  "author": "Crombolo",
@@ -31,7 +31,7 @@
31
31
  "access": "public"
32
32
  },
33
33
  "dependencies": {
34
- "@crombolo/core": "0.13.2",
34
+ "@crombolo/core": "0.14.4",
35
35
  "@crombolo/backoffice-dist": "18.2.0"
36
36
  }
37
37
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "../../../backoffice-dist/dist/umbraco-package-schema.json",
3
3
  "name": "Crombolo Assistant",
4
4
  "id": "Crombolo.Assistant",
5
- "version": "0.13.2",
5
+ "version": "0.14.4",
6
6
  "allowPublicAccess": false,
7
7
  "extensions": [
8
8
  {
@@ -1,25 +1,20 @@
1
1
  /**
2
2
  * The two places the vendored client names the product in code rather than in a
3
- * string table, where the localisation override cannot reach.
3
+ * string table.
4
4
  *
5
- * `view.controller.js` builds the page title with a hardcoded string rather than
6
- * a localisation key, so the dictionary override cannot reach it. Patching the
7
- * vendored file would work until the next `vendor:backoffice`, which is the
8
- * reason nothing here patches vendored files.
9
- *
10
- * So it is corrected as it is written. The suffix is the site's own name, which
11
- * the shell states in `<meta name="application-name">` — so a site called
12
- * something other than Crombolo gets its own name rather than a second hardcoded
13
- * one.
5
+ * `view.controller.js` builds the page title with a hardcoded suffix, which
6
+ * `vendor:backoffice` rebrands to Crombolo. The suffix should be the site's own
7
+ * name, which the shell states in `<meta name="application-name">` — so a site
8
+ * called something other than Crombolo gets its own name rather than ours.
14
9
  *
15
10
  * Not read from `document.title`, although the shell puts the name there too.
16
11
  * This module loads after the client has started, and by then the client may
17
- * have written a title of its own. Taking `Content | Umbraco` for the site's
18
- * name makes every correction end in ` | Umbraco` again: the observer below
12
+ * have written a title of its own. Taking `Content | Crombolo` for the site's
13
+ * name makes every correction end in ` | Crombolo` again: the observer below
19
14
  * then answers its own change, forever, and the page never gets its main thread
20
15
  * back.
21
16
  */
22
- const CLIENT_SUFFIX = ' | Umbraco'
17
+ const CLIENT_SUFFIX = ' | Crombolo'
23
18
 
24
19
  /** The site's name as the shell stated it. */
25
20
  export function brandName(doc = document) {
@@ -70,7 +65,7 @@ function correctLogoLink() {
70
65
  if (!anchor) return false
71
66
  anchor.href = 'https://github.com/kineco-au/crombolo-cms'
72
67
  anchor.textContent = 'Crombolo on GitHub'
73
- const image = logo.shadowRoot.querySelector('img[alt="Umbraco"]')
68
+ const image = logo.shadowRoot.querySelector('img#logo')
74
69
  if (image) image.alt = brandName()
75
70
  return true
76
71
  }
@@ -46,6 +46,9 @@ export default {
46
46
  "Hello there and welcome to Crombolo! In just 1 minute you'll be good to go, we just need you to setup a password and add a picture for your avatar.",
47
47
  userinviteExpiredMessage:
48
48
  'Welcome to Crombolo! Unfortunately your invite has expired. Please contact your administrator to get a new one.',
49
+
50
+ // The heading over the remote permissions (story 107) in a user group.
51
+ 'permissionsEntityGroup_crombolo-remote': 'Moving whole sites',
49
52
  },
50
53
  general: {
51
54
  umbracoInfo: 'Crombolo info',
@@ -106,6 +109,14 @@ export default {
106
109
  searchUmbracoIssues: 'Search Crombolo issues',
107
110
  searchUmbracoIssuesOnGithub: 'Search Crombolo issues on GitHub',
108
111
  },
112
+ redirectUrls: {
113
+ // Followed by `CROMBOLO_TRACK_REDIRECTS`, which `vendor:backoffice` puts in
114
+ // place of the .NET setting; it tracks when true, the inverse of Umbraco's.
115
+ disableUrlTrackerInstruction:
116
+ "Redirect URL tracking is set in the site's environment. To turn it off, set this variable to false:",
117
+ enableUrlTrackerInstruction:
118
+ "Redirect URL tracking is set in the site's environment. To turn it on, set this variable to true, or remove it:",
119
+ },
109
120
  defaultdialogs: {
110
121
  linkYourConfirm:
111
122
  'You are about to link your Crombolo and {0} accounts and you will be redirected to {0} to complete the process.',
@@ -68,6 +68,8 @@
68
68
  --uui-color-header-surface: var(--crombolo-orange);
69
69
  --uui-color-header-contrast: rgb(84 50 31 / 80%);
70
70
  --uui-color-header-contrast-emphasis: var(--crombolo-cocoa);
71
+ /* The active section's marker, dark so it stands out from the white submenu below. */
72
+ --crombolo-header-current: var(--crombolo-orange-text);
71
73
 
72
74
  /* Pure white panels on a warm off-white page, so cards still separate. */
73
75
  --uui-color-background: var(--crombolo-cream);
@@ -3,45 +3,6 @@
3
3
  // plugin/branding/localization-en.js curates, and "content type" where
4
4
  // Umbraco says "document type".
5
5
  export default {
6
- "login": {
7
- "instruction": "سجل الدخول إلى Crombolo"
8
- },
9
- "user": {
10
- "changePasswordDescription": "يمكنك تغيير كلمة مرورك للوصول إلى واجهة Crombolo من خلال ملء النموذج أدناه والنقر على زر 'تغيير كلمة المرور'",
11
- "createUserHelp": "قم بإنشاء مستخدمين جدد لمنحهم الوصول إلى Crombolo. عند إنشاء مستخدم جديد، سيتم توليد كلمة مرور يمكنك مشاركتها مع المستخدم.",
12
- "inviteUserHelp": "ادعُ مستخدمين جدد لمنحهم الوصول إلى Crombolo. سيتم إرسال بريد إلكتروني دعوة إلى المستخدم مع معلومات حول كيفية تسجيل الدخول إلى Crombolo. تدوم الدعوات لمدة 72 ساعة.",
13
- "noConsole": "تعطيل الوصول إلى Crombolo",
14
- "userCreatedSuccessHelp": "تم إنشاء المستخدم الجديد بنجاح. لتسجيل الدخول إلى Crombolo استخدم كلمة المرور أدناه.",
15
- "userInvitedSuccessHelp": "تم إرسال دعوة إلى المستخدم الجديد مع تفاصيل حول كيفية تسجيل الدخول إلى Crombolo.",
16
- "userinviteWelcomeMessage": "مرحباً بك في Crombolo! في دقيقة واحدة فقط ستكون جاهزاً، نحن فقط بحاجة منك لتعيين كلمة مرور وإضافة صورة لملفك الشخصي.",
17
- "userinviteExpiredMessage": "مرحباً بك في Crombolo! للأسف انتهت صلاحية دعوتك. يرجى الاتصال بالمسؤول الخاص بك واطلب منهم إعادة إرسالها.",
18
- "documentType": "نوع المحتوى"
19
- },
20
- "general": {
21
- "umbracoInfo": "معلومات Crombolo"
22
- },
23
- "visuallyHiddenTexts": {
24
- "searchOverlayTitle": "بحث في مكتب Crombolo الخلفي"
25
- },
26
- "languages": {
27
- "defaultLanguageHelp": "يمكن لموقع Crombolo أن يحتوي فقط على لغة افتراضية واحدة."
28
- },
29
- "paste": {
30
- "errorMessage": "النص الذي تحاول لصقه يحتوي على أحرف خاصة أو تنسيق. قد يكون ذلك بسبب نسخ النص من Microsoft Word. يمكن لـ Crombolo إزالة الأحرف الخاصة أو التنسيق تلقائيًا، بحيث يكون المحتوى الملصق أكثر ملاءمة للويب.\n"
31
- },
32
- "dashboard": {
33
- "nothinghappens": "إذا لم يفتح Crombolo، قد تحتاج إلى السماح بالنوافذ المنبثقة من هذا الموقع"
34
- },
35
- "logViewer": {
36
- "searchUmbracoSource": "البحث في مصدر Crombolo",
37
- "searchWithinUmbracoSourceCodeOnGithub": "البحث ضمن كود مصدر Crombolo على Github",
38
- "searchUmbracoIssues": "البحث في قضايا Crombolo",
39
- "searchUmbracoIssuesOnGithub": "البحث في قضايا Crombolo على Github"
40
- },
41
- "defaultdialogs": {
42
- "linkYourConfirm": "أنت على وشك ربط حسابك في Crombolo وحسابك في {0} وسيتم توجيهك إلى {0} للتأكيد.",
43
- "unLinkYourConfirm": "أنت على وشك إلغاء ربط حسابك في Crombolo وحسابك في {0} وسيتم تسجيل خروجك."
44
- },
45
6
  "actions": {
46
7
  "changeDocType": "تغيير نوع المحتوى",
47
8
  "exportDocumentType": "تصدير نوع المحتوى",
@@ -87,6 +48,9 @@ export default {
87
48
  "treeHeaders": {
88
49
  "documentTypes": "أنواع المحتوى"
89
50
  },
51
+ "user": {
52
+ "documentType": "نوع المحتوى"
53
+ },
90
54
  "references": {
91
55
  "labelUsedByDocumentTypes": "مستشهد به من قبل أنواع المحتوى التالية"
92
56
  },
@@ -3,35 +3,6 @@
3
3
  // plugin/branding/localization-en.js curates, and "content type" where
4
4
  // Umbraco says "document type".
5
5
  export default {
6
- "user": {
7
- "changePasswordDescription": "Možete promijeniti lozinku za pristup Crombolo backofficeu popunjavanjem\n izađite iz donjeg obrasca i kliknite na dugme 'Promijeni lozinku'\n ",
8
- "createUserHelp": "Kreirajte nove korisnike da im date pristup Crombolo. Kada se novom korisniku kreira lozinka\n će biti generisano koje možete podijeliti s korisnikom.\n ",
9
- "inviteUserHelp": "Pozovite nove korisnike da im daju pristup Crombolo. E-mail sa pozivom će biti poslan na\n korisnik s informacijama o tome kako se prijaviti na Crombolo. Pozivnice traju 72 sata.\n ",
10
- "noConsole": "Onemogućite pristup Crombolo-u",
11
- "userCreatedSuccessHelp": "Novi korisnik je uspješno kreiran. Za prijavu na Crombolo koristite\n lozinka ispod.\n ",
12
- "userInvitedSuccessHelp": "Novom korisniku je poslana pozivnica s detaljima o tome kako se prijaviti\n Crombolo.\n ",
13
- "userinviteWelcomeMessage": "Pozdrav i dobrodošli u Crombolo! Za samo 1 minut možete krenuti, mi samo trebate postaviti lozinku.",
14
- "userinviteExpiredMessage": "Dobrodošli u Crombolo! Nažalost, vaš poziv je istekao. Molimo kontaktirajte svoju\n administratora i zamolite ih da ga ponovo pošalju.\n ",
15
- "documentType": "Tip sadržaja"
16
- },
17
- "general": {
18
- "umbracoInfo": "Crombolo info"
19
- },
20
- "languages": {
21
- "defaultLanguageHelp": "Crombolo stranica može imati samo jedan zadani jezik."
22
- },
23
- "paste": {
24
- "errorMessage": "Tekst koji pokušavate zalijepiti sadrži posebne znakove ili formatiranje. Ovo bi moglo biti\n uzrokovano kopiranjem teksta iz programa Microsoft Word. Crombolo može automatski ukloniti posebne znakove ili formatiranje, tako da\n zalijepljeni sadržaj će biti prikladniji za web.\n "
25
- },
26
- "dashboard": {
27
- "nothinghappens": "Ako se Crombolo ne otvara, možda ćete morati dozvoliti iskačuće prozore sa ove stranice"
28
- },
29
- "logViewer": {
30
- "searchUmbracoSource": "Pretraži Crombolo Source",
31
- "searchWithinUmbracoSourceCodeOnGithub": "Pretraži Crombolo source code on Github-u",
32
- "searchUmbracoIssues": "Pretraži Crombolo Issues",
33
- "searchUmbracoIssuesOnGithub": "Pretraži Crombolo Issues na Github-u"
34
- },
35
6
  "actions": {
36
7
  "changeDocType": "Promijeni Tip Sadržaja",
37
8
  "exportDocumentType": "Izvezi Tip Sadržaja",
@@ -76,6 +47,9 @@ export default {
76
47
  "treeHeaders": {
77
48
  "documentTypes": "Tipovi sadržaja"
78
49
  },
50
+ "user": {
51
+ "documentType": "Tip sadržaja"
52
+ },
79
53
  "references": {
80
54
  "labelUsedByDocumentTypes": "Koristi se u tipovima sadržaja"
81
55
  },