@crombolo/backoffice-host 0.0.0-stage → 0.9.1

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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +69 -0
  3. package/assets/login.js +352 -0
  4. package/package.json +35 -4
  5. package/plugin/assistant/client.js +53 -0
  6. package/plugin/assistant/drawer.js +865 -0
  7. package/plugin/assistant/umbraco-package.json +16 -0
  8. package/plugin/branding/backdrop.svg +1 -0
  9. package/plugin/branding/branding.js +100 -0
  10. package/plugin/branding/bundle-builder-labels.js +63 -0
  11. package/plugin/branding/client-credentials.js +49 -0
  12. package/plugin/branding/components-tree.js +40 -0
  13. package/plugin/branding/content-type-labels.js +27 -0
  14. package/plugin/branding/favicon.svg +1 -0
  15. package/plugin/branding/help-menu.js +46 -0
  16. package/plugin/branding/icon-mark.js +15 -0
  17. package/plugin/branding/icon-registry.js +40 -0
  18. package/plugin/branding/localization-en.js +115 -0
  19. package/plugin/branding/log-viewer-menu.js +70 -0
  20. package/plugin/branding/logo-header.svg +1 -0
  21. package/plugin/branding/logo-surface.svg +1 -0
  22. package/plugin/branding/query-builder.js +28 -0
  23. package/plugin/branding/sign-in.js +80 -0
  24. package/plugin/branding/sysinfo.js +89 -0
  25. package/plugin/branding/theme.css +102 -0
  26. package/plugin/bundles-client.js +34 -0
  27. package/plugin/bundles-created-overview.js +150 -0
  28. package/plugin/bundles-created.js +70 -0
  29. package/plugin/bundles-installed.js +147 -0
  30. package/plugin/bundles-marketplace.js +199 -0
  31. package/plugin/changes-dashboard.js +132 -0
  32. package/plugin/element-type-hint.js +58 -0
  33. package/plugin/form-picker.js +116 -0
  34. package/plugin/forms-client.js +52 -0
  35. package/plugin/forms-entries.js +244 -0
  36. package/plugin/forms-overview.js +380 -0
  37. package/plugin/localizations/ar.js +97 -0
  38. package/plugin/localizations/bs.js +86 -0
  39. package/plugin/localizations/cs.js +73 -0
  40. package/plugin/localizations/cy.js +92 -0
  41. package/plugin/localizations/da.js +101 -0
  42. package/plugin/localizations/de.js +79 -0
  43. package/plugin/localizations/en.js +71 -0
  44. package/plugin/localizations/es.js +52 -0
  45. package/plugin/localizations/fr.js +78 -0
  46. package/plugin/localizations/he.js +24 -0
  47. package/plugin/localizations/hr.js +89 -0
  48. package/plugin/localizations/it.js +86 -0
  49. package/plugin/localizations/ja.js +43 -0
  50. package/plugin/localizations/ko.js +24 -0
  51. package/plugin/localizations/nb.js +44 -0
  52. package/plugin/localizations/nl.js +85 -0
  53. package/plugin/localizations/pl.js +44 -0
  54. package/plugin/localizations/pt-br.js +25 -0
  55. package/plugin/localizations/pt.js +100 -0
  56. package/plugin/localizations/ro.js +9 -0
  57. package/plugin/localizations/ru.js +46 -0
  58. package/plugin/localizations/sv.js +46 -0
  59. package/plugin/localizations/tr.js +80 -0
  60. package/plugin/localizations/uk.js +46 -0
  61. package/plugin/localizations/umbraco-package.json +279 -0
  62. package/plugin/localizations/vi.js +104 -0
  63. package/plugin/localizations/zh-tw.js +38 -0
  64. package/plugin/localizations/zh.js +38 -0
  65. package/plugin/read-only-banner.js +48 -0
  66. package/plugin/report-client.js +13 -0
  67. package/plugin/seo/client.js +35 -0
  68. package/plugin/seo/dashboard.js +242 -0
  69. package/plugin/seo/document-view.js +161 -0
  70. package/plugin/seo/type-hint.js +45 -0
  71. package/plugin/seo/umbraco-package.json +61 -0
  72. package/plugin/settings-dashboard.js +123 -0
  73. package/plugin/tsx-editors.js +337 -0
  74. package/plugin/tsx-snippets.js +63 -0
  75. package/plugin/umbraco-package.json +298 -0
  76. package/plugin/welcome-dashboard.js +106 -0
  77. package/src/branding.ts +59 -0
  78. package/src/extensions.ts +228 -0
  79. package/src/graphics.ts +87 -0
  80. package/src/index.ts +7 -0
  81. package/src/login-assets.ts +18 -0
  82. package/src/manifests.ts +154 -0
  83. package/src/paths.ts +117 -0
  84. package/src/shell.ts +139 -0
  85. package/src/static.ts +107 -0
  86. package/src/text-imports.d.ts +23 -0
  87. package/README.md +0 -3
