@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,16 @@
1
+ {
2
+ "$schema": "../../../backoffice-dist/dist/umbraco-package-schema.json",
3
+ "name": "Crombolo Assistant",
4
+ "id": "Crombolo.Assistant",
5
+ "version": "0.9.1",
6
+ "allowPublicAccess": false,
7
+ "extensions": [
8
+ {
9
+ "type": "headerApp",
10
+ "alias": "Crombolo.HeaderApp.Assistant",
11
+ "name": "Crombolo Assistant",
12
+ "element": "/crombolo-plugin/assistant/drawer.js",
13
+ "weight": 900
14
+ }
15
+ ]
16
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 800" role="presentation" aria-hidden="true"><title>crombolo</title><defs><radialGradient id="g" cx="50%" cy="38%" r="72%"><stop offset="0%" stop-color="#fff7ea"/><stop offset="100%" stop-color="#f3e3ce"/></radialGradient><mask id="m"><rect width="1200" height="800" fill="#000"/><g transform="translate(600 300) scale(1.6) translate(-200 -388.5)"><circle cx="200" cy="388.5" r="116.75" fill="#fff"/><path transform="matrix(0.025 0 0 -0.025 75 516)" d="M4285 8205 c-459 -94 -647 -159 -1014 -349 -249 -130 -544 -347 -669 -494 -26 -30 -82 -96 -125 -145 -218 -254 -207 -378 24 -266 295 143 924 329 1113 329 19 0 78 9 131 20 128 26 792 30 910 5 39 -8 108 -20 155 -25 378 -43 1093 -273 1364 -438 45 -27 114 -66 153 -86 91 -45 440 -278 525 -349 35 -29 91 -74 124 -100 651 -496 1244 -1578 1303 -2379 13 -165 47 -197 125 -115 86 90 236 513 236 663 0 23 9 86 20 139 25 122 27 345 4 450 -8 39 -19 111 -24 162 -50 500 -394 1231 -774 1648 -50 55 -116 130 -145 167 -30 37 -81 89 -115 115 -33 26 -114 96 -178 155 -117 106 -442 334 -560 394 -29 14 -100 55 -158 89 -97 59 -240 123 -516 231 -122 47 -541 156 -671 174 -45 6 -112 17 -150 25 -152 32 -900 19 -1088 -20z M3300 6458 c-58 -11 -142 -26 -186 -34 -80 -14 -243 -58 -414 -111 -742 -234 -1299 -909 -1300 -1575 0 -312 82 -391 218 -208 298 402 1010 790 1450 790 17 0 74 9 127 20 79 16 136 20 305 20 168 0 228 -4 310 -20 56 -11 124 -20 152 -20 406 0 1286 -389 1680 -742 56 -52 133 -117 171 -147 37 -29 88 -81 114 -115 26 -33 75 -92 108 -131 303 -346 645 -1054 645 -1336 0 -14 9 -68 20 -120 23 -111 27 -371 8 -505 -22 -155 37 -185 183 -93 657 414 699 1621 89 2594 -137 218 -431 585 -518 647 -11 7 -75 66 -144 130 -138 130 -157 146 -338 273 -329 233 -595 368 -985 501 -208 72 -585 164 -670 164 -18 0 -79 9 -135 20 -147 29 -730 28 -890 -2z M2909 4331 c-921 -186 -1424 -895 -1087 -1533 105 -199 210 -222 296 -65 284 517 979 758 1655 572 241 -66 501 -192 647 -314 25 -21 78 -64 119 -97 262 -210 502 -618 586 -998 64 -293 346 -107 440 291 45 189 45 192 -20 531 -50 262 -304 691 -560 947 -304 304 -848 601 -1190 650 -28 4 -90 16 -140 26 -153 32 -566 26 -746 -10z" fill="#000"/></g></mask></defs><rect width="1200" height="800" fill="url(#g)"/><g opacity="0.5"><circle cx="190" cy="690" r="120" fill="#ebc7b8" opacity="0.35"/><circle cx="1040" cy="150" r="90" fill="#cb6f48" opacity="0.3"/><circle cx="1090" cy="660" r="150" fill="#fdebd4" opacity="0.35"/></g><rect width="1200" height="800" fill="#cb6f48" mask="url(#m)" opacity="0.85"/></svg>
@@ -0,0 +1,100 @@
1
+ /**
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.
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.
14
+ *
15
+ * Not read from `document.title`, although the shell puts the name there too.
16
+ * 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
19
+ * then answers its own change, forever, and the page never gets its main thread
20
+ * back.
21
+ */
22
+ const CLIENT_SUFFIX = ' | Umbraco'
23
+
24
+ /** The site's name as the shell stated it. */
25
+ export function brandName(doc = document) {
26
+ const stated = doc.querySelector('meta[name="application-name"]')?.getAttribute('content')
27
+ return stated?.trim() || 'Crombolo'
28
+ }
29
+
30
+ /**
31
+ * `title` with the client's suffix replaced by the site's name.
32
+ *
33
+ * A fixed point: its own output comes back unchanged, whatever the name is —
34
+ * including a name that itself ends in the client's suffix. That is what makes
35
+ * it safe to call from an observer of the very thing it writes.
36
+ */
37
+ export function brandedTitle(title, brand) {
38
+ const branded = ` | ${brand}`
39
+ if (title.endsWith(branded) || !title.endsWith(CLIENT_SUFFIX)) return title
40
+ return `${title.slice(0, -CLIENT_SUFFIX.length)}${branded}`
41
+ }
42
+
43
+ /**
44
+ * The logo popover carries a link to umbraco.com, hardcoded in
45
+ * `backoffice-header-logo.element.js`. Its image is already served as crombolo's
46
+ * (see `graphics.ts`), so only the link is left.
47
+ *
48
+ * This reaches into a vendored element's shadow root, which is worth naming as
49
+ * the compromise it is: there is no extension point for the app shell's own
50
+ * chrome. It is written to do nothing at all if that element or its anchor is not
51
+ * what it was, so an upstream change costs a stale link and never an error.
52
+ */
53
+ function deepQuery(selector, root = document) {
54
+ const direct = root.querySelector(selector)
55
+ if (direct) return direct
56
+ // The header logo lives inside the header's shadow root, so a plain
57
+ // `document.querySelector` cannot see it. Nothing here is reachable without
58
+ // walking through the roots.
59
+ for (const element of root.querySelectorAll('*')) {
60
+ if (!element.shadowRoot) continue
61
+ const found = deepQuery(selector, element.shadowRoot)
62
+ if (found) return found
63
+ }
64
+ return null
65
+ }
66
+
67
+ function correctLogoLink() {
68
+ const logo = deepQuery('umb-backoffice-header-logo')
69
+ const anchor = logo?.shadowRoot?.querySelector('a[href*="umbraco.com"]')
70
+ if (!anchor) return false
71
+ anchor.href = 'https://github.com/kineco-au/crombolo-cms'
72
+ anchor.textContent = 'Crombolo on GitHub'
73
+ const image = logo.shadowRoot.querySelector('img[alt="Umbraco"]')
74
+ if (image) image.alt = brandName()
75
+ return true
76
+ }
77
+
78
+ export const onInit = () => {
79
+ const brand = brandName()
80
+ const correct = () => {
81
+ const corrected = brandedTitle(document.title, brand)
82
+ if (corrected !== document.title) document.title = corrected
83
+ }
84
+ correct()
85
+ // The header renders after the app boots, and the popover's contents with it.
86
+ let tries = 0
87
+ const timer = setInterval(() => {
88
+ if (correctLogoLink() || ++tries > 40) clearInterval(timer)
89
+ }, 250)
90
+ const head = document.querySelector('head')
91
+ const title = document.querySelector('title')
92
+ if (!head || !title) return
93
+ // The element is replaced as well as retitled, so both are watched.
94
+ new MutationObserver(correct).observe(title, {
95
+ childList: true,
96
+ characterData: true,
97
+ subtree: true,
98
+ })
99
+ new MutationObserver(correct).observe(head, { childList: true })
100
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The two labels Umbraco's bundle builder hard-codes.
3
+ *
4
+ * The builder workspace is vendored and its copy is English in the template
5
+ * rather than dictionary keys, so there is no key to override: `Name of the
6
+ * package` labels the name input and `Package Content` heads the picker box.
7
+ * Everything else in that screen is already a key or a field name.
8
+ *
9
+ * Corrected after the element's first render, the same way the client
10
+ * credential dialog's prefix is, because both strings are written by the
11
+ * element's own template and there is nothing further upstream to intercept.
12
+ *
13
+ * Does nothing if upstream changes either string, so the cost of a vendor bump
14
+ * is the correction and never an error — `tests/bundles-backoffice.test.ts`
15
+ * asserts both are still there to correct.
16
+ */
17
+
18
+ /** Vendored text to the text that replaces it. */
19
+ export const BUILDER_LABELS = [
20
+ ['Name of the package', 'Name of the bundle'],
21
+ ['Package Content', 'Bundle content'],
22
+ ]
23
+
24
+ /** The replacement for a vendored string, or undefined when it is not one of ours. */
25
+ export function correctionFor(text) {
26
+ if (typeof text !== 'string') return undefined
27
+ const trimmed = text.trim()
28
+ return BUILDER_LABELS.find(([before]) => before === trimmed)?.[1]
29
+ }
30
+
31
+ function correct(root) {
32
+ const input = root?.querySelector('#package-name-input')
33
+ const label = correctionFor(input?.getAttribute('label'))
34
+ if (label) input.setAttribute('label', label)
35
+
36
+ for (const box of root?.querySelectorAll('uui-box') ?? []) {
37
+ const headline = correctionFor(box.getAttribute('headline'))
38
+ if (headline) box.setAttribute('headline', headline)
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Wraps the element's own `updated`, not `firstUpdated`: the builder renders
44
+ * its header before the definition has loaded and fills it in afterwards, so a
45
+ * one-shot correction would run against a template that is not there yet.
46
+ */
47
+ function patch(element) {
48
+ const proto = element?.prototype
49
+ const original = proto?.updated
50
+ if (typeof original !== 'function') return false
51
+ proto.updated = function updated(changed) {
52
+ original.call(this, changed)
53
+ correct(this.shadowRoot)
54
+ }
55
+ return true
56
+ }
57
+
58
+ export const onInit = () => {
59
+ customElements
60
+ .whenDefined('umb-workspace-package-builder')
61
+ .then(patch)
62
+ .catch(() => {})
63
+ }
@@ -0,0 +1,49 @@
1
+ /**
2
+ * The prefix label in the Create client credential dialog.
3
+ *
4
+ * The dialog prints the required prefix in a decorative `prepend` beside the Id
5
+ * input, and submits the input's value whole — so an administrator reads that
6
+ * label and types the prefix themselves. It is hardcoded in the vendored element
7
+ * as a private field, and it names the prefix the server rejected as of
8
+ * migration 020, which makes a stale label a dialog nobody can complete.
9
+ *
10
+ * Corrected after the element's first render rather than in its template: the
11
+ * prepend is built inside a private method, so there is nothing to intercept
12
+ * further upstream.
13
+ */
14
+
15
+ /** Must match `CLIENT_ID_PREFIX` in packages/server/src/adapters/users.ts. */
16
+ export const REQUIRED_PREFIX = 'crombolo-back-office-'
17
+
18
+ /** True for the vendored label, and false for anything a correction produced. */
19
+ export function needsCorrection(text) {
20
+ return typeof text === 'string' && text.trim() === 'umbraco-back-office-'
21
+ }
22
+
23
+ function correct(root) {
24
+ const prepend = root?.querySelector('.prepend')
25
+ if (prepend && needsCorrection(prepend.textContent)) prepend.textContent = REQUIRED_PREFIX
26
+ }
27
+
28
+ /**
29
+ * Wraps the element's own `firstUpdated`, which the vendored class defines and
30
+ * which runs once the prepend exists. Does nothing if it is not there, so an
31
+ * upstream change costs the correction and never an error.
32
+ */
33
+ function patch(element) {
34
+ const proto = element?.prototype
35
+ const original = proto?.firstUpdated
36
+ if (typeof original !== 'function') return false
37
+ proto.firstUpdated = function firstUpdated(changed) {
38
+ original.call(this, changed)
39
+ correct(this.shadowRoot)
40
+ }
41
+ return true
42
+ }
43
+
44
+ export const onInit = () => {
45
+ customElements
46
+ .whenDefined('umb-create-user-client-credential-modal')
47
+ .then(patch)
48
+ .catch(() => {})
49
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * One tree over `components/`, in place of Umbraco's Templates and Partial Views.
3
+ *
4
+ * Umbraco splits the two because Razor needed the distinction for
5
+ * master-template inheritance; a TSX layout is an import, so here a template is
6
+ * just a component a content type names (`docs/05-rendering.md`). The tree that
7
+ * survives is the partial-view one, because it is path-addressed and understands
8
+ * folders — the template tree is id-addressed and cannot show one.
9
+ *
10
+ * **Relabelled in place rather than replaced through `overwrites`.** The sidebar
11
+ * keeps the open item's ancestors expanded by tagging them with the menu item's
12
+ * alias (`menu-tree-structure-workspace-context-base.js:161`), and the
13
+ * workspace contexts that do the tagging name `Umb.MenuItem.PartialView`
14
+ * literally. Registering a new alias and overwriting that one took the tag away,
15
+ * so the tree collapsed every time a component was opened. Re-registering under
16
+ * the same alias keeps every one of those references working and changes only
17
+ * the label.
18
+ */
19
+ const PARTIAL_VIEWS = 'Umb.MenuItem.PartialView'
20
+ const TEMPLATES = 'Umb.MenuItem.Templates'
21
+
22
+ export const onInit = (_host, extensionRegistry) => {
23
+ // A template is a component now, and it is in the tree below.
24
+ extensionRegistry.unregister(TEMPLATES)
25
+
26
+ extensionRegistry.unregister(PARTIAL_VIEWS)
27
+ extensionRegistry.register({
28
+ type: 'menuItem',
29
+ kind: 'tree',
30
+ // Umbraco's own alias, deliberately: see above.
31
+ alias: PARTIAL_VIEWS,
32
+ name: 'Components Menu Item',
33
+ weight: 40,
34
+ meta: {
35
+ label: 'Components',
36
+ treeAlias: 'Umb.Tree.PartialView',
37
+ menus: ['Umb.Menu.Templating'],
38
+ },
39
+ })
40
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * "Content Types" in the two places Umbraco hard-codes "Document Types".
3
+ *
4
+ * Every other mention of the term is a dictionary string, rewritten per language
5
+ * by `scripts/build-localizations.ts`. The Settings menu item and the search
6
+ * provider carry a literal English label instead, so they are re-registered
7
+ * pointing at the dictionary key the global search already uses, and follow the
8
+ * user's language like the rest.
9
+ *
10
+ * Re-registered under Umbraco's own aliases, for the reason `components-tree.js`
11
+ * gives: workspace contexts name the menu item's alias to keep the tree expanded.
12
+ */
13
+ const LABEL = '#treeHeaders_documentTypes'
14
+
15
+ export const RELABELLED = ['Umb.MenuItem.DocumentTypes', 'Umb.SearchProvider.DocumentType']
16
+
17
+ /** Re-register `alias` with `label`, leaving the rest of its manifest as Umbraco wrote it. */
18
+ export function relabel(extensionRegistry, alias, label) {
19
+ const manifest = extensionRegistry.getByAlias(alias)
20
+ if (!manifest) return
21
+ extensionRegistry.unregister(alias)
22
+ extensionRegistry.register({ ...manifest, meta: { ...manifest.meta, label } })
23
+ }
24
+
25
+ export const onInit = (_host, extensionRegistry) => {
26
+ for (const alias of RELABELLED) relabel(extensionRegistry, alias, LABEL)
27
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="81.25 269.75 237.5 237.5"><title>crombolo</title><circle cx="200" cy="388.5" r="116.75" fill="#cb6f48"/><path transform="matrix(0.025 0 0 -0.025 75 516)" d="M4285 8205 c-459 -94 -647 -159 -1014 -349 -249 -130 -544 -347 -669 -494 -26 -30 -82 -96 -125 -145 -218 -254 -207 -378 24 -266 295 143 924 329 1113 329 19 0 78 9 131 20 128 26 792 30 910 5 39 -8 108 -20 155 -25 378 -43 1093 -273 1364 -438 45 -27 114 -66 153 -86 91 -45 440 -278 525 -349 35 -29 91 -74 124 -100 651 -496 1244 -1578 1303 -2379 13 -165 47 -197 125 -115 86 90 236 513 236 663 0 23 9 86 20 139 25 122 27 345 4 450 -8 39 -19 111 -24 162 -50 500 -394 1231 -774 1648 -50 55 -116 130 -145 167 -30 37 -81 89 -115 115 -33 26 -114 96 -178 155 -117 106 -442 334 -560 394 -29 14 -100 55 -158 89 -97 59 -240 123 -516 231 -122 47 -541 156 -671 174 -45 6 -112 17 -150 25 -152 32 -900 19 -1088 -20z M3300 6458 c-58 -11 -142 -26 -186 -34 -80 -14 -243 -58 -414 -111 -742 -234 -1299 -909 -1300 -1575 0 -312 82 -391 218 -208 298 402 1010 790 1450 790 17 0 74 9 127 20 79 16 136 20 305 20 168 0 228 -4 310 -20 56 -11 124 -20 152 -20 406 0 1286 -389 1680 -742 56 -52 133 -117 171 -147 37 -29 88 -81 114 -115 26 -33 75 -92 108 -131 303 -346 645 -1054 645 -1336 0 -14 9 -68 20 -120 23 -111 27 -371 8 -505 -22 -155 37 -185 183 -93 657 414 699 1621 89 2594 -137 218 -431 585 -518 647 -11 7 -75 66 -144 130 -138 130 -157 146 -338 273 -329 233 -595 368 -985 501 -208 72 -585 164 -670 164 -18 0 -79 9 -135 20 -147 29 -730 28 -890 -2z M2909 4331 c-921 -186 -1424 -895 -1087 -1533 105 -199 210 -222 296 -65 284 517 979 758 1655 572 241 -66 501 -192 647 -314 25 -21 78 -64 119 -97 262 -210 502 -618 586 -998 64 -293 346 -107 440 291 45 189 45 192 -20 531 -50 262 -304 691 -560 947 -304 304 -848 601 -1190 650 -28 4 -90 16 -140 26 -153 32 -566 26 -746 -10z" fill="#fdebd4"/></svg>
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The Help menu, pointed at this project rather than at Umbraco's.
3
+ *
4
+ * The vendored client ships two items — the Umbraco Learning Base and the
5
+ * Umbraco community forum. Relabelling them would be worse than leaving them:
6
+ * "Crombolo Learning Base" linking to Umbraco's YouTube is a lie, and there is no
7
+ * crombolo forum to send anyone to. So they are replaced by alias with links that
8
+ * go where they say.
9
+ *
10
+ * `overwrites` is the registry's own mechanism for this — the same way the
11
+ * Welcome dashboard takes the place of Umbraco's news dashboard — so an upstream
12
+ * bump cannot quietly restore them.
13
+ */
14
+ export const onInit = (_host, extensionRegistry) => {
15
+ extensionRegistry.registerMany([
16
+ {
17
+ type: 'menuItem',
18
+ // The same kind the originals use: the registry renders it as a link.
19
+ kind: 'link',
20
+ alias: 'Crombolo.MenuItem.Help.Documentation',
21
+ name: 'Crombolo Documentation',
22
+ weight: 200,
23
+ overwrites: ['Umb.MenuItem.Help.LearningBase'],
24
+ meta: {
25
+ menus: ['Umb.Menu.Help'],
26
+ label: 'Crombolo documentation',
27
+ icon: 'icon-book-alt',
28
+ href: 'https://github.com/kineco-au/crombolo-cms#readme',
29
+ },
30
+ },
31
+ {
32
+ type: 'menuItem',
33
+ kind: 'link',
34
+ alias: 'Crombolo.MenuItem.Help.Issues',
35
+ name: 'Crombolo Issues',
36
+ weight: 190,
37
+ overwrites: ['Umb.MenuItem.Help.CommunityForum'],
38
+ meta: {
39
+ menus: ['Umb.Menu.Help'],
40
+ label: 'Report an issue',
41
+ icon: 'icon-bug',
42
+ href: 'https://github.com/kineco-au/crombolo-cms/issues',
43
+ },
44
+ },
45
+ ])
46
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The Crombolo mark as a registry icon, served in place of the vendored
3
+ * `icon-umbraco.js` (see `BRANDED_ASSETS` in `graphics.ts`).
4
+ *
5
+ * `icon-registry.js` keeps this name out of the icon picker, so nothing in the
6
+ * interface offers it. This exists because the registry is addressed by name
7
+ * and any element may ask for one, so the glyph behind the name is ours rather
8
+ * than the roundel.
9
+ *
10
+ * `currentColor` and no explicit size, as every icon in the registry: the
11
+ * consumer sets both. The mask id is namespaced because `uui-icon` inlines this
12
+ * markup into the page, where a bare `id="b"` would collide with the marks in
13
+ * the shells.
14
+ */
15
+ export default `<svg xmlns="http://www.w3.org/2000/svg" fill="currentColor" viewBox="81.25 269.75 237.5 237.5"><defs><mask id="crombolo-icon-mark"><rect x="83.25" y="271.75" width="233.5" height="233.5" fill="#fff"/><path transform="matrix(0.025 0 0 -0.025 75 516)" d="M4285 8205 c-459 -94 -647 -159 -1014 -349 -249 -130 -544 -347 -669 -494 -26 -30 -82 -96 -125 -145 -218 -254 -207 -378 24 -266 295 143 924 329 1113 329 19 0 78 9 131 20 128 26 792 30 910 5 39 -8 108 -20 155 -25 378 -43 1093 -273 1364 -438 45 -27 114 -66 153 -86 91 -45 440 -278 525 -349 35 -29 91 -74 124 -100 651 -496 1244 -1578 1303 -2379 13 -165 47 -197 125 -115 86 90 236 513 236 663 0 23 9 86 20 139 25 122 27 345 4 450 -8 39 -19 111 -24 162 -50 500 -394 1231 -774 1648 -50 55 -116 130 -145 167 -30 37 -81 89 -115 115 -33 26 -114 96 -178 155 -117 106 -442 334 -560 394 -29 14 -100 55 -158 89 -97 59 -240 123 -516 231 -122 47 -541 156 -671 174 -45 6 -112 17 -150 25 -152 32 -900 19 -1088 -20z M3300 6458 c-58 -11 -142 -26 -186 -34 -80 -14 -243 -58 -414 -111 -742 -234 -1299 -909 -1300 -1575 0 -312 82 -391 218 -208 298 402 1010 790 1450 790 17 0 74 9 127 20 79 16 136 20 305 20 168 0 228 -4 310 -20 56 -11 124 -20 152 -20 406 0 1286 -389 1680 -742 56 -52 133 -117 171 -147 37 -29 88 -81 114 -115 26 -33 75 -92 108 -131 303 -346 645 -1054 645 -1336 0 -14 9 -68 20 -120 23 -111 27 -371 8 -505 -22 -155 37 -185 183 -93 657 414 699 1621 89 2594 -137 218 -431 585 -518 647 -11 7 -75 66 -144 130 -138 130 -157 146 -338 273 -329 233 -595 368 -985 501 -208 72 -585 164 -670 164 -18 0 -79 9 -135 20 -147 29 -730 28 -890 -2z M2909 4331 c-921 -186 -1424 -895 -1087 -1533 105 -199 210 -222 296 -65 284 517 979 758 1655 572 241 -66 501 -192 647 -314 25 -21 78 -64 119 -97 262 -210 502 -618 586 -998 64 -293 346 -107 440 291 45 189 45 192 -20 531 -50 262 -304 691 -560 947 -304 304 -848 601 -1190 650 -28 4 -90 16 -140 26 -153 32 -566 26 -746 -10z" fill="#000"/></mask></defs><circle cx="200" cy="388.5" r="116.75" mask="url(#crombolo-icon-mark)"/></svg>`
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The icon picker, with Umbraco's own mark taken out of it.
3
+ *
4
+ * `icon-umbraco` is one of the 700-odd icons the registry offers, so the picker
5
+ * on a content type or a media type listed the Umbraco roundel among the
6
+ * icons a site can label its content with, under a tooltip and an accessible
7
+ * name of `icon-umbraco` (`icon-picker-modal.element.js` renders `icon.name`
8
+ * into both).
9
+ *
10
+ * The icon list is contributed by `icons` extensions, and the context appends
11
+ * each one as its module resolves — so a second `icons` extension naming the
12
+ * same icon wins or loses on import timing, not on registration order. This
13
+ * replaces the context instead, which is a decision rather than a race: it
14
+ * narrows `approvedIcons`, the list the picker reads, and leaves `icons` whole
15
+ * so anything rendering by name still gets a glyph (`icon-mark.js` makes that
16
+ * glyph ours).
17
+ *
18
+ * Registered with `overwrites`, which is resolved where a `globalContext` is
19
+ * instantiated — unlike `byType`, which is why `sign-in.js` cannot use it.
20
+ */
21
+
22
+ import { map } from '@umbraco-cms/backoffice/external/rxjs'
23
+ import { UmbIconRegistryContext } from '@umbraco-cms/backoffice/icon'
24
+
25
+ /** Icons the picker does not offer, whatever the registry holds. */
26
+ export const WITHHELD = new Set(['icon-umbraco'])
27
+
28
+ export const isOffered = (icon) => !WITHHELD.has(icon.name)
29
+
30
+ export class CromboloIconRegistryContext extends UmbIconRegistryContext {
31
+ constructor(host) {
32
+ super(host)
33
+ // `approvedIcons` is the parent's own filter on `hidden`; this composes with
34
+ // it rather than replacing it, so an icon hidden upstream stays hidden.
35
+ const approved = this.approvedIcons
36
+ this.approvedIcons = approved.pipe(map((icons) => icons.filter(isOffered)))
37
+ }
38
+ }
39
+
40
+ export { CromboloIconRegistryContext as api }
@@ -0,0 +1,115 @@
1
+ /**
2
+ * The strings that name the product, in the flows crombolo actually has.
3
+ *
4
+ * A `localization` extension rather than a patched `assets/lang/en.js`: the
5
+ * vendored client is served unmodified, and `vendor:backoffice` would overwrite
6
+ * any edit to it on the next upgrade. Overriding through the extension point
7
+ * survives that.
8
+ *
9
+ * Registered with a weight **below** the core dictionary's, because the registry
10
+ * sorts highest-to-lowest and applies in that order — so the lowest weight is the
11
+ * one that wins. See `localization.registry.js`: `$weight: extension.weight ?? 100`.
12
+ *
13
+ * Only what a person reads in a flow this CMS implements. Strings about
14
+ * `appsettings.json`, IIS permissions, Examine or the .NET installer are left
15
+ * alone: they name real things that are not ours to rename, and the features they
16
+ * belong to are not built.
17
+ *
18
+ * `sections` and `packager` are the other kind of rename: not the product's name
19
+ * but a feature's. Umbraco's Packages section builds a `.umb` file; the artifact
20
+ * here is a bundle (`docs/17-bundles.md`), so the section and the flow that
21
+ * builds one say so. `build-localizations.ts` reads this file for its key set and
22
+ * rewrites only values that name the product, so these pass through untouched and
23
+ * every other language keeps its own word.
24
+ */
25
+ export default {
26
+ login: {
27
+ instruction: 'Sign in to Crombolo',
28
+ userInviteWelcomeMessage:
29
+ "Hello there and welcome to Crombolo! In just 1 minute you'll be good to go, we just need you to setup a password.",
30
+ userInviteExpiredMessage:
31
+ 'Welcome to Crombolo! Unfortunately your invite has expired. Please contact your administrator to get a new one.',
32
+ },
33
+ user: {
34
+ changePasswordDescription:
35
+ "You can change your password for accessing the Crombolo backoffice by filling out the form below and click the 'Change Password' button",
36
+ createUserHelp:
37
+ 'Create new users to give them access to Crombolo. When a new user is created a password will be generated that you can share with them.',
38
+ inviteUserHelp:
39
+ 'Invite new users to give them access to Crombolo. An invite email will be sent to the user with information on how to log in.',
40
+ noConsole: 'Disable Crombolo Access',
41
+ userCreatedSuccessHelp:
42
+ 'The new user has successfully been created. To log in to Crombolo use the password below.',
43
+ userInvitedSuccessHelp:
44
+ 'An invitation has been sent to the new user with details about how to log in to Crombolo.',
45
+ userinviteWelcomeMessage:
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
+ userinviteExpiredMessage:
48
+ 'Welcome to Crombolo! Unfortunately your invite has expired. Please contact your administrator to get a new one.',
49
+ },
50
+ general: {
51
+ umbracoInfo: 'Crombolo info',
52
+ },
53
+ buttons: {
54
+ // The accessible name of the header logo button, on every page of the editor.
55
+ // Nothing renders it as visible text, which is why the first sweep through
56
+ // these dictionaries missed it; it surfaced in the accessibility snapshot
57
+ // attached to an unrelated Playwright failure.
58
+ viewSystemDetails: 'View Crombolo system information and version number',
59
+ },
60
+ visuallyHiddenTexts: {
61
+ searchOverlayTitle: 'Search the Crombolo backoffice',
62
+ },
63
+ dashboardTabs: {
64
+ contentIntro: 'Welcome to Crombolo',
65
+ },
66
+ languages: {
67
+ defaultLanguageHelp: 'A Crombolo site can only have one default language set.',
68
+ },
69
+ member: {
70
+ externalMemberDescription:
71
+ 'This member is managed by an external authentication provider. Identity data such as email and username is maintained by the provider, not Crombolo.',
72
+ },
73
+ paste: {
74
+ errorMessage:
75
+ "The text you're trying to paste contains special characters or formatting. This could be caused by copying text from Microsoft Word. Crombolo can remove special characters or formatting automatically, so the pasted content will be more suitable for the web.",
76
+ },
77
+ dashboard: {
78
+ nothinghappens: "If Crombolo isn't opening, you might need to allow popups from this site",
79
+ },
80
+ sections: {
81
+ packages: 'Bundles',
82
+ },
83
+ create: {
84
+ // The create options on the Components tree. The folder option is Umbraco's
85
+ // own and already reads correctly.
86
+ newEmptyPartialView: 'Empty component',
87
+ newPartialViewFromSnippet: 'Component from snippet',
88
+ },
89
+ treeHeaders: {
90
+ // One tree over one directory: templates and partial views are the same
91
+ // kind of file here, and what makes one a template is that a content type
92
+ // names it, not where it sits.
93
+ partialViews: 'Components',
94
+ },
95
+ packager: {
96
+ createPackage: 'Create bundle',
97
+ noPackagesCreated: 'No bundles have been created yet',
98
+ },
99
+ logViewer: {
100
+ // The two items `log-viewer-menu.js` keeps and repoints at this project's
101
+ // repository. The keys stay Umbraco's, as every key here does; only what a
102
+ // person reads changes, so the generated languages rename the product in
103
+ // their own wording and keep pointing at the same two items.
104
+ searchUmbracoSource: 'Search Crombolo source',
105
+ searchWithinUmbracoSourceCodeOnGithub: 'Search within Crombolo source code on GitHub',
106
+ searchUmbracoIssues: 'Search Crombolo issues',
107
+ searchUmbracoIssuesOnGithub: 'Search Crombolo issues on GitHub',
108
+ },
109
+ defaultdialogs: {
110
+ linkYourConfirm:
111
+ 'You are about to link your Crombolo and {0} accounts and you will be redirected to {0} to complete the process.',
112
+ unLinkYourConfirm:
113
+ 'You are about to un-link your Crombolo and {0} accounts and you will be logged out.',
114
+ },
115
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * The search menu on a log message, pointed at this project rather than at
3
+ * Umbraco's.
4
+ *
5
+ * `log-viewer-message.element.js` builds six items in `_getSearchMenuData()`,
6
+ * four of them Umbraco's: two that search the Umbraco forum — one under the
7
+ * Umbraco mark — and two that search Umbraco-CMS on GitHub by the line's
8
+ * `SourceContext`. The forum pair goes, because there is no crombolo forum to
9
+ * send anyone to and the item above it already puts the message into Google.
10
+ * The GitHub pair stays, repointed: searching the source and the issues by the
11
+ * type that logged a line is how you chase one down, it just has to be this
12
+ * CMS's source and issues.
13
+ *
14
+ * The array is rebuilt on every render by a method on the element's prototype,
15
+ * so the method is wrapped rather than the element replaced: the rest of the
16
+ * log viewer is what we want, and wrapping keeps the two items we leave alone —
17
+ * and anything upstream adds — exactly as they were.
18
+ *
19
+ * Items are matched on their **resolved** label rather than the key, because
20
+ * the key is not readable off the built array: `localize.term` returns the
21
+ * string. Resolving the same keys through the same controller matches in
22
+ * whatever language the editor is in.
23
+ *
24
+ * Patched rather than registered, because the menu is not an extension point.
25
+ */
26
+ const TAG = 'umb-log-viewer-message'
27
+ export const REPOSITORY = 'https://github.com/kineco-au/crombolo-cms'
28
+
29
+ /** The forum pair, which has no equivalent here. */
30
+ export const DROPPED = ['logViewer_searchOurUmbraco', 'logViewer_searchOurUmbracoWithGoogle']
31
+
32
+ /** The GitHub pair, whose labels we keep and whose targets we replace. */
33
+ export const REPOINTED = {
34
+ logViewer_searchUmbracoSource: (query) => `${REPOSITORY}/search?q=${query}`,
35
+ logViewer_searchUmbracoIssues: (query) => `${REPOSITORY}/issues?q=${query}`,
36
+ }
37
+
38
+ /**
39
+ * `items` with Umbraco's forum entries gone and its GitHub entries aimed here.
40
+ *
41
+ * `term` resolves a dictionary key the way the element does, so this matches
42
+ * the labels in the editor's own language; `query` is the `SourceContext` the
43
+ * two surviving searches look for.
44
+ */
45
+ export function rebrandSearchMenu(items, term, query) {
46
+ const dropped = new Set(DROPPED.map(term))
47
+ const repointed = new Map(
48
+ Object.entries(REPOINTED).map(([key, href]) => [term(key), () => href(query)]),
49
+ )
50
+ return items
51
+ .filter((item) => !dropped.has(item.label))
52
+ .map((item) => {
53
+ const href = repointed.get(item.label)
54
+ return href ? { ...item, href } : item
55
+ })
56
+ }
57
+
58
+ export const onInit = async () => {
59
+ // Registered with the Log Viewer section, so not defined until it is opened.
60
+ const element = await customElements.whenDefined(TAG)
61
+ const prototype = element.prototype
62
+ const original = prototype._getSearchMenuData
63
+ if (typeof original !== 'function') return
64
+
65
+ prototype._getSearchMenuData = function patched() {
66
+ const query =
67
+ this.properties?.find((property) => property.name === 'SourceContext')?.value ?? ''
68
+ return rebrandSearchMenu(original.call(this), (key) => this.localize.term(key), query)
69
+ }
70
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="81.25 269.75 1022.75 237.5" role="img" aria-label="crombolo"><circle cx="200" cy="388.5" r="116.75" fill="#cb6f48"/><path transform="matrix(0.025 0 0 -0.025 75 516)" d="M4285 8205 c-459 -94 -647 -159 -1014 -349 -249 -130 -544 -347 -669 -494 -26 -30 -82 -96 -125 -145 -218 -254 -207 -378 24 -266 295 143 924 329 1113 329 19 0 78 9 131 20 128 26 792 30 910 5 39 -8 108 -20 155 -25 378 -43 1093 -273 1364 -438 45 -27 114 -66 153 -86 91 -45 440 -278 525 -349 35 -29 91 -74 124 -100 651 -496 1244 -1578 1303 -2379 13 -165 47 -197 125 -115 86 90 236 513 236 663 0 23 9 86 20 139 25 122 27 345 4 450 -8 39 -19 111 -24 162 -50 500 -394 1231 -774 1648 -50 55 -116 130 -145 167 -30 37 -81 89 -115 115 -33 26 -114 96 -178 155 -117 106 -442 334 -560 394 -29 14 -100 55 -158 89 -97 59 -240 123 -516 231 -122 47 -541 156 -671 174 -45 6 -112 17 -150 25 -152 32 -900 19 -1088 -20z M3300 6458 c-58 -11 -142 -26 -186 -34 -80 -14 -243 -58 -414 -111 -742 -234 -1299 -909 -1300 -1575 0 -312 82 -391 218 -208 298 402 1010 790 1450 790 17 0 74 9 127 20 79 16 136 20 305 20 168 0 228 -4 310 -20 56 -11 124 -20 152 -20 406 0 1286 -389 1680 -742 56 -52 133 -117 171 -147 37 -29 88 -81 114 -115 26 -33 75 -92 108 -131 303 -346 645 -1054 645 -1336 0 -14 9 -68 20 -120 23 -111 27 -371 8 -505 -22 -155 37 -185 183 -93 657 414 699 1621 89 2594 -137 218 -431 585 -518 647 -11 7 -75 66 -144 130 -138 130 -157 146 -338 273 -329 233 -595 368 -985 501 -208 72 -585 164 -670 164 -18 0 -79 9 -135 20 -147 29 -730 28 -890 -2z M2909 4331 c-921 -186 -1424 -895 -1087 -1533 105 -199 210 -222 296 -65 284 517 979 758 1655 572 241 -66 501 -192 647 -314 25 -21 78 -64 119 -97 262 -210 502 -618 586 -998 64 -293 346 -107 440 291 45 189 45 192 -20 531 -50 262 -304 691 -560 947 -304 304 -848 601 -1190 650 -28 4 -90 16 -140 26 -153 32 -566 26 -746 -10z" fill="#fdebd4"/><path transform="matrix(0.025 0 0 -0.025 340 470)" d="M17231 5945 c-168 -47 -253 -129 -320 -310 -32 -87 -48 -4483 -16 -4609 116 -459 691 -531 916 -115 64 119 84 124 184 38 765 -661 1980 -322 2461 686 211 443 246 966 103 1527 -156 616 -774 1208 -1325 1273 -33 4 -103 16 -157 26 -288 56 -760 -83 -1009 -295 -193 -166 -188 -183 -188 699 0 837 0 835 -144 974 -117 112 -326 156 -505 106z m1804 -2497 c449 -160 700 -668 575 -1164 -177 -708 -937 -952 -1444 -465 -535 516 -283 1523 414 1655 84 16 386 -2 455 -26z M25519 5946 c-139 -37 -259 -165 -328 -351 -41 -109 -46 -4365 -6 -4534 132 -554 862 -581 1000 -36 23 90 23 4460 0 4550 -76 297 -358 454 -666 371z M2125 4445 c-485 -100 -723 -223 -1037 -540 -245 -245 -338 -398 -452 -740 -219 -652 -94 -1396 311 -1852 46 -52 91 -105 99 -119 9 -13 66 -65 128 -116 295 -246 613 -393 943 -438 45 -6 112 -17 150 -25 103 -22 324 -19 463 5 63 12 153 28 200 36 766 137 1266 828 868 1200 -185 173 -405 157 -681 -48 -184 -137 -210 -151 -367 -204 -503 -170 -1103 189 -1182 706 -5 36 -16 102 -24 148 -41 249 136 680 339 823 406 286 884 272 1252 -37 290 -244 676 -128 770 231 147 567 -919 1148 -1780 970z M8460 4464 c-36 -7 -103 -19 -150 -25 -91 -12 -407 -112 -494 -156 -129 -66 -499 -343 -532 -399 -6 -11 -42 -55 -79 -99 -429 -503 -572 -1318 -336 -1911 86 -219 195 -419 289 -532 31 -38 78 -96 104 -130 27 -34 76 -83 110 -110 33 -26 95 -75 137 -110 521 -432 1453 -504 2071 -160 1479 825 1219 3159 -397 3572 -283 73 -560 96 -723 60z m423 -999 c75 -19 211 -88 326 -165 370 -248 476 -916 208 -1315 -457 -679 -1424 -500 -1628 302 -176 689 438 1349 1094 1178z M14940 4464 c-36 -7 -103 -19 -150 -25 -248 -33 -455 -137 -665 -334 -169 -159 -170 -159 -345 12 -176 172 -279 233 -485 290 -166 46 -660 43 -790 -6 -164 -60 -274 -124 -384 -221 -145 -128 -166 -128 -235 -1 -213 388 -797 302 -911 -134 -23 -89 -23 -2930 0 -3020 95 -368 575 -518 841 -264 148 142 144 114 144 1134 0 920 5 998 81 1200 165 442 803 510 1016 109 102 -194 97 -136 103 -1204 5 -965 5 -965 27 -1024 183 -481 916 -433 973 64 5 47 15 101 22 120 9 26 14 263 18 940 5 905 5 905 31 975 152 410 629 539 935 252 186 -173 188 -189 194 -1342 5 -980 5 -980 28 -1037 169 -418 775 -418 944 0 39 97 41 2271 2 2435 -92 387 -272 671 -547 865 -212 150 -645 260 -847 216z M22565 4445 c-282 -58 -363 -84 -555 -180 -506 -253 -953 -845 -1009 -1339 -6 -44 -17 -110 -25 -146 -60 -251 31 -828 177 -1115 199 -393 460 -651 865 -854 141 -71 383 -156 468 -166 33 -3 103 -15 156 -26 302 -60 757 21 1108 196 1042 522 1348 1936 628 2907 -416 563 -1157 859 -1813 723z m590 -997 c396 -142 605 -458 605 -913 0 -803 -753 -1231 -1403 -798 -576 383 -505 1340 123 1662 174 90 496 113 675 49z M28340 4464 c-35 -7 -101 -19 -145 -24 -484 -66 -963 -375 -1258 -815 -156 -233 -278 -547 -298 -765 -5 -58 -16 -132 -24 -165 -21 -87 -19 -270 5 -375 11 -47 20 -101 20 -120 2 -368 398 -997 793 -1261 386 -258 945 -393 1324 -320 54 10 124 22 157 26 709 84 1433 881 1486 1640 5 66 14 136 19 155 6 19 11 64 11 100 0 36 -5 81 -11 100 -5 19 -14 89 -19 155 -33 469 -342 1002 -764 1317 -216 162 -535 294 -793 328 -45 6 -112 17 -150 25 -85 18 -267 18 -353 -1z m455 -1016 c875 -312 832 -1609 -60 -1855 -748 -206 -1382 660 -1017 1392 121 243 374 442 622 489 84 16 386 -2 455 -26z M6060 4425 c-214 -48 -298 -92 -455 -240 -164 -154 -183 -157 -249 -36 -238 434 -814 353 -931 -130 -41 -171 -35 -2904 7 -3017 184 -495 793 -495 976 0 27 73 27 73 32 918 5 845 5 845 49 970 126 362 303 489 706 509 311 16 439 111 521 388 118 400 -226 735 -656 638z" fill="#54321f"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="81.25 269.75 1022.75 237.5" role="img" aria-label="crombolo"><circle cx="200" cy="388.5" r="116.75" fill="#cb6f48"/><path transform="matrix(0.025 0 0 -0.025 75 516)" d="M4285 8205 c-459 -94 -647 -159 -1014 -349 -249 -130 -544 -347 -669 -494 -26 -30 -82 -96 -125 -145 -218 -254 -207 -378 24 -266 295 143 924 329 1113 329 19 0 78 9 131 20 128 26 792 30 910 5 39 -8 108 -20 155 -25 378 -43 1093 -273 1364 -438 45 -27 114 -66 153 -86 91 -45 440 -278 525 -349 35 -29 91 -74 124 -100 651 -496 1244 -1578 1303 -2379 13 -165 47 -197 125 -115 86 90 236 513 236 663 0 23 9 86 20 139 25 122 27 345 4 450 -8 39 -19 111 -24 162 -50 500 -394 1231 -774 1648 -50 55 -116 130 -145 167 -30 37 -81 89 -115 115 -33 26 -114 96 -178 155 -117 106 -442 334 -560 394 -29 14 -100 55 -158 89 -97 59 -240 123 -516 231 -122 47 -541 156 -671 174 -45 6 -112 17 -150 25 -152 32 -900 19 -1088 -20z M3300 6458 c-58 -11 -142 -26 -186 -34 -80 -14 -243 -58 -414 -111 -742 -234 -1299 -909 -1300 -1575 0 -312 82 -391 218 -208 298 402 1010 790 1450 790 17 0 74 9 127 20 79 16 136 20 305 20 168 0 228 -4 310 -20 56 -11 124 -20 152 -20 406 0 1286 -389 1680 -742 56 -52 133 -117 171 -147 37 -29 88 -81 114 -115 26 -33 75 -92 108 -131 303 -346 645 -1054 645 -1336 0 -14 9 -68 20 -120 23 -111 27 -371 8 -505 -22 -155 37 -185 183 -93 657 414 699 1621 89 2594 -137 218 -431 585 -518 647 -11 7 -75 66 -144 130 -138 130 -157 146 -338 273 -329 233 -595 368 -985 501 -208 72 -585 164 -670 164 -18 0 -79 9 -135 20 -147 29 -730 28 -890 -2z M2909 4331 c-921 -186 -1424 -895 -1087 -1533 105 -199 210 -222 296 -65 284 517 979 758 1655 572 241 -66 501 -192 647 -314 25 -21 78 -64 119 -97 262 -210 502 -618 586 -998 64 -293 346 -107 440 291 45 189 45 192 -20 531 -50 262 -304 691 -560 947 -304 304 -848 601 -1190 650 -28 4 -90 16 -140 26 -153 32 -566 26 -746 -10z" fill="#fdebd4"/><path transform="matrix(0.025 0 0 -0.025 340 470)" d="M17231 5945 c-168 -47 -253 -129 -320 -310 -32 -87 -48 -4483 -16 -4609 116 -459 691 -531 916 -115 64 119 84 124 184 38 765 -661 1980 -322 2461 686 211 443 246 966 103 1527 -156 616 -774 1208 -1325 1273 -33 4 -103 16 -157 26 -288 56 -760 -83 -1009 -295 -193 -166 -188 -183 -188 699 0 837 0 835 -144 974 -117 112 -326 156 -505 106z m1804 -2497 c449 -160 700 -668 575 -1164 -177 -708 -937 -952 -1444 -465 -535 516 -283 1523 414 1655 84 16 386 -2 455 -26z M25519 5946 c-139 -37 -259 -165 -328 -351 -41 -109 -46 -4365 -6 -4534 132 -554 862 -581 1000 -36 23 90 23 4460 0 4550 -76 297 -358 454 -666 371z M2125 4445 c-485 -100 -723 -223 -1037 -540 -245 -245 -338 -398 -452 -740 -219 -652 -94 -1396 311 -1852 46 -52 91 -105 99 -119 9 -13 66 -65 128 -116 295 -246 613 -393 943 -438 45 -6 112 -17 150 -25 103 -22 324 -19 463 5 63 12 153 28 200 36 766 137 1266 828 868 1200 -185 173 -405 157 -681 -48 -184 -137 -210 -151 -367 -204 -503 -170 -1103 189 -1182 706 -5 36 -16 102 -24 148 -41 249 136 680 339 823 406 286 884 272 1252 -37 290 -244 676 -128 770 231 147 567 -919 1148 -1780 970z M8460 4464 c-36 -7 -103 -19 -150 -25 -91 -12 -407 -112 -494 -156 -129 -66 -499 -343 -532 -399 -6 -11 -42 -55 -79 -99 -429 -503 -572 -1318 -336 -1911 86 -219 195 -419 289 -532 31 -38 78 -96 104 -130 27 -34 76 -83 110 -110 33 -26 95 -75 137 -110 521 -432 1453 -504 2071 -160 1479 825 1219 3159 -397 3572 -283 73 -560 96 -723 60z m423 -999 c75 -19 211 -88 326 -165 370 -248 476 -916 208 -1315 -457 -679 -1424 -500 -1628 302 -176 689 438 1349 1094 1178z M14940 4464 c-36 -7 -103 -19 -150 -25 -248 -33 -455 -137 -665 -334 -169 -159 -170 -159 -345 12 -176 172 -279 233 -485 290 -166 46 -660 43 -790 -6 -164 -60 -274 -124 -384 -221 -145 -128 -166 -128 -235 -1 -213 388 -797 302 -911 -134 -23 -89 -23 -2930 0 -3020 95 -368 575 -518 841 -264 148 142 144 114 144 1134 0 920 5 998 81 1200 165 442 803 510 1016 109 102 -194 97 -136 103 -1204 5 -965 5 -965 27 -1024 183 -481 916 -433 973 64 5 47 15 101 22 120 9 26 14 263 18 940 5 905 5 905 31 975 152 410 629 539 935 252 186 -173 188 -189 194 -1342 5 -980 5 -980 28 -1037 169 -418 775 -418 944 0 39 97 41 2271 2 2435 -92 387 -272 671 -547 865 -212 150 -645 260 -847 216z M22565 4445 c-282 -58 -363 -84 -555 -180 -506 -253 -953 -845 -1009 -1339 -6 -44 -17 -110 -25 -146 -60 -251 31 -828 177 -1115 199 -393 460 -651 865 -854 141 -71 383 -156 468 -166 33 -3 103 -15 156 -26 302 -60 757 21 1108 196 1042 522 1348 1936 628 2907 -416 563 -1157 859 -1813 723z m590 -997 c396 -142 605 -458 605 -913 0 -803 -753 -1231 -1403 -798 -576 383 -505 1340 123 1662 174 90 496 113 675 49z M28340 4464 c-35 -7 -101 -19 -145 -24 -484 -66 -963 -375 -1258 -815 -156 -233 -278 -547 -298 -765 -5 -58 -16 -132 -24 -165 -21 -87 -19 -270 5 -375 11 -47 20 -101 20 -120 2 -368 398 -997 793 -1261 386 -258 945 -393 1324 -320 54 10 124 22 157 26 709 84 1433 881 1486 1640 5 66 14 136 19 155 6 19 11 64 11 100 0 36 -5 81 -11 100 -5 19 -14 89 -19 155 -33 469 -342 1002 -764 1317 -216 162 -535 294 -793 328 -45 6 -112 17 -150 25 -85 18 -267 18 -353 -1z m455 -1016 c875 -312 832 -1609 -60 -1855 -748 -206 -1382 660 -1017 1392 121 243 374 442 622 489 84 16 386 -2 455 -26z M6060 4425 c-214 -48 -298 -92 -455 -240 -164 -154 -183 -157 -249 -36 -238 434 -814 353 -931 -130 -41 -171 -35 -2904 7 -3017 184 -495 793 -495 976 0 27 73 27 73 32 918 5 845 5 845 49 970 126 362 303 489 706 509 311 16 439 111 521 388 118 400 -226 735 -656 638z" fill="#54321f"/></svg>