@@ -0,0 +1,161 @@
1
+ /**
2
+ * The "Search & sharing" view on a page whose type includes those fields: how
3
+ * the page will look in search results, and where each value comes from.
4
+ *
5
+ * The values come from the server, which works them out with the same code the
6
+ * page's head uses, so the preview cannot disagree with what is served. It
7
+ * reads the last saved version, so an editor sees a change once it is saved.
8
+ */
9
+ import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document'
10
+ import { css, html, nothing } from '@umbraco-cms/backoffice/external/lit'
11
+ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
12
+ import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property'
13
+ import { readPreview } from './client.js'
14
+
15
+ const SOURCES = {
16
+ page: 'From this page',
17
+ template: 'From the page design',
18
+ site: 'Site setting',
19
+ name: 'Page name',
20
+ none: 'Not set',
21
+ }
22
+
23
+ export default class CromboloSeoDocumentViewElement extends UmbLitElement {
24
+ static properties = {
25
+ _preview: { state: true },
26
+ _error: { state: true },
27
+ }
28
+
29
+ #unique = undefined
30
+ #culture = null
31
+
32
+ constructor() {
33
+ super()
34
+ this.consumeContext(UMB_DOCUMENT_WORKSPACE_CONTEXT, (context) => {
35
+ if (!context) return
36
+ this.observe(context.unique, (unique) => {
37
+ this.#unique = unique
38
+ this.#load()
39
+ })
40
+ })
41
+ this.consumeContext(UMB_PROPERTY_DATASET_CONTEXT, (dataset) => {
42
+ this.#culture = dataset?.getVariantId?.()?.culture ?? null
43
+ this.#load()
44
+ })
45
+ }
46
+
47
+ async #load() {
48
+ if (!this.#unique) return
49
+ try {
50
+ this._preview = await readPreview(this.#unique, this.#culture)
51
+ this._error = undefined
52
+ } catch (error) {
53
+ this._error = error.message
54
+ }
55
+ }
56
+
57
+ #row(label, item, render = (value) => value) {
58
+ return html`
59
+ <tr>
60
+ <th>${label}</th>
61
+ <td>${item.value ? render(item.value) : html`<em>Nothing yet</em>`}</td>
62
+ <td><uui-tag look="secondary">${SOURCES[item.source] ?? item.source}</uui-tag></td>
63
+ </tr>
64
+ `
65
+ }
66
+
67
+ render() {
68
+ if (this._error) return html`<uui-box><p>${this._error}</p></uui-box>`
69
+ const preview = this._preview
70
+ if (!preview) return html`<uui-loader></uui-loader>`
71
+ const { meta, snippet } = preview
72
+ return html`
73
+ <uui-box headline="In search results">
74
+ <div class="snippet">
75
+ <div class="url">${snippet.url || 'This page has no address yet'}</div>
76
+ <div class="title">${snippet.title}</div>
77
+ <div class="description">
78
+ ${snippet.description || html`<em>Search engines will pick some text from the page.</em>`}
79
+ </div>
80
+ </div>
81
+ ${
82
+ meta.noindex.value
83
+ ? html`<p class="warning">
84
+ ${
85
+ meta.noindex.source === 'site'
86
+ ? 'The whole site is hidden from search engines in Settings.'
87
+ : 'This page asks search engines to leave it out of their results.'
88
+ }
89
+ </p>`
90
+ : nothing
91
+ }
92
+ <p class="note">
93
+ Shows the last saved version${preview.published ? '' : ', which is not published yet'}.
94
+ </p>
95
+ </uui-box>
96
+ <uui-box headline="Where each value comes from">
97
+ <table>
98
+ ${this.#row('Title', meta.title)}
99
+ ${this.#row('Description', meta.description)}
100
+ ${this.#row('Sharing image', meta.image, (src) => html`<img src=${src} alt="" />`)}
101
+ ${this.#row('Preferred address', meta.canonical)}
102
+ </table>
103
+ </uui-box>
104
+ `
105
+ }
106
+
107
+ static styles = css`
108
+ :host {
109
+ display: block;
110
+ padding: var(--uui-size-layout-1);
111
+ }
112
+ uui-box + uui-box {
113
+ margin-top: var(--uui-size-layout-1);
114
+ }
115
+ .snippet {
116
+ max-width: 600px;
117
+ font-family: arial, sans-serif;
118
+ }
119
+ .snippet .url {
120
+ color: var(--uui-color-text-alt);
121
+ font-size: 14px;
122
+ overflow-wrap: anywhere;
123
+ }
124
+ .snippet .title {
125
+ color: #1a0dab;
126
+ font-size: 20px;
127
+ margin: 4px 0;
128
+ }
129
+ .snippet .description {
130
+ color: var(--uui-color-text);
131
+ font-size: 14px;
132
+ }
133
+ .warning {
134
+ color: var(--uui-color-danger);
135
+ }
136
+ .note {
137
+ color: var(--uui-color-text-alt);
138
+ font-size: 12px;
139
+ }
140
+ table {
141
+ width: 100%;
142
+ border-collapse: collapse;
143
+ }
144
+ th,
145
+ td {
146
+ text-align: left;
147
+ padding: var(--uui-size-space-3);
148
+ border-bottom: 1px solid var(--uui-color-divider);
149
+ vertical-align: top;
150
+ }
151
+ th {
152
+ width: 12em;
153
+ }
154
+ img {
155
+ max-width: 240px;
156
+ max-height: 140px;
157
+ }
158
+ `
159
+ }
160
+
161
+ customElements.define('crombolo-seo-document-view', CromboloSeoDocumentViewElement)
@@ -0,0 +1,45 @@
1
+ /**
2
+ * A note in the document-type workspace footer for the "Search & sharing" type:
3
+ * its own fields come with the CMS and stay, but a site may add fields beside
4
+ * them. The server refuses removing or changing them; this says so up front.
5
+ */
6
+ import { UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/document-type'
7
+ import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api'
8
+ import { css, html, LitElement, nothing } from '@umbraco-cms/backoffice/external/lit'
9
+
10
+ const SEO_FIELDS_KEY = '5e0f1c2a-7b3d-4c8e-9a61-0d2b4f6e8a13'
11
+
12
+ export default class CromboloSeoFieldsHintElement extends UmbElementMixin(LitElement) {
13
+ static properties = { _unique: { state: true } }
14
+
15
+ constructor() {
16
+ super()
17
+ this._unique = undefined
18
+ this.consumeContext(UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT, (context) => {
19
+ if (!context) return
20
+ this.observe(context.unique, (value) => {
21
+ this._unique = value
22
+ })
23
+ })
24
+ }
25
+
26
+ render() {
27
+ if (this._unique?.toLowerCase() !== SEO_FIELDS_KEY) return nothing
28
+ return html`<uui-tag
29
+ color="default"
30
+ look="secondary"
31
+ title="Pages that include this type get these fields for search results and sharing. The fields it comes with cannot be removed or changed, but you can add your own beside them."
32
+ >
33
+ Provided by Crombolo — add your own fields beside these
34
+ </uui-tag>`
35
+ }
36
+
37
+ static styles = css`
38
+ :host {
39
+ display: flex;
40
+ align-items: center;
41
+ }
42
+ `
43
+ }
44
+
45
+ customElements.define('crombolo-seo-fields-hint', CromboloSeoFieldsHintElement)
@@ -0,0 +1,61 @@
1
+ {
2
+ "$schema": "../../../backoffice-dist/dist/umbraco-package-schema.json",
3
+ "name": "Crombolo Search & sharing",
4
+ "id": "Crombolo.Seo",
5
+ "version": "0.9.1",
6
+ "allowPublicAccess": false,
7
+ "extensions": [
8
+ {
9
+ "type": "dashboard",
10
+ "alias": "Crombolo.Dashboard.Seo",
11
+ "name": "Crombolo Search & Sharing Dashboard",
12
+ "element": "/crombolo-plugin/seo/dashboard.js",
13
+ "weight": 10,
14
+ "meta": {
15
+ "label": "Search & sharing",
16
+ "pathname": "search-and-sharing"
17
+ },
18
+ "conditions": [
19
+ {
20
+ "alias": "Umb.Condition.SectionAlias",
21
+ "match": "Umb.Section.Settings"
22
+ }
23
+ ]
24
+ },
25
+ {
26
+ "type": "workspaceView",
27
+ "alias": "Crombolo.WorkspaceView.Document.Seo",
28
+ "name": "Crombolo Search & Sharing Preview",
29
+ "element": "/crombolo-plugin/seo/document-view.js",
30
+ "weight": 90,
31
+ "meta": {
32
+ "label": "Search & sharing",
33
+ "pathname": "search-and-sharing",
34
+ "icon": "icon-search"
35
+ },
36
+ "conditions": [
37
+ {
38
+ "alias": "Umb.Condition.WorkspaceAlias",
39
+ "match": "Umb.Workspace.Document"
40
+ },
41
+ {
42
+ "alias": "Umb.Condition.WorkspaceContentTypeAlias",
43
+ "match": "seoFields"
44
+ }
45
+ ]
46
+ },
47
+ {
48
+ "type": "workspaceFooterApp",
49
+ "alias": "Crombolo.WorkspaceFooterApp.SeoFieldsHint",
50
+ "name": "Crombolo Search & Sharing Type Hint",
51
+ "element": "/crombolo-plugin/seo/type-hint.js",
52
+ "weight": 900,
53
+ "conditions": [
54
+ {
55
+ "alias": "Umb.Condition.WorkspaceAlias",
56
+ "match": "Umb.Workspace.DocumentType"
57
+ }
58
+ ]
59
+ }
60
+ ]
61
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * The Settings dashboard, in place of Umbraco's.
3
+ *
4
+ * Its manifest declares `overwrites: ['Umb.Dashboard.SettingsWelcome']`, the same
5
+ * way the Welcome dashboard takes the place of the news one. What it replaces is
6
+ * marketing for Umbraco's own training and consultancy — accurate for Umbraco and
7
+ * misleading here, and not the sort of thing to fix by renaming, since the
8
+ * services it points at are real and are not this project's.
9
+ *
10
+ * What it says instead is the thing a person in Settings actually needs to know:
11
+ * that types are files, and that changing one here writes that file.
12
+ */
13
+ // UmbLitElement, not plain LitElement: a dashboard is used as a controller host,
14
+ // and a plain element fails with `controllerHost.provideContext is not a function`.
15
+ import { css, html, nothing } from '@umbraco-cms/backoffice/external/lit'
16
+ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element'
17
+
18
+ /** Whether this site can send e-mail; the server answers with the reason. */
19
+ const emailStatus = async () => {
20
+ const href = document.querySelector('base')?.getAttribute('href') ?? '/umbraco/'
21
+ const response = await fetch(`${href.replace(/\/$/, '')}/crombolo/api/email`, {
22
+ credentials: 'include',
23
+ headers: { accept: 'application/json' },
24
+ })
25
+ if (!response.ok) throw new Error(`email responded ${response.status}`)
26
+ return response.json()
27
+ }
28
+
29
+ export default class CromboloSettingsDashboardElement extends UmbLitElement {
30
+ static properties = {
31
+ _email: { state: true },
32
+ }
33
+
34
+ connectedCallback() {
35
+ super.connectedCallback()
36
+ // A failure here leaves the section out rather than the dashboard broken:
37
+ // this is an explanation, not a control.
38
+ emailStatus().then(
39
+ (status) => {
40
+ this._email = status
41
+ },
42
+ () => {},
43
+ )
44
+ }
45
+
46
+ #email() {
47
+ const status = this._email
48
+ if (!status) return nothing
49
+ if (status.available)
50
+ return html`
51
+ <uui-box headline="E-mail">
52
+ <p>E-mail is sent through <strong>${status.provider}</strong>.</p>
53
+ </uui-box>
54
+ `
55
+ return html`
56
+ <uui-box headline="E-mail">
57
+ <p>${status.reason}</p>
58
+ ${
59
+ status.affects.length > 0
60
+ ? html`<p>Until one is configured, these are unavailable:</p>
61
+ <ul>
62
+ ${status.affects.map((feature) => html`<li>${feature}</li>`)}
63
+ </ul>`
64
+ : nothing
65
+ }
66
+ </uui-box>
67
+ `
68
+ }
69
+
70
+ render() {
71
+ return html`
72
+ <uui-box headline="Settings">
73
+ <p>
74
+ Content types, data types and languages are <strong>files</strong>, in
75
+ <code>schema/</code>, written as TOML. The database is a view of them: what you change
76
+ here is written to the file, and the file is what a deploy and every other node reads.
77
+ </p>
78
+ <p>
79
+ That is why a type can be reviewed in a pull request, and why a schema change carries a
80
+ version — a change that converts existing content is a major, and the backoffice moves
81
+ the number for you.
82
+ </p>
83
+ <p class="links">
84
+ <a href="https://github.com/kineco-au/crombolo-cms#schema-as-code" target="_blank" rel="noopener">
85
+ Schema as code
86
+ </a>
87
+ <a href="https://github.com/kineco-au/crombolo-cms#schema-in-shared-storage" target="_blank" rel="noopener">
88
+ Schema in shared storage
89
+ </a>
90
+ <a href="https://github.com/kineco-au/crombolo-cms/issues" target="_blank" rel="noopener">
91
+ Report an issue
92
+ </a>
93
+ </p>
94
+ </uui-box>
95
+ ${this.#email()}
96
+ `
97
+ }
98
+
99
+ static styles = css`
100
+ :host {
101
+ display: block;
102
+ padding: var(--uui-size-layout-1, 24px);
103
+ }
104
+ p {
105
+ max-width: 60ch;
106
+ line-height: 1.5;
107
+ }
108
+ .links {
109
+ display: flex;
110
+ gap: var(--uui-size-space-5, 18px);
111
+ flex-wrap: wrap;
112
+ }
113
+ uui-box + uui-box {
114
+ margin-top: var(--uui-size-layout-1, 24px);
115
+ }
116
+ ul {
117
+ max-width: 60ch;
118
+ line-height: 1.5;
119
+ }
120
+ `
121
+ }
122
+
123
+ customElements.define('crombolo-settings-dashboard', CromboloSettingsDashboardElement)