Merge branch 'main' into feature/permissions

This commit is contained in:
Mads Rasmussen
2023-09-13 12:48:21 +02:00
87 changed files with 2109 additions and 922 deletions

View File

@@ -1,4 +1,4 @@
# Umbraco.CMS.Bacoffice (Bellissima)
# Umbraco.CMS.Backoffice (Bellissima)
This is the working repository of the upcoming new Backoffice to Umbraco CMS.
@@ -48,3 +48,7 @@ Storybook is also being built and deployed automatically on the Main branch, inc
## Contributing
We accept contributions to this project. However be aware that we are mainly working on a private backlog, so not everyone will be immediately obvious. If you want to get started on contributing, please read the [contribute space](https://github.com/umbraco/Umbraco.CMS.Backoffice/contribute) where you will be able to find the guidelines on how to contribute as well as a list of good first issues.
## Documentation
The documentation can be found on [Umbraco Docs](https://docs.umbraco.com/umbraco-backoffice/). The documentation is a work in progress.

View File

@@ -24,7 +24,7 @@ jobs:
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: true
- name: Build And Deploy

View File

@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-latest
name: Build and Deploy Job
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
submodules: true
- name: Build And Deploy

View File

@@ -28,7 +28,7 @@ jobs:
node-version: [18.x]
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:

View File

@@ -38,7 +38,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -15,6 +15,6 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: 'Checkout Repository'
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: 'Dependency Review'
uses: actions/dependency-review-action@v3

View File

@@ -21,7 +21,7 @@ jobs:
security-events: write
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Run DevSkim scanner
uses: microsoft/DevSkim-Action@v1

View File

@@ -28,7 +28,7 @@ jobs:
group: npm-publish
cancel-in-progress: true
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 18

View File

@@ -23,10 +23,10 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@babel/core": "^7.22.10",
"@babel/core": "^7.22.17",
"@mdx-js/react": "^2.3.0",
"@open-wc/testing": "^3.2.0",
"@playwright/test": "^1.36.2",
"@playwright/test": "^1.37.1",
"@rollup/plugin-commonjs": "^25.0.3",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.1.0",
@@ -42,7 +42,7 @@
"@types/mocha": "^10.0.1",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"@typescript-eslint/parser": "^6.5.0",
"@web/dev-server-esbuild": "^0.4.1",
"@web/dev-server-import-maps": "^0.1.1",
"@web/test-runner": "^0.17.0",
@@ -50,7 +50,7 @@
"babel-loader": "^9.1.3",
"eslint": "^8.46.0",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-lit": "^1.8.3",
"eslint-plugin-lit-a11y": "^4.1.0",
@@ -172,12 +172,12 @@
}
},
"node_modules/@babel/code-frame": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
"integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
"dev": true,
"dependencies": {
"@babel/highlight": "^7.22.10",
"@babel/highlight": "^7.22.13",
"chalk": "^2.4.2"
},
"engines": {
@@ -194,25 +194,25 @@
}
},
"node_modules/@babel/core": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.10.tgz",
"integrity": "sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==",
"version": "7.22.17",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.17.tgz",
"integrity": "sha512-2EENLmhpwplDux5PSsZnSbnSkB3tZ6QTksgO25xwEL7pIDcNOMhF5v/s6RzwjMZzZzw9Ofc30gHv5ChCC8pifQ==",
"dev": true,
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.22.10",
"@babel/generator": "^7.22.10",
"@babel/helper-compilation-targets": "^7.22.10",
"@babel/helper-module-transforms": "^7.22.9",
"@babel/helpers": "^7.22.10",
"@babel/parser": "^7.22.10",
"@babel/template": "^7.22.5",
"@babel/traverse": "^7.22.10",
"@babel/types": "^7.22.10",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.22.15",
"@babel/helper-compilation-targets": "^7.22.15",
"@babel/helper-module-transforms": "^7.22.17",
"@babel/helpers": "^7.22.15",
"@babel/parser": "^7.22.16",
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.22.17",
"@babel/types": "^7.22.17",
"convert-source-map": "^1.7.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.2",
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
@@ -224,12 +224,12 @@
}
},
"node_modules/@babel/generator": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz",
"integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.15.tgz",
"integrity": "sha512-Zu9oWARBqeVOW0dZOjXc3JObrzuqothQ3y/n1kUtrjCoCPLkXUwMvOo/F/TCfoHMbWIFlWwpZtkZVb9ga4U2pA==",
"dev": true,
"dependencies": {
"@babel/types": "^7.22.10",
"@babel/types": "^7.22.15",
"@jridgewell/gen-mapping": "^0.3.2",
"@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
@@ -263,13 +263,13 @@
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz",
"integrity": "sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.15.tgz",
"integrity": "sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.22.9",
"@babel/helper-validator-option": "^7.22.5",
"@babel/helper-validator-option": "^7.22.15",
"browserslist": "^4.21.9",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
@@ -381,28 +381,28 @@
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz",
"integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz",
"integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==",
"dev": true,
"dependencies": {
"@babel/types": "^7.22.5"
"@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.22.9",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz",
"integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==",
"version": "7.22.17",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.17.tgz",
"integrity": "sha512-XouDDhQESrLHTpnBtCKExJdyY4gJCdrvH2Pyv8r8kovX2U8G0dRUOT45T9XlbLtuu9CLXP15eusnkprhoPV5iQ==",
"dev": true,
"dependencies": {
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-module-imports": "^7.22.5",
"@babel/helper-module-imports": "^7.22.15",
"@babel/helper-simple-access": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/helper-validator-identifier": "^7.22.5"
"@babel/helper-validator-identifier": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
@@ -512,18 +512,18 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.15.tgz",
"integrity": "sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==",
"dev": true,
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz",
"integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz",
"integrity": "sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -544,23 +544,23 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.10.tgz",
"integrity": "sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.15.tgz",
"integrity": "sha512-7pAjK0aSdxOwR+CcYAqgWOGy5dcfvzsTIfFTb2odQqW47MDfv14UaJDY6eng8ylM2EaeKXdxaSWESbkmaQHTmw==",
"dev": true,
"dependencies": {
"@babel/template": "^7.22.5",
"@babel/traverse": "^7.22.10",
"@babel/types": "^7.22.10"
"@babel/template": "^7.22.15",
"@babel/traverse": "^7.22.15",
"@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
"integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
"version": "7.22.13",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
"integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.5",
@@ -572,9 +572,9 @@
}
},
"node_modules/@babel/parser": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz",
"integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==",
"version": "7.22.16",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.16.tgz",
"integrity": "sha512-+gPfKv8UWeKKeJTUxe59+OobVcrYHETCsORl61EmSkmgymguYk/X5bp7GuUIXaFsc6y++v8ZxPsLSSuujqDphA==",
"dev": true,
"bin": {
"parser": "bin/babel-parser.js"
@@ -2053,33 +2053,33 @@
}
},
"node_modules/@babel/template": {
"version": "7.22.5",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
"integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
"version": "7.22.15",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.22.5",
"@babel/parser": "^7.22.5",
"@babel/types": "^7.22.5"
"@babel/code-frame": "^7.22.13",
"@babel/parser": "^7.22.15",
"@babel/types": "^7.22.15"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz",
"integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==",
"version": "7.22.17",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.17.tgz",
"integrity": "sha512-xK4Uwm0JnAMvxYZxOVecss85WxTEIbTa7bnGyf/+EgCL5Zt3U7htUpEOWv9detPlamGKuRzCqw74xVglDWpPdg==",
"dev": true,
"dependencies": {
"@babel/code-frame": "^7.22.10",
"@babel/generator": "^7.22.10",
"@babel/code-frame": "^7.22.13",
"@babel/generator": "^7.22.15",
"@babel/helper-environment-visitor": "^7.22.5",
"@babel/helper-function-name": "^7.22.5",
"@babel/helper-hoist-variables": "^7.22.5",
"@babel/helper-split-export-declaration": "^7.22.6",
"@babel/parser": "^7.22.10",
"@babel/types": "^7.22.10",
"@babel/parser": "^7.22.16",
"@babel/types": "^7.22.17",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -2088,13 +2088,13 @@
}
},
"node_modules/@babel/types": {
"version": "7.22.10",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz",
"integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==",
"version": "7.22.17",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.17.tgz",
"integrity": "sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==",
"dev": true,
"dependencies": {
"@babel/helper-string-parser": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.5",
"@babel/helper-validator-identifier": "^7.22.15",
"to-fast-properties": "^2.0.0"
},
"engines": {
@@ -3358,64 +3358,14 @@
"node": ">=14"
}
},
"node_modules/@pkgr/utils": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/@pkgr/utils/-/utils-2.4.1.tgz",
"integrity": "sha512-JOqwkgFEyi+OROIyq7l4Jy28h/WwhDnG/cPkXG2Z1iFbubB6jsHW1NDvmyOzTBxHr3yg68YGirmh1JUgMqa+9w==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"fast-glob": "^3.2.12",
"is-glob": "^4.0.3",
"open": "^9.1.0",
"picocolors": "^1.0.0",
"tslib": "^2.5.0"
},
"engines": {
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/@pkgr/utils/node_modules/define-lazy-prop": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz",
"integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@pkgr/utils/node_modules/open": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/open/-/open-9.1.0.tgz",
"integrity": "sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==",
"dev": true,
"dependencies": {
"default-browser": "^4.0.0",
"define-lazy-prop": "^3.0.0",
"is-inside-container": "^1.0.0",
"is-wsl": "^2.2.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@playwright/test": {
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.36.2.tgz",
"integrity": "sha512-2rVZeyPRjxfPH6J0oGJqE8YxiM1IBRyM8hyrXYK7eSiAqmbNhxwcLa7dZ7fy9Kj26V7FYia5fh9XJRq4Dqme+g==",
"version": "1.37.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.1.tgz",
"integrity": "sha512-bq9zTli3vWJo8S3LwB91U0qDNQDpEXnw7knhxLM0nwDvexQAwx9tO8iKDZSqqneVq+URd/WIoz+BALMqUTgdSg==",
"dev": true,
"dependencies": {
"@types/node": "*",
"playwright-core": "1.36.2"
"playwright-core": "1.37.1"
},
"bin": {
"playwright": "cli.js"
@@ -3428,9 +3378,9 @@
}
},
"node_modules/@playwright/test/node_modules/playwright-core": {
"version": "1.36.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.36.2.tgz",
"integrity": "sha512-sQYZt31dwkqxOrP7xy2ggDfEzUxM1lodjhsQ3NMMv5uGTRDsLxU0e4xf4wwMkF2gplIxf17QMBCodSFgm6bFVQ==",
"version": "1.37.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz",
"integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
@@ -7347,15 +7297,15 @@
"dev": true
},
"node_modules/@typescript-eslint/parser": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.3.0.tgz",
"integrity": "sha512-ibP+y2Gr6p0qsUkhs7InMdXrwldjxZw66wpcQq9/PzAroM45wdwyu81T+7RibNCh8oc0AgrsyCwJByncY0Ongg==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.5.0.tgz",
"integrity": "sha512-LMAVtR5GN8nY0G0BadkG0XIe4AcNMeyEy3DyhKGAh9k4pLSMBO7rF29JvDBpZGCmp5Pgz5RLHP6eCpSYZJQDuQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "6.3.0",
"@typescript-eslint/types": "6.3.0",
"@typescript-eslint/typescript-estree": "6.3.0",
"@typescript-eslint/visitor-keys": "6.3.0",
"@typescript-eslint/scope-manager": "6.5.0",
"@typescript-eslint/types": "6.5.0",
"@typescript-eslint/typescript-estree": "6.5.0",
"@typescript-eslint/visitor-keys": "6.5.0",
"debug": "^4.3.4"
},
"engines": {
@@ -7375,13 +7325,13 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.3.0.tgz",
"integrity": "sha512-WlNFgBEuGu74ahrXzgefiz/QlVb+qg8KDTpknKwR7hMH+lQygWyx0CQFoUmMn1zDkQjTBBIn75IxtWss77iBIQ==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.5.0.tgz",
"integrity": "sha512-A8hZ7OlxURricpycp5kdPTH3XnjG85UpJS6Fn4VzeoH4T388gQJ/PGP4ole5NfKt4WDVhmLaQ/dBLNDC4Xl/Kw==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.3.0",
"@typescript-eslint/visitor-keys": "6.3.0"
"@typescript-eslint/types": "6.5.0",
"@typescript-eslint/visitor-keys": "6.5.0"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -7392,9 +7342,9 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.3.0.tgz",
"integrity": "sha512-K6TZOvfVyc7MO9j60MkRNWyFSf86IbOatTKGrpTQnzarDZPYPVy0oe3myTMq7VjhfsUAbNUW8I5s+2lZvtx1gg==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.5.0.tgz",
"integrity": "sha512-eqLLOEF5/lU8jW3Bw+8auf4lZSbbljHR2saKnYqON12G/WsJrGeeDHWuQePoEf9ro22+JkbPfWQwKEC5WwLQ3w==",
"dev": true,
"engines": {
"node": "^16.0.0 || >=18.0.0"
@@ -7405,13 +7355,13 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.3.0.tgz",
"integrity": "sha512-Xh4NVDaC4eYKY4O3QGPuQNp5NxBAlEvNQYOqJquR2MePNxO11E5K3t5x4M4Mx53IZvtpW+mBxIT0s274fLUocg==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.5.0.tgz",
"integrity": "sha512-q0rGwSe9e5Kk/XzliB9h2LBc9tmXX25G0833r7kffbl5437FPWb2tbpIV9wAATebC/018pGa9fwPDuvGN+LxWQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.3.0",
"@typescript-eslint/visitor-keys": "6.3.0",
"@typescript-eslint/types": "6.5.0",
"@typescript-eslint/visitor-keys": "6.5.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
@@ -7432,12 +7382,12 @@
}
},
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.3.0.tgz",
"integrity": "sha512-kEhRRj7HnvaSjux1J9+7dBen15CdWmDnwrpyiHsFX6Qx2iW5LOBUgNefOFeh2PjWPlNwN8TOn6+4eBU3J/gupw==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.5.0.tgz",
"integrity": "sha512-yCB/2wkbv3hPsh02ZS8dFQnij9VVQXJMN/gbQsaaY+zxALkZnxa/wagvLEFsAWMPv7d7lxQmNsIzGU1w/T/WyA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "6.3.0",
"@typescript-eslint/types": "6.5.0",
"eslint-visitor-keys": "^3.4.1"
},
"engines": {
@@ -9994,21 +9944,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bundle-name": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-3.0.0.tgz",
"integrity": "sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==",
"dev": true,
"dependencies": {
"run-applescript": "^5.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/bytes": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@@ -10903,24 +10838,6 @@
"node": ">=0.10.0"
}
},
"node_modules/default-browser": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/default-browser/-/default-browser-4.0.0.tgz",
"integrity": "sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==",
"dev": true,
"dependencies": {
"bundle-name": "^3.0.0",
"default-browser-id": "^3.0.0",
"execa": "^7.1.1",
"titleize": "^3.0.0"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/default-browser-id": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-3.0.0.tgz",
@@ -10937,116 +10854,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/default-browser/node_modules/execa": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/execa/-/execa-7.1.1.tgz",
"integrity": "sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.3",
"get-stream": "^6.0.1",
"human-signals": "^4.3.0",
"is-stream": "^3.0.0",
"merge-stream": "^2.0.0",
"npm-run-path": "^5.1.0",
"onetime": "^6.0.0",
"signal-exit": "^3.0.7",
"strip-final-newline": "^3.0.0"
},
"engines": {
"node": "^14.18.0 || ^16.14.0 || >=18.0.0"
},
"funding": {
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/default-browser/node_modules/human-signals": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-4.3.1.tgz",
"integrity": "sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==",
"dev": true,
"engines": {
"node": ">=14.18.0"
}
},
"node_modules/default-browser/node_modules/is-stream": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
"dev": true,
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/default-browser/node_modules/mimic-fn": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/default-browser/node_modules/npm-run-path": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.1.0.tgz",
"integrity": "sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==",
"dev": true,
"dependencies": {
"path-key": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/default-browser/node_modules/onetime": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
"dev": true,
"dependencies": {
"mimic-fn": "^4.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/default-browser/node_modules/path-key": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/default-browser/node_modules/strip-final-newline": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
@@ -11751,19 +11558,18 @@
}
},
"node_modules/eslint-import-resolver-typescript": {
"version": "3.5.5",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.5.5.tgz",
"integrity": "sha512-TdJqPHs2lW5J9Zpe17DZNQuDnox4xo2o+0tE7Pggain9Rbc19ik8kFtXdxZ250FVx2kF4vlt2RSf4qlUpG7bhw==",
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.0.tgz",
"integrity": "sha512-QTHR9ddNnn35RTxlaEnx2gCxqFlF2SEN0SE2d17SqwyM7YOSI2GHWRYp5BiRkObTUNYPupC/3Fq2a0PpT+EKpg==",
"dev": true,
"dependencies": {
"debug": "^4.3.4",
"enhanced-resolve": "^5.12.0",
"eslint-module-utils": "^2.7.4",
"fast-glob": "^3.3.1",
"get-tsconfig": "^4.5.0",
"globby": "^13.1.3",
"is-core-module": "^2.11.0",
"is-glob": "^4.0.3",
"synckit": "^0.8.5"
"is-glob": "^4.0.3"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
@@ -11776,37 +11582,6 @@
"eslint-plugin-import": "*"
}
},
"node_modules/eslint-import-resolver-typescript/node_modules/globby": {
"version": "13.2.2",
"resolved": "https://registry.npmjs.org/globby/-/globby-13.2.2.tgz",
"integrity": "sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==",
"dev": true,
"dependencies": {
"dir-glob": "^3.0.1",
"fast-glob": "^3.3.0",
"ignore": "^5.2.4",
"merge2": "^1.4.1",
"slash": "^4.0.0"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-import-resolver-typescript/node_modules/slash": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz",
"integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/eslint-module-utils": {
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz",
@@ -12461,9 +12236,9 @@
"dev": true
},
"node_modules/fast-glob": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz",
"integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==",
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
"integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@@ -14133,39 +13908,6 @@
"node": ">=0.10.0"
}
},
"node_modules/is-inside-container": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz",
"integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==",
"dev": true,
"dependencies": {
"is-docker": "^3.0.0"
},
"bin": {
"is-inside-container": "cli.js"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-inside-container/node_modules/is-docker": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz",
"integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==",
"dev": true,
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-interactive": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
@@ -19067,21 +18809,6 @@
"integrity": "sha512-bH3g1/xOwbkuwE4iQ0tLwp1/r+dqttQr/ezO0tzp+KCttsCcxxgSCbEYy8+ePuSCLsiS3WhuTfFSED74d+tMjg==",
"license": "MIT"
},
"node_modules/run-applescript": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-5.0.0.tgz",
"integrity": "sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==",
"dev": true,
"dependencies": {
"execa": "^5.0.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/run-async": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
@@ -19928,22 +19655,6 @@
"integrity": "sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==",
"dev": true
},
"node_modules/synckit": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.5.tgz",
"integrity": "sha512-L1dapNV6vu2s/4Sputv8xGsCdAVlb5nRDMFU/E27D44l5U6cw1g0dGd45uLc+OXjNMmF4ntiMdCimzcjFKQI8Q==",
"dev": true,
"dependencies": {
"@pkgr/utils": "^2.3.1",
"tslib": "^2.5.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/unts"
}
},
"node_modules/table-layout": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/table-layout/-/table-layout-3.0.2.tgz",
@@ -20242,18 +19953,6 @@
"tslib": "^2.0.3"
}
},
"node_modules/titleize": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/titleize/-/titleize-3.0.0.tgz",
"integrity": "sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/tmp": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",

View File

@@ -5,6 +5,7 @@
"type": "module",
"exports": {
".": null,
"./app": "./dist-cms/apps/app/index.js",
"./class-api": "./dist-cms/libs/class-api/index.js",
"./context-api": "./dist-cms/libs/context-api/index.js",
"./controller-api": "./dist-cms/libs/controller-api/index.js",
@@ -13,7 +14,6 @@
"./localization-api": "./dist-cms/libs/localization-api/index.js",
"./observable-api": "./dist-cms/libs/observable-api/index.js",
"./auth": "./dist-cms/shared/auth/index.js",
"./context": "./dist-cms/shared/context/index.js",
"./events": "./dist-cms/shared/umb-events/index.js",
"./icon": "./dist-cms/shared/icon/index.js",
"./models": "./dist-cms/shared/models/index.js",
@@ -26,6 +26,7 @@
"./collection": "./dist-cms/packages/core/collection/index.js",
"./components": "./dist-cms/packages/core/components/index.js",
"./content-type": "./dist-cms/packages/core/content-type/index.js",
"./culture": "./dist-cms/packages/core/culture/index.js",
"./debug": "./dist-cms/packages/core/debug/index.js",
"./entity-action": "./dist-cms/packages/core/entity-action/index.js",
"./entity-bulk-action": "./dist-cms/packages/core/entity-bulk-action/index.js",
@@ -139,10 +140,10 @@
"uuid": "^9.0.0"
},
"devDependencies": {
"@babel/core": "^7.22.10",
"@babel/core": "^7.22.17",
"@mdx-js/react": "^2.3.0",
"@open-wc/testing": "^3.2.0",
"@playwright/test": "^1.36.2",
"@playwright/test": "^1.37.1",
"@rollup/plugin-commonjs": "^25.0.3",
"@rollup/plugin-json": "^6.0.0",
"@rollup/plugin-node-resolve": "^15.1.0",
@@ -158,7 +159,7 @@
"@types/mocha": "^10.0.1",
"@types/uuid": "^9.0.2",
"@typescript-eslint/eslint-plugin": "^6.3.0",
"@typescript-eslint/parser": "^6.3.0",
"@typescript-eslint/parser": "^6.5.0",
"@web/dev-server-esbuild": "^0.4.1",
"@web/dev-server-import-maps": "^0.1.1",
"@web/test-runner": "^0.17.0",
@@ -166,7 +167,7 @@
"babel-loader": "^9.1.3",
"eslint": "^8.46.0",
"eslint-config-prettier": "^9.0.0",
"eslint-import-resolver-typescript": "^3.5.5",
"eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-import": "^2.28.0",
"eslint-plugin-lit": "^1.8.3",
"eslint-plugin-lit-a11y": "^4.1.0",

View File

@@ -1,7 +1,7 @@
import type { UmbAppErrorElement } from './app-error.element.js';
import { UMB_APP, UmbAppContext } from './app.context.js';
import { umbLocalizationRegistry } from '@umbraco-cms/backoffice/localization';
import { UMB_AUTH, UmbAuthFlow, UmbAuthContext } from '@umbraco-cms/backoffice/auth';
import { UMB_APP, UmbAppContext } from '@umbraco-cms/backoffice/context';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UUIIconRegistryEssential } from '@umbraco-cms/backoffice/external/uui';
import { UmbIconRegistry } from '@umbraco-cms/backoffice/icon';
@@ -114,6 +114,10 @@ export class UmbAppElement extends UmbLitElement {
// Try to initialise the auth flow and get the runtime status
try {
if (this.bypassAuth === false) {
await this.#authFlow.fetchServiceConfiguration();
}
// Get the current runtime level
await this.#setInitStatus();

View File

@@ -1,3 +1,4 @@
export * from './app-context-config.interface.js';
export * from './app-error.element.js';
export * from './app.element.js';
export * from './app.context.js';

View File

@@ -25,6 +25,7 @@ const CORE_PACKAGES = [
import('../../packages/umbraco-news/umbraco-package.js'),
import('../../packages/tags/umbraco-package.js'),
import('../../packages/log-viewer/umbraco-package.js'),
import('../../packages/health-check/umbraco-package.js'),
];
@customElement('umb-backoffice')

View File

@@ -31,6 +31,7 @@ import { handlers as stylesheetHandlers } from './handlers/stylesheet.handlers.j
import { handlers as partialViewsHandlers } from './handlers/partial-views.handlers.js';
import { handlers as tagHandlers } from './handlers/tag-handlers.js';
import { handlers as configHandlers } from './handlers/config.handlers.js';
import { handlers as scriptHandlers } from './handlers/scripts.handlers.js';
const handlers = [
serverHandlers.serverVersionHandler,
@@ -65,6 +66,7 @@ const handlers = [
...partialViewsHandlers,
...tagHandlers,
...configHandlers,
...scriptHandlers,
];
switch (import.meta.env.VITE_UMBRACO_INSTALL_STATUS) {

View File

@@ -0,0 +1,238 @@
import { UmbData } from './data.js';
import { UmbEntityData } from './entity.data.js';
import { createFileItemResponseModelBaseModel, createFileSystemTreeItem, createTextFileItem } from './utils.js';
import {
CreatePathFolderRequestModel,
CreateTextFileViewModelBaseModel,
FileSystemTreeItemPresentationModel,
PagedFileSystemTreeItemPresentationModel,
ScriptItemResponseModel,
ScriptResponseModel,
UpdateScriptRequestModel,
} from '@umbraco-cms/backoffice/backend-api';
type ScriptsDataItem = ScriptResponseModel & FileSystemTreeItemPresentationModel;
export const data: Array<ScriptsDataItem> = [
{
path: 'some-folder',
isFolder: true,
name: 'some-folder',
type: 'script',
hasChildren: true,
},
{
path: 'another-folder',
isFolder: true,
name: 'another-folder',
type: 'script',
hasChildren: true,
},
{
path: 'very important folder',
isFolder: true,
name: 'very important folder',
type: 'script',
hasChildren: true,
},
{
path: 'some-folder/ugly script.js',
isFolder: false,
name: 'ugly script.js',
type: 'script',
hasChildren: false,
content: `function makeid(length) {
var result = '';
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
var charactersLength = characters.length;
for ( var i = 0; i < length; i++ ) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
}
return result;
}
console.log(makeid(5));`,
},
{
path: 'some-folder/nice script.js',
isFolder: false,
name: 'nice script.js',
type: 'script',
hasChildren: false,
content: `var items = {
"item_1": "1",
"item_2": "2",
"item_3": "3"
}
for (var item in items) {
console.log(items[item]);
}`,
},
{
path: 'another-folder/only bugs.js',
isFolder: false,
name: 'only bugs.js',
type: 'script',
hasChildren: false,
content: `var my_arr = [4, '', 0, 10, 7, '', false, 10];
my_arr = my_arr.filter(Boolean);
console.log(my_arr);`,
},
{
path: 'very important folder/no bugs at all.js',
isFolder: false,
name: 'no bugs at all.js',
type: 'script',
hasChildren: false,
content: `const date_str = "07/20/2021";
const date = new Date(date_str);
const full_day_name = date.toLocaleDateString('default', { weekday: 'long' });
// -> to get full day name e.g. Tuesday
const short_day_name = date.toLocaleDateString('default', { weekday: 'short' });
console.log(short_day_name);
// -> TO get the short day name e.g. Tue`,
},
{
path: 'very important folder/nope.js',
isFolder: false,
name: 'nope.js',
type: 'script',
hasChildren: false,
content: `// Define an object
const employee = {
"name": "John Deo",
"department": "IT",
"project": "Inventory Manager"
};
// Remove a property
delete employee["project"];
console.log(employee);`,
},
];
class UmbScriptsData extends UmbData<ScriptsDataItem> {
constructor() {
super(data);
}
getTreeRoot(): PagedFileSystemTreeItemPresentationModel {
const items = this.data.filter((item) => item.path?.includes('/') === false);
const treeItems = items.map((item) => createFileSystemTreeItem(item));
const total = items.length;
return { items: treeItems, total };
}
getTreeItemChildren(parentPath: string): PagedFileSystemTreeItemPresentationModel {
const items = this.data.filter((item) => item.path?.startsWith(parentPath));
const treeItems = items.map((item) => createFileSystemTreeItem(item));
const total = items.length;
return { items: treeItems, total };
}
getTreeItem(paths: Array<string>): Array<FileSystemTreeItemPresentationModel> {
const items = this.data.filter((item) => paths.includes(item.path ?? ''));
return items.map((item) => createFileSystemTreeItem(item));
}
getItem(paths: Array<string>): Array<ScriptItemResponseModel> {
const items = this.data.filter((item) => paths.includes(item.path ?? ''));
return items.map((item) => createFileItemResponseModelBaseModel(item));
}
getFolder(path: string): FileSystemTreeItemPresentationModel {
const items = data.filter((item) => item.isFolder && item.path === path);
return items as FileSystemTreeItemPresentationModel;
}
postFolder(payload: CreatePathFolderRequestModel) {
const newFolder = {
path: `${payload.parentPath ?? ''}/${payload.name}`,
isFolder: true,
name: payload.name,
type: 'script',
hasChildren: false,
};
return this.insert(newFolder);
}
deleteFolder(path: string) {
return this.delete([path]);
}
getScript(path: string): ScriptResponseModel | undefined {
return createTextFileItem(this.data.find((item) => item.path === path));
}
insertScript(item: CreateTextFileViewModelBaseModel) {
const newItem: ScriptsDataItem = {
...item,
path: `${item.parentPath}/${item.name}.js}`,
isFolder: false,
hasChildren: false,
type: 'script',
};
this.insert(newItem);
return newItem;
}
insert(item: ScriptsDataItem) {
const exits = this.data.find((i) => i.path === item.path);
if (exits) {
throw new Error(`Item with path ${item.path} already exists`);
}
this.data.push(item);
return item;
}
updateData(updateItem: UpdateScriptRequestModel) {
const itemIndex = this.data.findIndex((item) => item.path === updateItem.existingPath);
const item = this.data[itemIndex];
if (!item) return;
// TODO: revisit this code, seems like something we can solve smarter/type safer now:
const itemKeys = Object.keys(item);
const newItem = { ...item };
for (const [key] of Object.entries(updateItem)) {
if (itemKeys.indexOf(key) !== -1) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
newItem[key] = updateItem[key];
}
}
// Specific to fileSystem, we need to update path based on name:
const dirName = updateItem.existingPath?.substring(0, updateItem.existingPath.lastIndexOf('/'));
newItem.path = `${dirName}${dirName ? '/' : ''}${updateItem.name}`;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.data[itemIndex] = newItem;
}
delete(paths: Array<string>) {
const pathsOfItemsToDelete = this.data
.filter((item) => {
if (!item.path) throw new Error('Item has no path');
return paths.includes(item.path);
})
.map((item) => item.path);
this.data = this.data.filter((item) => {
if (!item.path) throw new Error('Item has no path');
return paths.indexOf(item.path) === -1;
});
return pathsOfItemsToDelete;
}
}
export const umbScriptsData = new UmbScriptsData();

View File

@@ -0,0 +1,86 @@
const { rest } = window.MockServiceWorker;
import { RestHandler, MockedRequest, DefaultBodyType } from 'msw';
import { umbScriptsData } from '../data/scripts.data.js';
import { umbracoPath } from '@umbraco-cms/backoffice/utils';
import { CreatePathFolderRequestModel, CreateTextFileViewModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
const treeHandlers = [
rest.get(umbracoPath('/tree/script/root'), (req, res, ctx) => {
const response = umbScriptsData.getTreeRoot();
return res(ctx.status(200), ctx.json(response));
}),
rest.get(umbracoPath('/tree/script/children'), (req, res, ctx) => {
const path = req.url.searchParams.get('path');
if (!path) return;
const response = umbScriptsData.getTreeItemChildren(path);
return res(ctx.status(200), ctx.json(response));
}),
rest.get(umbracoPath('/tree/script/item'), (req, res, ctx) => {
const paths = req.url.searchParams.getAll('paths');
if (!paths) return;
const items = umbScriptsData.getTreeItem(paths);
return res(ctx.status(200), ctx.json(items));
}),
];
const detailHandlers: RestHandler<MockedRequest<DefaultBodyType>>[] = [
rest.get(umbracoPath('/script'), (req, res, ctx) => {
const path = decodeURIComponent(req.url.searchParams.get('path') ?? '').replace('-js', '.js');
if (!path) return res(ctx.status(400));
const response = umbScriptsData.getScript(path);
return res(ctx.status(200), ctx.json(response));
}),
rest.get(umbracoPath('/script/item'), (req, res, ctx) => {
const path = decodeURIComponent(req.url.searchParams.get('path') ?? '').replace('-js', '.js');
if (!path) return res(ctx.status(400, 'no body found'));
const response = umbScriptsData.getItem([path]);
return res(ctx.status(200), ctx.json(response));
}),
rest.post(umbracoPath('/script'), async (req, res, ctx) => {
const requestBody = (await req.json()) as CreateTextFileViewModelBaseModel;
if (!requestBody) return res(ctx.status(400, 'no body found'));
const response = umbScriptsData.insertScript(requestBody);
return res(ctx.status(200), ctx.json(response));
}),
rest.delete(umbracoPath('/script'), (req, res, ctx) => {
const path = req.url.searchParams.get('path');
if (!path) return res(ctx.status(400));
const response = umbScriptsData.delete([path]);
return res(ctx.status(200), ctx.json(response));
}),
rest.put(umbracoPath('/script'), async (req, res, ctx) => {
const requestBody = (await req.json()) as CreateTextFileViewModelBaseModel;
if (!requestBody) return res(ctx.status(400, 'no body found'));
const response = umbScriptsData.updateData(requestBody);
return res(ctx.status(200));
}),
];
const folderHandlers: RestHandler<MockedRequest<DefaultBodyType>>[] = [
rest.get(umbracoPath('script/folder'), (req, res, ctx) => {
const path = decodeURIComponent(req.url.searchParams.get('path') ?? '').replace('-js', '.js');
if (!path) return res(ctx.status(400));
const response = umbScriptsData.getFolder(path);
return res(ctx.status(200), ctx.json(response));
}),
rest.post(umbracoPath('script/folder'), (req, res, ctx) => {
const requestBody = req.json() as CreatePathFolderRequestModel;
if (!requestBody) return res(ctx.status(400, 'no body found'));
return res(ctx.status(200));
}),
rest.delete(umbracoPath('script/folder'), (req, res, ctx) => {
const path = decodeURIComponent(req.url.searchParams.get('path') ?? '').replace('-js', '.js');
if (!path) return res(ctx.status(400));
const response = umbScriptsData.deleteFolder(path);
return res(ctx.status(200), ctx.json(response));
}),
];
export const handlers = [...treeHandlers, ...detailHandlers, ...folderHandlers];

View File

@@ -22,7 +22,7 @@ export class UmbContentTypeContainerStructureHelper {
// Containers defined in data might be more than actual containers to display as we merge them by name.
// Direct containers are the containers defining the total of this container(Multiple containers with the same name and type)
private _ownerAlikeContainers: PropertyTypeContainerModelBaseModel[] = [];
// Owner containers are containers owned by the owner Document Type (The specific one up for editing)
// Owner containers are containers owned by the owner Content Type (The specific one up for editing)
private _ownerContainers: PropertyTypeContainerModelBaseModel[] = [];
// State containing the merged containers (only one pr. name):
@@ -106,7 +106,7 @@ export class UmbContentTypeContainerStructureHelper {
(ownerContainers) => {
this._ownerContainers = ownerContainers || [];
},
'_observeOwnerContainers'
'_observeOwnerContainers',
);
} else if (this._ownerName) {
new UmbObserverController(
@@ -121,7 +121,7 @@ export class UmbContentTypeContainerStructureHelper {
this._observeChildContainers();
}
},
'_observeOwnerContainers'
'_observeOwnerContainers',
);
}
}
@@ -136,7 +136,7 @@ export class UmbContentTypeContainerStructureHelper {
(hasProperties) => {
this.#hasProperties.next(hasProperties);
},
'_observeOwnerHasProperties_' + container.id
'_observeOwnerHasProperties_' + container.id,
);
});
}
@@ -149,7 +149,7 @@ export class UmbContentTypeContainerStructureHelper {
this.#host,
this.#structure!.containersOfParentKey(container.id, this._childType!),
this._insertGroupContainers,
'_observeGroupsOf_' + container.id
'_observeGroupsOf_' + container.id,
);
});
}
@@ -164,7 +164,7 @@ export class UmbContentTypeContainerStructureHelper {
this.#containers.next([]);
this._insertGroupContainers(rootContainers);
},
'_observeRootContainers'
'_observeRootContainers',
);
}
@@ -223,7 +223,7 @@ export class UmbContentTypeContainerStructureHelper {
if (!this.#structure) return;
const newName =
this.#structure.makeContainerNameUniqueForOwnerDocument(name, this._childType, containerParentId) ?? name;
this.#structure.makeContainerNameUniqueForOwnerContentType(name, this._childType, containerParentId) ?? name;
return await this.partialUpdateContainer(containerId, { name: newName });
}

View File

@@ -29,8 +29,8 @@ export class UmbContentTypePropertyStructureHelper {
this.#propertyStructure.sortBy((a, b) => ((a as any).sortOrder ?? 0) - ((b as any).sortOrder ?? 0));
}
public getOwnerDocumentTypes() {
return this.#structure?.documentTypes;
get ownerDocumentTypes() {
return this.#structure?.contentTypes;
}
public setStructureManager(structure: UmbContentTypePropertyStructureManager) {

View File

@@ -28,11 +28,11 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
#contentTypeRepository: R;
#ownerDocumentTypeId?: string;
#documentTypeObservers = new Array<UmbController>();
#documentTypes = new UmbArrayState<T>([], (x) => x.id);
readonly documentTypes = this.#documentTypes.asObservable();
private readonly _documentTypeContainers = this.#documentTypes.asObservablePart((x) =>
#ownerContentTypeId?: string;
#contentTypeObservers = new Array<UmbController>();
#contentTypes = new UmbArrayState<T>([], (x) => x.id);
readonly contentTypes = this.#contentTypes.asObservable();
private readonly _contentTypeContainers = this.#contentTypes.asObservablePart((x) =>
x.flatMap((x) => x.containers ?? []),
);
@@ -43,13 +43,13 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
this.#host = host;
this.#contentTypeRepository = typeRepository;
new UmbObserverController(host, this.documentTypes, (documentTypes) => {
documentTypes.forEach((documentType) => {
this._loadDocumentTypeCompositions(documentType);
new UmbObserverController(host, this.contentTypes, (contentTypes) => {
contentTypes.forEach((contentType) => {
this._loadContentTypeCompositions(contentType);
});
});
new UmbObserverController(host, this._documentTypeContainers, (documentTypeContainers) => {
this.#containers.next(documentTypeContainers);
new UmbObserverController(host, this._contentTypeContainers, (contentTypeContainers) => {
this.#containers.next(contentTypeContainers);
});
}
@@ -60,7 +60,7 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
public async loadType(id?: string) {
this._reset();
this.#ownerDocumentTypeId = id;
this.#ownerContentTypeId = id;
const promiseResult = this._loadType(id);
this.#init = promiseResult;
@@ -76,29 +76,27 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
const { data } = await this.#contentTypeRepository.createScaffold(parentId);
if (!data) return {};
this.#ownerDocumentTypeId = data.id;
this.#ownerContentTypeId = data.id;
this.#init = this._observeDocumentType(data);
this.#init = this._observeContentType(data);
await this.#init;
return { data };
}
public async save() {
const documentType = this.getOwnerDocumentType();
if (!documentType || !documentType.id) return false;
const contentType = this.getOwnerContentType();
if (!contentType || !contentType.id) return false;
await this.#contentTypeRepository.save(documentType.id, documentType);
await this.#contentTypeRepository.save(contentType.id, contentType);
return true;
}
public async create() {
const documentType = this.getOwnerDocumentType();
if (!documentType || !documentType.id) return false;
//const value = documentType as CreateDocumentTypeRequestModel & { id: string };
const { data } = await this.#contentTypeRepository.create(documentType);
const contentType = this.getOwnerContentType();
if (!contentType || !contentType.id) return false;
const { data } = await this.#contentTypeRepository.create(contentType);
if (!data) return false;
await this.loadType(data.id);
@@ -108,7 +106,7 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
private async _ensureType(id?: string) {
if (!id) return;
if (this.#documentTypes.getValue().find((x) => x.id === id)) return;
if (this.#contentTypes.getValue().find((x) => x.id === id)) return;
await this._loadType(id);
}
@@ -118,53 +116,47 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
const { data } = await this.#contentTypeRepository.requestById(id);
if (!data) return {};
await this._observeDocumentType(data);
await this._observeContentType(data);
return { data };
}
public async _observeDocumentType(data: T) {
public async _observeContentType(data: T) {
if (!data.id) return;
// Load inherited and composed types:
this._loadDocumentTypeCompositions(data);
this._loadContentTypeCompositions(data);
this.#documentTypeObservers.push(
this.#contentTypeObservers.push(
new UmbObserverController(this.#host, await this.#contentTypeRepository.byId(data.id), (docType) => {
if (docType) {
// TODO: Handle if there was changes made to the owner document type in this context.
/*
possible easy solutions could be to notify user wether they want to update(Discard the changes to accept the new ones).
*/
this.#documentTypes.appendOne(docType);
this.#contentTypes.appendOne(docType);
}
}),
);
}
private async _loadDocumentTypeCompositions(documentType: T) {
documentType.compositions?.forEach((composition) => {
private async _loadContentTypeCompositions(contentType: T) {
contentType.compositions?.forEach((composition) => {
this._ensureType(composition.id);
});
}
/*
private async _initDocumentTypeContainers(documentType: T) {
documentType.containers?.forEach((container) => {
this.#containers.appendOne({ ...container, _ownerDocumentTypeKey: documentType.id });
});
}
*/
/** Public methods for consuming structure: */
ownerDocumentType() {
return this.#documentTypes.asObservablePart((x) => x.find((y) => y.id === this.#ownerDocumentTypeId));
ownerContentType() {
return this.#contentTypes.asObservablePart((x) => x.find((y) => y.id === this.#ownerContentTypeId));
}
getOwnerDocumentType() {
return this.#documentTypes.getValue().find((y) => y.id === this.#ownerDocumentTypeId);
getOwnerContentType() {
return this.#contentTypes.getValue().find((y) => y.id === this.#ownerContentTypeId);
}
updateOwnerDocumentType(entry: T) {
this.#documentTypes.updateOne(this.#ownerDocumentTypeId, entry);
updateOwnerContentType(entry: T) {
this.#contentTypes.updateOne(this.#ownerContentTypeId, entry);
}
// We could move the actions to another class?
@@ -176,7 +168,7 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
sortOrder?: number,
) {
await this.#init;
contentTypeId = contentTypeId ?? this.#ownerDocumentTypeId!;
contentTypeId = contentTypeId ?? this.#ownerContentTypeId!;
const container: PropertyTypeContainerModelBaseModel = {
id: UmbId.new(),
@@ -186,15 +178,15 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
sortOrder: sortOrder ?? 0,
};
const containers = [...(this.#documentTypes.getValue().find((x) => x.id === contentTypeId)?.containers ?? [])];
const containers = [...(this.#contentTypes.getValue().find((x) => x.id === contentTypeId)?.containers ?? [])];
containers.push(container);
this.#documentTypes.updateOne(contentTypeId, { containers });
this.#contentTypes.updateOne(contentTypeId, { containers });
return container;
}
makeContainerNameUniqueForOwnerDocument(
makeContainerNameUniqueForOwnerContentType(
newName: string,
containerType: PropertyContainerTypes = 'Tab',
parentId: string | null = null,
@@ -213,28 +205,28 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
}
async updateContainer(
documentTypeId: string | null,
contentTypeId: string | null,
containerId: string,
partialUpdate: Partial<PropertyTypeContainerModelBaseModel>,
) {
await this.#init;
documentTypeId = documentTypeId ?? this.#ownerDocumentTypeId!;
contentTypeId = contentTypeId ?? this.#ownerContentTypeId!;
const frozenContainers = this.#documentTypes.getValue().find((x) => x.id === documentTypeId)?.containers ?? [];
const frozenContainers = this.#contentTypes.getValue().find((x) => x.id === contentTypeId)?.containers ?? [];
const containers = partialUpdateFrozenArray(frozenContainers, partialUpdate, (x) => x.id === containerId);
this.#documentTypes.updateOne(documentTypeId, { containers });
this.#contentTypes.updateOne(contentTypeId, { containers });
}
async removeContainer(documentTypeKey: string | null, containerId: string | null = null) {
async removeContainer(contentTypeId: string | null, containerId: string | null = null) {
await this.#init;
documentTypeKey = documentTypeKey ?? this.#ownerDocumentTypeId!;
contentTypeId = contentTypeId ?? this.#ownerContentTypeId!;
const frozenContainers = this.#documentTypes.getValue().find((x) => x.id === documentTypeKey)?.containers ?? [];
const frozenContainers = this.#contentTypes.getValue().find((x) => x.id === contentTypeId)?.containers ?? [];
const containers = frozenContainers.filter((x) => x.id !== containerId);
this.#documentTypes.updateOne(documentTypeKey, { containers });
this.#contentTypes.updateOne(contentTypeId, { containers });
}
createPropertyScaffold(containerId: string | null = null, sortOrder?: number) {
@@ -262,80 +254,76 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
return property;
}
async createProperty(documentTypeId: string | null, containerId: string | null = null, sortOrder?: number) {
async createProperty(contentTypeId: string | null, containerId: string | null = null, sortOrder?: number) {
await this.#init;
documentTypeId = documentTypeId ?? this.#ownerDocumentTypeId!;
contentTypeId = contentTypeId ?? this.#ownerContentTypeId!;
const property: PropertyTypeModelBaseModel = this.createPropertyScaffold(containerId, sortOrder);
const properties = [...(this.#documentTypes.getValue().find((x) => x.id === documentTypeId)?.properties ?? [])];
const properties = [...(this.#contentTypes.getValue().find((x) => x.id === contentTypeId)?.properties ?? [])];
properties.push(property);
this.#documentTypes.updateOne(documentTypeId, { properties });
this.#contentTypes.updateOne(contentTypeId, { properties });
return property;
}
async insertProperty(documentTypeId: string | null, property: PropertyTypeModelBaseModel) {
async insertProperty(contentTypeId: string | null, property: PropertyTypeModelBaseModel) {
await this.#init;
documentTypeId = documentTypeId ?? this.#ownerDocumentTypeId!;
contentTypeId = contentTypeId ?? this.#ownerContentTypeId!;
const frozenProperties = this.#documentTypes.getValue().find((x) => x.id === documentTypeId)?.properties ?? [];
const frozenProperties = this.#contentTypes.getValue().find((x) => x.id === contentTypeId)?.properties ?? [];
const properties = appendToFrozenArray(frozenProperties, property, (x) => x.id === property.id);
this.#documentTypes.updateOne(documentTypeId, { properties });
this.#contentTypes.updateOne(contentTypeId, { properties });
}
async removeProperty(documentTypeId: string | null, propertyId: string) {
async removeProperty(contentTypeId: string | null, propertyId: string) {
await this.#init;
documentTypeId = documentTypeId ?? this.#ownerDocumentTypeId!;
contentTypeId = contentTypeId ?? this.#ownerContentTypeId!;
const frozenProperties = this.#documentTypes.getValue().find((x) => x.id === documentTypeId)?.properties ?? [];
const frozenProperties = this.#contentTypes.getValue().find((x) => x.id === contentTypeId)?.properties ?? [];
const properties = filterFrozenArray(frozenProperties, (x) => x.id !== propertyId);
this.#documentTypes.updateOne(documentTypeId, { properties });
this.#contentTypes.updateOne(contentTypeId, { properties });
}
async updateProperty(
documentTypeId: string | null,
contentTypeId: string | null,
propertyId: string,
partialUpdate: Partial<PropertyTypeModelBaseModel>,
) {
await this.#init;
documentTypeId = documentTypeId ?? this.#ownerDocumentTypeId!;
contentTypeId = contentTypeId ?? this.#ownerContentTypeId!;
const frozenProperties = this.#documentTypes.getValue().find((x) => x.id === documentTypeId)?.properties ?? [];
const frozenProperties = this.#contentTypes.getValue().find((x) => x.id === contentTypeId)?.properties ?? [];
const properties = partialUpdateFrozenArray(frozenProperties, partialUpdate, (x) => x.id === propertyId);
this.#documentTypes.updateOne(documentTypeId, { properties });
this.#contentTypes.updateOne(contentTypeId, { properties });
}
// TODO: Refactor: These property methods, should maybe be named without structure in their name.
async propertyStructureById(
propertyId: string
) {
async propertyStructureById(propertyId: string) {
await this.#init;
return this.#documentTypes.asObservablePart((docTypes) => {
return this.#contentTypes.asObservablePart((docTypes) => {
for (const docType of docTypes) {
const foundProp = docType.properties?.find((property) => property.id === propertyId);
if(foundProp) {
if (foundProp) {
return foundProp;
}
}
return undefined;
});
}
async propertyStructureByAlias(
propertyAlias: string
) {
async propertyStructureByAlias(propertyAlias: string) {
await this.#init;
return this.#documentTypes.asObservablePart((docTypes) => {
return this.#contentTypes.asObservablePart((docTypes) => {
for (const docType of docTypes) {
const foundProp = docType.properties?.find((property) => property.alias === propertyAlias);
if(foundProp) {
if (foundProp) {
return foundProp;
}
}
@@ -345,52 +333,35 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
async getPropertyStructureById(propertyId: string) {
await this.#init;
for (const docType of this.#documentTypes.getValue()) {
for (const docType of this.#contentTypes.getValue()) {
const foundProp = docType.properties?.find((property) => property.id === propertyId);
if(foundProp) {
if (foundProp) {
return foundProp;
}
}
return undefined;
}
async getPropertyStructureByAlias(propertyAlias: string) {
await this.#init;
for (const docType of this.#documentTypes.getValue()) {
for (const docType of this.#contentTypes.getValue()) {
const foundProp = docType.properties?.find((property) => property.alias === propertyAlias);
if(foundProp) {
if (foundProp) {
return foundProp;
}
}
return undefined;
}
/*
rootDocumentTypeName() {
return this.#documentTypes.asObservablePart((docTypes) => {
const docType = docTypes.find((x) => x.id === this.#rootDocumentTypeKey);
return docType?.name ?? '';
});
}
*/
ownerDocumentTypeObservablePart<PartResult>(mappingFunction: MappingFunction<T, PartResult>) {
return this.#documentTypes.asObservablePart((docTypes) => {
const docType = docTypes.find((x) => x.id === this.#ownerDocumentTypeId);
ownerContentTypeObservablePart<PartResult>(mappingFunction: MappingFunction<T, PartResult>) {
return this.#contentTypes.asObservablePart((docTypes) => {
const docType = docTypes.find((x) => x.id === this.#ownerContentTypeId);
return docType ? mappingFunction(docType) : undefined;
});
}
/*
nameOfDocumentType(id: string) {
return this.#documentTypes.asObservablePart((docTypes) => {
const docType = docTypes.find((x) => x.id === id);
return docType?.name ?? '';
});
}
*/
hasPropertyStructuresOf(containerId: string | null) {
return this.#documentTypes.asObservablePart((docTypes) => {
return this.#contentTypes.asObservablePart((docTypes) => {
return (
docTypes.find((docType) => {
return docType.properties?.find((property) => property.containerId === containerId);
@@ -398,11 +369,13 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
);
});
}
rootPropertyStructures() {
return this.propertyStructuresOf(null);
}
propertyStructuresOf(containerId: string | null) {
return this.#documentTypes.asObservablePart((docTypes) => {
return this.#contentTypes.asObservablePart((docTypes) => {
const props: DocumentTypePropertyTypeResponseModel[] = [];
docTypes.forEach((docType) => {
docType.properties?.forEach((property) => {
@@ -432,15 +405,15 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
}
ownerContainersOf(containerType: PropertyContainerTypes) {
return this.ownerDocumentTypeObservablePart((x) => x.containers?.filter((x) => x.type === containerType) ?? []);
return this.ownerContentTypeObservablePart((x) => x.containers?.filter((x) => x.type === containerType) ?? []);
}
getOwnerContainers(containerType: PropertyContainerTypes, parentId: string | null = null) {
return this.getOwnerDocumentType()?.containers?.filter((x) => x.parentId === parentId && x.type === containerType);
return this.getOwnerContentType()?.containers?.filter((x) => x.parentId === parentId && x.type === containerType);
}
isOwnerContainer(containerId: string) {
return this.getOwnerDocumentType()?.containers?.filter((x) => x.id === containerId);
return this.getOwnerContentType()?.containers?.filter((x) => x.id === containerId);
}
containersOfParentKey(
@@ -460,14 +433,14 @@ export class UmbContentTypePropertyStructureManager<R extends UmbDetailRepositor
}
private _reset() {
this.#documentTypeObservers.forEach((observer) => observer.destroy());
this.#documentTypeObservers = [];
this.#documentTypes.next([]);
this.#contentTypeObservers.forEach((observer) => observer.destroy());
this.#contentTypeObservers = [];
this.#contentTypes.next([]);
this.#containers.next([]);
}
public destroy() {
this._reset();
this.#documentTypes.complete();
this.#contentTypes.complete();
this.#containers.complete();
}
}

View File

@@ -0,0 +1 @@
export * from './input-culture-select/input-culture-select.element.js';

View File

@@ -1,10 +1,6 @@
import { UmbCultureRepository } from '../../repository/culture.repository.js';
import { css, html, repeat, ifDefined, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import {
FormControlMixin,
UUIComboboxElement,
UUIComboboxEvent,
} from '@umbraco-cms/backoffice/external/uui';
import { html, repeat, ifDefined, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { FormControlMixin, UUIComboboxElement, UUIComboboxEvent } from '@umbraco-cms/backoffice/external/uui';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/events';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { CultureReponseModel } from '@umbraco-cms/backoffice/backend-api';
@@ -89,12 +85,11 @@ export class UmbInputCultureSelectElement extends FormControlMixin(UmbLitElement
${repeat(
this.#filteredCultures,
(culture) => culture.name,
(culture) =>
html`
<uui-combobox-list-option value=${ifDefined(culture.name)}
>${culture.englishName}</uui-combobox-list-option
>
`
(culture) => html`
<uui-combobox-list-option value=${ifDefined(culture.name)}
>${culture.englishName}</uui-combobox-list-option
>
`,
)}
</uui-combobox-list>
</uui-combobox>

View File

@@ -0,0 +1,2 @@
export * from './components/index.js';
export * from './repository/culture.repository.js';

View File

@@ -1,4 +1,4 @@
import { UmbCultureRepository } from '../repository/culture.repository.js';
import { UmbCultureRepository } from './culture.repository.js';
import { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry';
export const CULTURE_REPOSITORY_ALIAS = 'Umb.Repository.Culture';

View File

@@ -8,6 +8,7 @@ import { manifests as workspaceManifests } from './workspace/manifests.js';
import { manifests as modalManifests } from './modal/common/manifests.js';
import { manifests as themeManifests } from './themes/manifests.js';
import { manifests as conditionManifests } from './extension-registry/conditions/manifests.js';
import { manifests as cultureManifests } from './culture/manifests.js';
import { UmbNotificationContext, UMB_NOTIFICATION_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/notification';
import { UmbModalManagerContext, UMB_MODAL_MANAGER_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/modal';
@@ -42,6 +43,7 @@ export * from './store/index.js';
export * from './tree/index.js';
export * from './variant/index.js';
export * from './workspace/index.js';
export * from './culture/index.js';
const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
...conditionManifests,
@@ -53,6 +55,7 @@ const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
...workspaceManifests,
...modalManifests,
...themeManifests,
...cultureManifests,
];
export const onInit: UmbEntryPointOnInit = (host, extensionRegistry) => {

View File

@@ -8,6 +8,7 @@ import { PropertyValueMap, css, html, nothing, customElement, state } from '@umb
import { UmbModalBaseElement } from '@umbraco-cms/internal/modal';
import { UmbPropertySettingsModalResult, UmbPropertySettingsModalData } from '@umbraco-cms/backoffice/modal';
import { generateAlias } from '@umbraco-cms/backoffice/utils';
import { UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN } from '@umbraco-cms/backoffice/document-type';
// TODO: Could base take a token to get its types?.
// TODO: Missing a workspace context... unless this should not be a workspace any way.
@customElement('umb-property-settings-modal')
@@ -43,12 +44,24 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement<
@state() private _aliasLocked = true;
@state()
protected _ownerDocumentType?: UmbPropertySettingsModalResult;
@state()
protected _returnData!: UmbPropertySettingsModalResult;
connectedCallback(): void {
super.connectedCallback();
this._returnData = JSON.parse(JSON.stringify(this.data));
// TODO: This is actually not good enough, we need to be able to get to the DOCUMENT_WORKSPACE_CONTEXT, so we can have a look at the draft/runtime version of the document. Otherwise 'Vary by culture' is first updated when saved.
this.consumeContext(UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN, (instance) => {
this.observe(instance.byId(this.data?.documentTypeId), (documentType) => {
this._ownerDocumentType = documentType;
this.requestUpdate('_ownerDocumentType');
}, '_observeDocumentType');
});
this._returnData = JSON.parse(JSON.stringify(this.data?.propertyData ?? {}));
this._returnData.validation ??= {};
@@ -105,7 +118,7 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement<
if (!this._aliasLocked) {
this._returnData.alias = alias;
} else {
this._returnData.alias = this.data?.alias;
this._returnData.alias = this.data?.propertyData?.alias;
}
this.requestUpdate('_returnData');
}
@@ -177,6 +190,11 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement<
this.requestUpdate('_returnData');
}
#onVaryByCultureChange(event: UUIBooleanInputEvent) {
this._returnData.variesByCulture = event.target.checked;
this.requestUpdate('_returnData');
}
// TODO: This would conceptually be a Property Editor Workspace, should be changed at one point in the future.
// For now this is hacky made available by giving the element an fixed alias.
// This would allow for workspace views and workspace actions.
@@ -226,6 +244,7 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement<
${this.#renderCustomValidation()}
</div>
<hr />
${this.#renderVariationControls()}
<div class="container">
<b style="margin-bottom: var(--uui-size-space-3)">Appearance</b>
<div id="appearances">${this.#renderAlignLeftIcon()} ${this.#renderAlignTopIcon()}</div>
@@ -277,13 +296,13 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement<
<uui-toggle
@change=${this.#onMandatoryChange}
id="mandatory"
value=${this._returnData.validation?.mandatory}
.checked=${this._returnData.validation?.mandatory ?? false}
slot="editor"></uui-toggle>
</div>
${this._returnData.validation?.mandatory
? html`<uui-input
name="mandatory-message"
value=${this._returnData.validation?.mandatoryMessage}
value=${this._returnData.validation?.mandatoryMessage ?? ''}
@change=${this.#onMandatoryMessageChange}
style="margin-top: var(--uui-size-space-1)"
id="mandatory-message"
@@ -312,6 +331,24 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement<
: nothing} `;
}
#renderVariationControls() {
return this._ownerDocumentType?.variesByCulture || this._ownerDocumentType?.variesBySegment ?
html`
<div class="container">
<b>Variation</b>
${this._ownerDocumentType?.variesByCulture ? this.#renderVaryByCulture() : ''}
</div>
<hr />`
: '';
}
#renderVaryByCulture() {
return html`<uui-toggle
@change=${this.#onVaryByCultureChange}
.checked=${this._returnData.variesByCulture ?? false}
label="Vary by culture"></uui-toggle>
`;
}
static styles = [
UmbTextStyles,
css`

View File

@@ -1,7 +1,10 @@
import { PropertyTypeModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export type UmbPropertySettingsModalData = PropertyTypeModelBaseModel;
export type UmbPropertySettingsModalData = {
documentTypeId: string;
propertyData: PropertyTypeModelBaseModel
};
export type UmbPropertySettingsModalResult = PropertyTypeModelBaseModel;
export const UMB_PROPERTY_SETTINGS_MODAL = new UmbModalToken<

View File

@@ -1,8 +1,4 @@
import {
UUIButtonState,
UUIPaginationElement,
UUIPaginationEvent,
} from '@umbraco-cms/backoffice/external/uui';
import { UUIButtonState, UUIPaginationElement, UUIPaginationEvent } from '@umbraco-cms/backoffice/external/uui';
import { css, html, nothing, customElement, state, query, property } from '@umbraco-cms/backoffice/external/lit';
import {
UmbModalManagerContext,
@@ -23,26 +19,26 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
@property({ type: Number, attribute: 'items-per-page' })
itemsPerPage = 20;
@property({ type: Number })
page = 1;
@state()
private _trackerEnabled = true;
@state()
private _total = 0;
@state()
private _redirectData?: RedirectUrlResponseModel[];
@state()
private _trackerStatus = true;
@state()
private _currentPage = 1;
@state()
private _total?: number;
@state()
private _buttonState: UUIButtonState;
@state()
private _filter?: string;
@query('#search-input')
private _searchField!: HTMLInputElement;
@query('#search')
private _search!: HTMLInputElement;
@query('uui-pagination')
private _pagination?: UUIPaginationElement;
@@ -58,214 +54,214 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
connectedCallback() {
super.connectedCallback();
this._getTrackerStatus();
this._getRedirectData();
this.#getTrackerStatus();
this.#getRedirectData();
}
private async _getTrackerStatus() {
async #getTrackerStatus() {
const { data } = await tryExecuteAndNotify(this, RedirectManagementResource.getRedirectManagementStatus());
if (data && data.status) this._trackerStatus = data.status === RedirectStatusModel.ENABLED ? true : false;
if (data && data.status) this._trackerEnabled = data.status === RedirectStatusModel.ENABLED ? true : false;
}
private _removeRedirectHandler(data: RedirectUrlResponseModel) {
// Fetch data
async #getRedirectData(filter: string | undefined = undefined) {
const skip = this.page * this.itemsPerPage - this.itemsPerPage;
const { data } = await tryExecuteAndNotify(
this,
RedirectManagementResource.getRedirectManagement({ filter, take: this.itemsPerPage, skip }),
);
if (!data) return;
this._total = data?.total;
this._redirectData = data?.items;
if (filter !== undefined) this._buttonState = 'success';
}
// Pagination
#onPageChange(event: UUIPaginationEvent) {
if (this.page === event.target.current) return;
this.page = event.target.current;
this.#getRedirectData();
}
// Delete Redirect Action
#onRequestDelete(data: RedirectUrlResponseModel) {
if (!data.id) return;
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
headline: 'Delete',
content: html`
<div style="width:300px">
<p>This will remove the redirect</p>
Original URL: <strong>${data.originalUrl}</strong><br />
Redirected To: <strong>${data.destinationUrl}</strong>
<p>Are you sure you want to delete?</p>
<p>${this.localize.term('redirectUrls_redirectRemoveWarning')}</p>
${this.localize.term('redirectUrls_originalUrl')}: <strong>${data.originalUrl}</strong><br />
${this.localize.term('redirectUrls_redirectedTo')}: <strong>${data.destinationUrl}</strong>
</div>
`,
color: 'danger',
confirmLabel: 'Delete',
});
modalContext?.onSubmit().then(() => {
this._removeRedirect(data);
});
modalContext
?.onSubmit()
.then(() => {
this.#redirectDelete(data.id!);
})
.catch(() => undefined);
}
async #redirectDelete(id: string) {
const { error } = await tryExecuteAndNotify(this, RedirectManagementResource.deleteRedirectManagementById({ id }));
if (error) return;
this._redirectData = this._redirectData?.filter((x) => x.id !== id);
}
private async _removeRedirect(r: RedirectUrlResponseModel) {
if (!r.id) return;
const res = await tryExecuteAndNotify(this, RedirectManagementResource.deleteRedirectManagementById({ id: r.id }));
if (!res.error) {
// or just run a this._getRedirectData() again?
//this.shadowRoot?.getElementById(`redirect-key-${r.id}`)?.remove();
// No no, never manipulate DOM manipulate the data for the DOM:
this._redirectData = this._redirectData?.filter((x) => x.id !== r.id);
// Search action
#onKeypress(e: KeyboardEvent) {
if (e.key === 'Enter') this.#onSearch();
}
#onSearch() {
this._buttonState = 'waiting';
this._filter = this._search?.value ?? '';
if (this._pagination) this._pagination.current = 1;
this.page = 1;
this.#getRedirectData(this._search.value);
}
// Tracker disable/enable
#onRequestTrackerToggle() {
if (!this._trackerEnabled) {
this.#trackerToggle();
return;
}
}
private _disableRedirectHandler() {
const modalContext = this._modalContext?.open(UMB_CONFIRM_MODAL, {
headline: 'Disable URL tracker',
content: html`Are you sure you want to disable the URL tracker?`,
headline: `${this.localize.term('redirectUrls_disableUrlTracker')}`,
content: `${this.localize.term('redirectUrls_confirmDisable')}`,
color: 'danger',
confirmLabel: 'Disable',
});
modalContext?.onSubmit().then(() => {
this._toggleRedirect();
});
modalContext
?.onSubmit()
.then(() => {
this.#trackerToggle();
})
.catch(() => undefined);
}
private async _toggleRedirect() {
async #trackerToggle() {
const status = this._trackerEnabled ? RedirectStatusModel.DISABLED : RedirectStatusModel.ENABLED;
const { error } = await tryExecuteAndNotify(
this,
RedirectManagementResource.postRedirectManagementStatus({ status: RedirectStatusModel.ENABLED })
RedirectManagementResource.postRedirectManagementStatus({ status }),
);
if (!error) {
this._trackerStatus = !this._trackerStatus;
}
}
private _inputHandler(pressed: KeyboardEvent) {
if (pressed.key === 'Enter') this._searchHandler();
}
private async _searchHandler() {
this._filter = this._searchField.value;
if (this._pagination) this._pagination.current = 1;
this._currentPage = 1;
if (this._filter.length) {
this._buttonState = 'waiting';
}
this._getRedirectData();
}
private _onPageChange(event: UUIPaginationEvent) {
if (this._currentPage === event.target.current) return;
this._currentPage = event.target.current;
this._getRedirectData();
}
private async _getRedirectData() {
const skip = this._currentPage * this.itemsPerPage - this.itemsPerPage;
const { data } = await tryExecuteAndNotify(
this,
RedirectManagementResource.getRedirectManagement({ filter: this._filter, take: this.itemsPerPage, skip })
);
if (data) {
this._total = data?.total;
this._redirectData = data?.items;
if (this._filter?.length) {
this._buttonState = 'success';
}
}
if (error) return;
this._trackerEnabled = !this._trackerEnabled;
}
// Renders
render() {
return html`
<umb-body-layout header-transparent class="uui-text">
<div slot="header" id="header">
${this._trackerStatus
? html`<div>
<uui-input
id="search-input"
placeholder="Original URL"
label="input for search"
@keypress="${this._inputHandler}">
</uui-input>
<uui-button
id="search-button"
look="primary"
color="positive"
label="search"
.state="${this._buttonState}"
@click="${this._searchHandler}">
Search<uui-icon name="umb:search"></uui-icon>
</uui-button>
</div>
return html` <div id="redirect-actions">
${this._trackerEnabled
? html`<div id="search-wrapper">
<uui-input
id="search"
placeholder="${this.localize.term('redirectUrls_originalUrl')}"
label="${this.localize.term('redirectUrls_originalUrl')}"
@keypress=${this.#onKeypress}></uui-input>
<uui-button
label="Disable URL tracker"
look="outline"
color="danger"
@click="${this._disableRedirectHandler}">
Disable URL tracker
</uui-button> `
: html`<uui-button
label="Enable URL tracker"
look="primary"
look="primary"
color="positive"
label="${this.localize.term('general_search')}"
@click=${this.#onSearch}
.state=${this._buttonState}>
${this.localize.term('general_search')}
</uui-button>
</div>
<uui-button
look="outline"
color="danger"
label="${this.localize.term('redirectUrls_disableUrlTracker')}"
@click=${this.#onRequestTrackerToggle}>
${this.localize.term('redirectUrls_disableUrlTracker')}
</uui-button>`
: html`<div></div>
<uui-button
look="outline"
color="positive"
@click="${this._toggleRedirect}">
Enable URL tracker
</uui-button>`}
</div>
<div id="main">
${this._total && this._total > 0
? html`<div class="wrapper ${this._trackerStatus ? 'trackerEnabled' : 'trackerDisabled'}">
${this.renderTable()}
</div>`
: this._filter?.length
? this._renderZeroResults()
: this.renderNoRedirects()}
</div>
</umb-body-layout>
`;
label="${this.localize.term('redirectUrls_enableUrlTracker')}"
@click=${this.#onRequestTrackerToggle}>
${this.localize.term('redirectUrls_enableUrlTracker')}
</uui-button>`}
</div>
${this._redirectData?.length
? html`<uui-box id="redirect-wrapper" style="--uui-box-default-padding:0">
${this._trackerEnabled ? '' : html`<div id="grey-out"></div>`} ${this.#renderTable()}
</uui-box>`
: this._filter !== undefined
? this.#renderZeroResults()
: this.#renderNoRedirects()}
${this.#renderPagination()}`;
}
private _renderZeroResults() {
#renderZeroResults() {
return html`<uui-box>
<strong>No redirects matching this search criteria</strong>
<p>Double check your search for any error or spelling mistakes.</p>
</uui-box>`;
}
private renderNoRedirects() {
#renderNoRedirects() {
return html`<uui-box>
<strong>No redirects have been made</strong>
<p>When a published page gets renamed or moved, a redirect will automatically be made to the new page.</p>
<strong>${this.localize.term('redirectUrls_noRedirects')}</strong>
<p>${this.localize.term('redirectUrls_noRedirectsDescription')}</p>
</uui-box>`;
}
private renderTable() {
// TODO: Instead of map, use repeat lit util:
return html`<uui-box style="--uui-box-default-padding: 0;">
<uui-table>
<uui-table-head>
<uui-table-head-cell style="width:10%;">Culture</uui-table-head-cell>
<uui-table-head-cell>Original URL</uui-table-head-cell>
<uui-table-head-cell style="width:10%;"></uui-table-head-cell>
<uui-table-head-cell>Redirected To</uui-table-head-cell>
<uui-table-head-cell style="width:10%;">Actions</uui-table-head-cell>
</uui-table-head>
${this._redirectData?.map((data) => {
return html` <uui-table-row>
<uui-table-cell> ${data.culture || '*'} </uui-table-cell>
<uui-table-cell>
<a href="${data.originalUrl || '#'}" target="_blank"> ${data.originalUrl}</a>
<uui-icon name="umb:out"></uui-icon>
</uui-table-cell>
<uui-table-cell>
<uui-icon name="umb:arrow-right"></uui-icon>
</uui-table-cell>
<uui-table-cell>
<a href="${data.destinationUrl || '#'}" target="_blank"> ${data.destinationUrl}</a>
<uui-icon name="umb:out"></uui-icon>
</uui-table-cell>
<uui-table-cell>
<uui-action-bar style="justify-self: left;">
<uui-button
label="Delete"
look="secondary"
.disabled=${!this._trackerStatus}
@click="${() => this._removeRedirectHandler(data)}">
<uui-icon name="delete"></uui-icon>
</uui-button>
</uui-action-bar>
</uui-table-cell>
</uui-table-row>`;
})}
</uui-table>
</uui-box>
${this._renderPagination()}
</uui-scroll-container
>`;
#renderTable() {
return html`<uui-table>
<uui-table-head>
<uui-table-head-cell style="width:10%;">${this.localize.term('redirectUrls_culture')}</uui-table-head-cell>
<uui-table-head-cell>${this.localize.term('redirectUrls_originalUrl')}</uui-table-head-cell>
<uui-table-head-cell style="width:10%;"></uui-table-head-cell>
<uui-table-head-cell>${this.localize.term('redirectUrls_redirectedTo')}</uui-table-head-cell>
<uui-table-head-cell style="width:10%;">${this.localize.term('general_actions')}</uui-table-head-cell>
</uui-table-head>
${this.#renderTableData()}
</uui-table>`;
}
private _renderPagination() {
#renderTableData() {
return html`${this._redirectData?.map((data) => {
return html` <uui-table-row>
<uui-table-cell> ${data.culture || '*'} </uui-table-cell>
<uui-table-cell>
<a href="${data.originalUrl || '#'}" target="_blank"> ${data.originalUrl}</a>
<uui-icon name="umb:out"></uui-icon>
</uui-table-cell>
<uui-table-cell>
<uui-icon name="umb:arrow-right"></uui-icon>
</uui-table-cell>
<uui-table-cell>
<a href="${data.destinationUrl || '#'}" target="_blank"> ${data.destinationUrl}</a>
<uui-icon name="umb:out"></uui-icon>
</uui-table-cell>
<uui-table-cell>
<uui-action-bar style="justify-self: left;">
<uui-button
label="Delete"
look="secondary"
.disabled=${!this._trackerEnabled}
@click=${() => this.#onRequestDelete(data)}>
<uui-icon name="delete"></uui-icon>
</uui-button>
</uui-action-bar>
</uui-table-cell>
</uui-table-row>`;
})}`;
}
#renderPagination() {
if (!this._total) return nothing;
const totalPages = Math.ceil(this._total / this.itemsPerPage);
@@ -273,7 +269,7 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
if (totalPages <= 1) return nothing;
return html`<div class="pagination">
<uui-pagination .total=${totalPages} @change="${this._onPageChange}"></uui-pagination>
<uui-pagination .total=${totalPages} @change=${this.#onPageChange}></uui-pagination>
</div>`;
}
@@ -281,74 +277,43 @@ export class UmbDashboardRedirectManagementElement extends UmbLitElement {
UmbTextStyles,
css`
:host {
display: block;
height: 100%;
}
#header {
display: flex;
gap: var(--uui-size-space-1);
flex-direction: column;
gap: var(--uui-size-4);
padding: var(--uui-size-layout-1);
}
#redirect-actions {
display: flex;
justify-content: space-between;
width: 100%;
padding: 0 var(--uui-size-layout-1);
}
#header uui-icon {
transform: translateX(50%);
#search-wrapper {
display: flex;
gap: var(--uui-size-4);
}
uui-table {
table-layout: fixed;
#redirect-wrapper {
position: relative;
display: block;
}
#redirect-wrapper #grey-out {
position: absolute;
inset: 0;
background-color: var(--uui-color-surface-alt);
opacity: 0.7;
z-index: 1;
}
uui-table-head-cell:nth-child(2*n) {
width: 10%;
}
uui-table-head-cell:last-child,
uui-table-cell:last-child {
text-align: right;
}
uui-table uui-icon {
vertical-align: sub;
}
uui-pagination {
display: inline-block;
}
.pagination {
display: flex;
justify-content: center;
margin-top: var(--uui-size-space-5);
}
.trackerDisabled {
position: relative;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.trackerDisabled::after {
content: '';
background-color: var(--uui-color-disabled);
position: absolute;
border-radius: 2px;
left: 0;
right: 0;
top: 0;
bottom: 0;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
a {
color: var(--uui-color-interactive);
}
a:hover,
a:focus {
color: var(--uui-color-interactive-emphasis);
}
`,
];
}

View File

@@ -18,7 +18,7 @@ const entityActions: Array<ManifestEntityAction> = [
weight: 900,
meta: {
icon: 'umb:trash',
label: 'Delete (TBD)',
label: 'Delete',
repositoryAlias: DOCUMENT_TYPE_REPOSITORY_ALIAS,
api: UmbDeleteEntityAction,
entityTypes: [entityType],

View File

@@ -1,5 +1,7 @@
import './components/index.js';
export * from './repository/index.js';
export const DOCUMENT_TYPE_ROOT_ENTITY_TYPE = 'document-type-root';
export const DOCUMENT_TYPE_ENTITY_TYPE = 'document-type';
export const DOCUMENT_TYPE_FOLDER_ENTITY_TYPE = 'document-type-folder';

View File

@@ -0,0 +1,38 @@
import { DocumentTypeItemResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbItemStore, UmbStoreBase } from '@umbraco-cms/backoffice/store';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
/**
* @export
* @class UmbDocumentTypeItemStore
* @extends {UmbStoreBase}
* @description - Data Store for Document Type items
*/
export class UmbDocumentTypeItemStore
extends UmbStoreBase<DocumentTypeItemResponseModel>
implements UmbItemStore<DocumentTypeItemResponseModel>
{
/**
* Creates an instance of UmbDocumentTypeItemStore.
* @param {UmbControllerHostElement} host
* @memberof UmbDocumentTypeItemStore
*/
constructor(host: UmbControllerHostElement) {
super(
host,
UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT_TOKEN.toString(),
new UmbArrayState<DocumentTypeItemResponseModel>([], (x) => x.id),
);
}
items(ids: Array<string>) {
return this._data.asObservablePart((items) => items.filter((item) => ids.includes(item.id ?? '')));
}
}
export const UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbDocumentTypeItemStore>(
'UmbDocumentTypeItemStore',
);

View File

@@ -2,6 +2,8 @@ import { UmbDocumentTypeTreeServerDataSource } from './sources/document-type.tre
import { UmbDocumentTypeServerDataSource } from './sources/document-type.server.data.js';
import { UmbDocumentTypeTreeStore, UMB_DOCUMENT_TYPE_TREE_STORE_CONTEXT_TOKEN } from './document-type.tree.store.js';
import { UmbDocumentTypeStore, UMB_DOCUMENT_TYPE_STORE_CONTEXT_TOKEN } from './document-type.store.js';
import { UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT_TOKEN, UmbDocumentTypeItemStore } from './document-type-item.store.js';
import { UmbDocumentTypeItemServerDataSource } from './sources/document-type-item.server.data.js';
import type { UmbTreeDataSource, UmbTreeRepository, UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
@@ -31,6 +33,9 @@ export class UmbDocumentTypeRepository
#detailDataSource: UmbDocumentTypeServerDataSource;
#detailStore?: UmbDocumentTypeStore;
#itemSource: UmbDocumentTypeItemServerDataSource;
#itemStore?: UmbDocumentTypeItemStore;
#notificationContext?: UmbNotificationContext;
constructor(host: UmbControllerHostElement) {
@@ -39,6 +44,7 @@ export class UmbDocumentTypeRepository
// TODO: figure out how spin up get the correct data source
this.#treeSource = new UmbDocumentTypeTreeServerDataSource(this.#host);
this.#detailDataSource = new UmbDocumentTypeServerDataSource(this.#host);
this.#itemSource = new UmbDocumentTypeItemServerDataSource(this.#host);
this.#init = Promise.all([
new UmbContextConsumerController(this.#host, UMB_DOCUMENT_TYPE_TREE_STORE_CONTEXT_TOKEN, (instance) => {
@@ -49,15 +55,17 @@ export class UmbDocumentTypeRepository
this.#detailStore = instance;
}),
new UmbContextConsumerController(this.#host, UMB_DOCUMENT_TYPE_ITEM_STORE_CONTEXT_TOKEN, (instance) => {
this.#itemStore = instance;
}),
new UmbContextConsumerController(this.#host, UMB_NOTIFICATION_CONTEXT_TOKEN, (instance) => {
this.#notificationContext = instance;
}),
]);
}
// TODO: Trash
// TODO: Move
async requestTreeRoot() {
await this.#init;
@@ -97,6 +105,19 @@ export class UmbDocumentTypeRepository
return { data, error, asObservable: () => this.#treeStore!.childrenOf(parentId) };
}
async requestItems(ids: Array<string>) {
if (!ids) throw new Error('Document Type Ids are missing');
await this.#init;
const { data, error } = await this.#itemSource.getItems(ids);
if (data) {
this.#itemStore?.appendItems(data);
}
return { data, error, asObservable: () => this.#itemStore!.items(ids) };
}
async requestItemsLegacy(ids: Array<string>) {
await this.#init;
@@ -168,35 +189,15 @@ export class UmbDocumentTypeRepository
// Could potentially be general methods:
async create(documentType: ItemType) {
if (!documentType || !documentType.id) throw new Error('Template is missing');
if (!documentType || !documentType.id) throw new Error('Document Type is missing');
await this.#init;
const { error, data } = await this.#detailDataSource.insert(documentType);
const { error } = await this.#detailDataSource.insert(documentType);
if (!error && data) {
// TODO: The parts here is a hack, when we can trust the IDs we send, then this should be removed/changed:
const splitResultUrl = data.split('/');
const newId = splitResultUrl[splitResultUrl.length - 1];
// Temporary hack while we are not in control of IDs:
const newDocument = { ...(await this.requestById(newId)).data };
if (newDocument) {
const notification = { data: { message: `Document Type created` } };
this.#notificationContext?.peek('positive', notification);
await this.requestRootTreeItems();
// TODO: currently we cannot put this data into our store, cause we don't have the right ID, as the server currently changes it (and other ids of it, container-id and property-id)
//this.#detailStore?.append(newDocument);
//const treeItem = createTreeItem(newDocument);
//this.#treeStore?.appendItems([treeItem]);
return { data: newDocument };
}
if (!error) {
this.#detailStore?.append(documentType);
const treeItem = createTreeItem(documentType);
this.#treeStore?.appendItems([treeItem]);
}
return { error };
@@ -226,7 +227,6 @@ export class UmbDocumentTypeRepository
}
// General:
async delete(id: string) {
if (!id) throw new Error('Document Type id is missing');
await this.#init;
@@ -240,9 +240,10 @@ export class UmbDocumentTypeRepository
// TODO: we currently don't use the detail store for anything.
// Consider to look up the data before fetching from the server.
// Consider notify a workspace if a template is deleted from the store while someone is editing it.
// TODO: would be nice to align the stores on methods/methodNames.
this.#detailStore?.remove([id]);
this.#treeStore?.removeItem(id);
// TODO: would be nice to align the stores on methods/methodNames.
this.#itemStore?.removeItem(id);
}
return { error };

View File

@@ -10,7 +10,7 @@ import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api
* @extends {UmbStoreBase}
* @description - Data Store for Document Types
*/
export class UmbDocumentTypeStore extends UmbStoreBase {
export class UmbDocumentTypeStore extends UmbStoreBase<DocumentTypeResponseModel> {
/**
* Creates an instance of UmbDocumentTypeStore.
* @param {UmbControllerHostElement} host

View File

@@ -0,0 +1,3 @@
export * from './document-type.repository.js';
export * from './document-type.store.js';
export * from './document-type.tree.store.js';

View File

@@ -1,7 +1,13 @@
import { UmbDocumentTypeItemStore } from './document-type-item.store.js';
import { UmbDocumentTypeRepository } from './document-type.repository.js';
import { UmbDocumentTypeStore } from './document-type.store.js';
import { UmbDocumentTypeTreeStore } from './document-type.tree.store.js';
import { ManifestRepository, ManifestStore, ManifestTreeStore } from '@umbraco-cms/backoffice/extension-registry';
import {
ManifestItemStore,
ManifestRepository,
ManifestStore,
ManifestTreeStore,
} from '@umbraco-cms/backoffice/extension-registry';
export const DOCUMENT_TYPE_REPOSITORY_ALIAS = 'Umb.Repository.DocumentType';
@@ -14,6 +20,7 @@ const repository: ManifestRepository = {
export const DOCUMENT_TYPE_STORE_ALIAS = 'Umb.Store.DocumentType';
export const DOCUMENT_TYPE_TREE_STORE_ALIAS = 'Umb.Store.DocumentTypeTree';
export const DOCUMENT_TYPE_ITEM_STORE_ALIAS = 'Umb.Store.DocumentTypeItem';
const store: ManifestStore = {
type: 'store',
@@ -29,4 +36,11 @@ const treeStore: ManifestTreeStore = {
class: UmbDocumentTypeTreeStore,
};
export const manifests = [repository, store, treeStore];
const itemStore: ManifestItemStore = {
type: 'itemStore',
alias: DOCUMENT_TYPE_ITEM_STORE_ALIAS,
name: 'Document Type Item Store',
class: UmbDocumentTypeItemStore,
};
export const manifests = [repository, store, treeStore, itemStore];

View File

@@ -0,0 +1,39 @@
import type { UmbItemDataSource } from '@umbraco-cms/backoffice/repository';
import { DocumentTypeItemResponseModel, DocumentTypeResource } from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
/**
* A data source for Document Type items that fetches data from the server
* @export
* @class UmbDocumentTypeItemServerDataSource
* @implements {UmbItemDataSource}
*/
export class UmbDocumentTypeItemServerDataSource implements UmbItemDataSource<DocumentTypeItemResponseModel> {
#host: UmbControllerHostElement;
/**
* Creates an instance of UmbDocumentTypeItemServerDataSource.
* @param {UmbControllerHostElement} host
* @memberof UmbDocumentTypeItemServerDataSource
*/
constructor(host: UmbControllerHostElement) {
this.#host = host;
}
/**
* Fetches the items for the given ids from the server
* @param {Array<string>} ids
* @return {*}
* @memberof UmbDocumentTypeItemServerDataSource
*/
async getItems(ids: Array<string>) {
if (!ids) throw new Error('Ids are missing');
return tryExecuteAndNotify(
this.#host,
DocumentTypeResource.getDocumentTypeItem({
id: ids,
}),
);
}
}

View File

@@ -45,7 +45,7 @@ export class UmbDocumentTypeServerDataSource
this.#host,
DocumentTypeResource.getDocumentTypeById({
id: id,
})
}),
);
}
@@ -91,27 +91,12 @@ export class UmbDocumentTypeServerDataSource
* @memberof UmbDocumentTypeServerDataSource
*/
async insert(documentType: CreateDocumentTypeRequestModel) {
if (!documentType) throw new Error('Document is missing');
//if (!document.id) throw new Error('ID is missing');
documentType = { ...documentType };
// TODO: Hack to remove some props that ruins the document-type post end-point.
(documentType as any).id = undefined;
// TODO: Investigate if this matters (should go away anyway when we have the end-point accepts us defining the ids.)
documentType.properties = documentType.properties?.map((prop) => {
return {
...prop,
id: undefined,
};
});
if (!documentType) throw new Error('Document Type is missing');
return tryExecuteAndNotify(
this.#host,
DocumentTypeResource.postDocumentType({
requestBody: documentType,
})
}),
);
}
@@ -164,7 +149,7 @@ export class UmbDocumentTypeServerDataSource
headers: {
'Content-Type': 'application/json',
},
}).then((res) => res.json())
}).then((res) => res.json()),
);
}
}

View File

@@ -43,26 +43,26 @@ export class UmbDocumentTypeWorkspaceContext
this.structure = new UmbContentTypePropertyStructureManager(this.host, this.repository);
// General for content types:
this.data = this.structure.ownerDocumentType;
this.name = this.structure.ownerDocumentTypeObservablePart((data) => data?.name);
this.alias = this.structure.ownerDocumentTypeObservablePart((data) => data?.alias);
this.description = this.structure.ownerDocumentTypeObservablePart((data) => data?.description);
this.icon = this.structure.ownerDocumentTypeObservablePart((data) => data?.icon);
this.allowedAsRoot = this.structure.ownerDocumentTypeObservablePart((data) => data?.allowedAsRoot);
this.variesByCulture = this.structure.ownerDocumentTypeObservablePart((data) => data?.variesByCulture);
this.variesBySegment = this.structure.ownerDocumentTypeObservablePart((data) => data?.variesBySegment);
this.isElement = this.structure.ownerDocumentTypeObservablePart((data) => data?.isElement);
this.allowedContentTypes = this.structure.ownerDocumentTypeObservablePart((data) => data?.allowedContentTypes);
this.compositions = this.structure.ownerDocumentTypeObservablePart((data) => data?.compositions);
this.data = this.structure.ownerContentType;
this.name = this.structure.ownerContentTypeObservablePart((data) => data?.name);
this.alias = this.structure.ownerContentTypeObservablePart((data) => data?.alias);
this.description = this.structure.ownerContentTypeObservablePart((data) => data?.description);
this.icon = this.structure.ownerContentTypeObservablePart((data) => data?.icon);
this.allowedAsRoot = this.structure.ownerContentTypeObservablePart((data) => data?.allowedAsRoot);
this.variesByCulture = this.structure.ownerContentTypeObservablePart((data) => data?.variesByCulture);
this.variesBySegment = this.structure.ownerContentTypeObservablePart((data) => data?.variesBySegment);
this.isElement = this.structure.ownerContentTypeObservablePart((data) => data?.isElement);
this.allowedContentTypes = this.structure.ownerContentTypeObservablePart((data) => data?.allowedContentTypes);
this.compositions = this.structure.ownerContentTypeObservablePart((data) => data?.compositions);
// Document type specific:
this.allowedTemplateIds = this.structure.ownerDocumentTypeObservablePart((data) => data?.allowedTemplateIds);
this.defaultTemplateId = this.structure.ownerDocumentTypeObservablePart((data) => data?.defaultTemplateId);
this.cleanup = this.structure.ownerDocumentTypeObservablePart((data) => data?.defaultTemplateId);
this.allowedTemplateIds = this.structure.ownerContentTypeObservablePart((data) => data?.allowedTemplateIds);
this.defaultTemplateId = this.structure.ownerContentTypeObservablePart((data) => data?.defaultTemplateId);
this.cleanup = this.structure.ownerContentTypeObservablePart((data) => data?.defaultTemplateId);
}
getData() {
return this.structure.getOwnerDocumentType() || {};
return this.structure.getOwnerContentType() || {};
}
getEntityId() {
@@ -74,45 +74,45 @@ export class UmbDocumentTypeWorkspaceContext
}
setName(name: string) {
this.structure.updateOwnerDocumentType({ name });
this.structure.updateOwnerContentType({ name });
}
setAlias(alias: string) {
this.structure.updateOwnerDocumentType({ alias });
this.structure.updateOwnerContentType({ alias });
}
setDescription(description: string) {
this.structure.updateOwnerDocumentType({ description });
this.structure.updateOwnerContentType({ description });
}
// TODO: manage setting icon color alias?
setIcon(icon: string) {
this.structure.updateOwnerDocumentType({ icon });
this.structure.updateOwnerContentType({ icon });
}
setAllowedAsRoot(allowedAsRoot: boolean) {
this.structure.updateOwnerDocumentType({ allowedAsRoot });
this.structure.updateOwnerContentType({ allowedAsRoot });
}
setVariesByCulture(variesByCulture: boolean) {
this.structure.updateOwnerDocumentType({ variesByCulture });
this.structure.updateOwnerContentType({ variesByCulture });
}
setVariesBySegment(variesBySegment: boolean) {
this.structure.updateOwnerDocumentType({ variesBySegment });
this.structure.updateOwnerContentType({ variesBySegment });
}
setIsElement(isElement: boolean) {
this.structure.updateOwnerDocumentType({ isElement });
this.structure.updateOwnerContentType({ isElement });
}
setAllowedContentTypes(allowedContentTypes: Array<ContentTypeSortModel>) {
this.structure.updateOwnerDocumentType({ allowedContentTypes });
this.structure.updateOwnerContentType({ allowedContentTypes });
}
setCompositions(compositions: Array<ContentTypeCompositionModel>) {
this.structure.updateOwnerDocumentType({ compositions });
this.structure.updateOwnerContentType({ compositions });
}
// Document type specific:
setAllowedTemplateIds(allowedTemplateIds: Array<string>) {
this.structure.updateOwnerDocumentType({ allowedTemplateIds });
this.structure.updateOwnerContentType({ allowedTemplateIds });
}
setDefaultTemplateId(defaultTemplateId: string) {
this.structure.updateOwnerDocumentType({ defaultTemplateId });
this.structure.updateOwnerContentType({ defaultTemplateId });
}
async create(parentId: string | null) {
@@ -156,9 +156,10 @@ export class UmbDocumentTypeWorkspaceContext
}
}
export const UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT = new UmbContextToken<UmbSaveableWorkspaceContextInterface, UmbDocumentTypeWorkspaceContext>(
export const UMB_DOCUMENT_TYPE_WORKSPACE_CONTEXT = new UmbContextToken<
UmbSaveableWorkspaceContextInterface,
UmbDocumentTypeWorkspaceContext
>(
'UmbWorkspaceContext',
(context): context is UmbDocumentTypeWorkspaceContext => context.getEntityType?.() === 'document-type'
(context): context is UmbDocumentTypeWorkspaceContext => context.getEntityType?.() === 'document-type',
);

View File

@@ -104,7 +104,14 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle
new UmbModalRouteRegistrationController(this, UMB_PROPERTY_SETTINGS_MODAL)
.addAdditionalPath('new-property')
.onSetup(async () => {
return (await this._propertyStructureHelper.createPropertyScaffold(this._containerId)) ?? false;
const documentTypeId = this._ownerDocumentTypes?.find(
(types) => types.containers?.find((containers) => containers.id === this.containerId),
)?.id;
if(documentTypeId === undefined) return false;
const propertyData = await this._propertyStructureHelper.createPropertyScaffold(this._containerId);
if(propertyData === undefined) return false;
return {propertyData, documentTypeId};
})
.onSubmit((result) => {
this.#addProperty(result);
@@ -116,7 +123,7 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle
connectedCallback(): void {
super.connectedCallback();
const doctypes = this._propertyStructureHelper.getOwnerDocumentTypes();
const doctypes = this._propertyStructureHelper.ownerDocumentTypes;
if (!doctypes) return;
this.observe(
doctypes,

View File

@@ -86,7 +86,11 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement {
this.#modalRegistration = new UmbModalRouteRegistrationController(this, UMB_PROPERTY_SETTINGS_MODAL)
.addUniquePaths(['propertyId'])
.onSetup(() => {
return this.property ?? false;
const documentTypeId = this.ownerDocumentTypeId;
if(documentTypeId === undefined) return false;
const propertyData = this.property;
if(propertyData === undefined) return false;
return {propertyData, documentTypeId};
})
.onSubmit((result) => {
this._partialUpdate(result);

View File

@@ -128,9 +128,13 @@ export class UmbDocumentTypeWorkspaceViewEditElement
#requestRemoveTab(tab: PropertyTypeContainerModelBaseModel | undefined) {
const Message: UmbConfirmModalData = {
headline: 'Delete tab',
content: html`<umb-localize key="contentTypeEditor_confirmDeleteTabMessage" .args=${[tab?.name ?? tab?.id]}>Are you sure you want to delete the tab <strong>${tab?.name ?? tab?.id}</strong></umb-localize>
content: html`<umb-localize key="contentTypeEditor_confirmDeleteTabMessage" .args=${[tab?.name ?? tab?.id]}
>Are you sure you want to delete the tab <strong>${tab?.name ?? tab?.id}</strong></umb-localize
>
<div style="color:var(--uui-color-danger-emphasis)">
<umb-localize key="contentTypeEditor_confirmDeleteTabNotice">This will delete all items that doesn't belong to a composition.</umb-localize>
<umb-localize key="contentTypeEditor_confirmDeleteTabNotice"
>This will delete all items that doesn't belong to a composition.</umb-localize
>
</div>`,
confirmLabel: this.localize.term('actions_delete'),
color: 'danger',
@@ -183,7 +187,7 @@ export class UmbDocumentTypeWorkspaceViewEditElement
(event.target as HTMLInputElement).value = 'Unnamed';
}
const changedName = this._workspaceContext?.structure.makeContainerNameUniqueForOwnerDocument(
const changedName = this._workspaceContext?.structure.makeContainerNameUniqueForOwnerContentType(
newName,
'Tab',
tab.id,
@@ -280,7 +284,7 @@ export class UmbDocumentTypeWorkspaceViewEditElement
</uui-button>
<uui-button label=${this.localize.term('general_reorder')} compact>
<uui-icon name="umb:navigation"></uui-icon>
<umb-localize key="general_reorder">Reorder</umb-localize>
<umb-localize key="general_reorder">Reorder</umb-localize>
</uui-button>
</div>`;
}

View File

@@ -0,0 +1 @@
export * from './manifests.js';

View File

@@ -0,0 +1,20 @@
export const manifests = [
{
type: 'dashboard',
alias: 'Umb.Dashboard.HealthCheck',
name: 'Health Check',
elementName: 'umb-dashboard-health-check',
loader: () => import('./dashboard-health-check.element.js'),
weight: 102,
meta: {
label: 'Health Check',
pathname: 'health-check',
},
conditions: [
{
alias: 'Umb.Condition.SectionAlias',
match: 'Umb.Section.Settings',
},
],
},
];

View File

@@ -0,0 +1,9 @@
export const name = 'Umbraco.Core.HealthCheck';
export const extensions = [
{
name: 'Health Check Bundle',
alias: 'Umb.Bundle.HealthCheck',
type: 'bundle',
loader: () => import('./manifests.js'),
},
];

View File

@@ -1 +0,0 @@
import './input-culture-select/input-culture-select.element.js';

View File

@@ -57,24 +57,6 @@ const dashboards: Array<ManifestDashboard> = [
},
],
},
{
type: 'dashboard',
alias: 'Umb.Dashboard.HealthCheck',
name: 'Health Check',
elementName: 'umb-dashboard-health-check',
loader: () => import('./health-check/dashboard-health-check.element.js'),
weight: 102,
meta: {
label: 'Health Check',
pathname: 'health-check',
},
conditions: [
{
alias: 'Umb.Condition.SectionAlias',
match: sectionAlias,
},
],
},
{
type: 'dashboard',
alias: 'Umb.Dashboard.Profiling',

View File

@@ -1,5 +1,5 @@
import type { UmbInputCultureSelectElement } from '@umbraco-cms/backoffice/culture';
import { UMB_LANGUAGE_WORKSPACE_CONTEXT } from '../../language-workspace.context.js';
import type { UmbInputCultureSelectElement } from '../../../../../cultures/components/input-culture-select/input-culture-select.element.js';
import type { UmbInputLanguagePickerElement } from '../../../../components/input-language-picker/input-language-picker.element.js';
import { UUIBooleanInputEvent, UUIToggleElement } from '@umbraco-cms/backoffice/external/uui';
import { css, html, nothing, customElement, state, ifDefined } from '@umbraco-cms/backoffice/external/lit';
@@ -129,7 +129,7 @@ export class UmbLanguageDetailsWorkspaceViewElement
<!-- TEMP VALIDATION ERROR -->
${this._validationErrors?.isoCode.map(
(isoCodeError) => html`<div class="validation-message">${isoCodeError}</div>`
(isoCodeError) => html`<div class="validation-message">${isoCodeError}</div>`,
)}
</div>
</umb-workspace-property-layout>

View File

@@ -1,4 +1,3 @@
import { manifests as cultureManifests } from './cultures/manifests.js';
import { manifests as dashboardManifests } from './dashboards/manifests.js';
import { manifests as dataTypeManifests } from './data-types/manifests.js';
import { manifests as extensionManifests } from './extensions/manifests.js';
@@ -8,7 +7,6 @@ import { manifests as settingsMenuManifests } from './menu.manifests.js';
import { manifests as settingsSectionManifests } from './section.manifests.js';
export const manifests = [
...cultureManifests,
...dashboardManifests,
...dataTypeManifests,
...extensionManifests,

View File

@@ -2,6 +2,7 @@ import { manifests as menuManifests } from './menu.manifests.js';
import { manifests as templateManifests } from './templates/manifests.js';
import { manifests as stylesheetManifests } from './stylesheets/manifests.js';
import { manifests as partialManifests } from './partial-views/manifests.js';
import { manifests as scriptsManifest } from './scripts/manifests.js';
import { manifests as modalManifests } from './modals/manifests.js';
export const manifests = [
@@ -10,4 +11,5 @@ export const manifests = [
...stylesheetManifests,
...partialManifests,
...modalManifests,
...scriptsManifest,
];

View File

@@ -0,0 +1,34 @@
import { ScriptResponseModel } from '@umbraco-cms/backoffice/backend-api';
export type ScriptDetails = ScriptResponseModel;
//ENTITY TYPES
export const SCRIPTS_ENTITY_TYPE = 'script';
export const SCRIPTS_ROOT_ENTITY_TYPE = 'script-root';
export const SCRIPTS_FOLDER_ENTITY_TYPE = 'script-folder';
export const SCRIPTS_FOLDER_EMPTY_ENTITY_TYPE = 'script-folder-empty';
export const SCRIPTS_STORE_ALIAS = 'Umb.Store.Scripts';
export const UMB_SCRIPTS_STORE_CONTEXT_TOKEN_ALIAS = 'Umb.Store.Scripts.Context.Token';
export const SCRIPTS_REPOSITORY_ALIAS = 'Umb.Repository.Scripts';
export const SCRIPTS_MENU_ITEM_ALIAS = 'Umb.MenuItem.Scripts';
//TREE
export const SCRIPTS_TREE_ALIAS = 'Umb.Tree.Scripts';
export const SCRIPTS_TREE_ITEM_ALIAS = 'Umb.TreeItem.Scripts';
export const SCRIPTS_TREE_STORE_ALIAS = 'Umb.Store.Scripts.Tree';
export const UMB_SCRIPTS_TREE_STORE_CONTEXT_TOKEN_ALIAS = 'Umb.Store.Scripts.Tree.Context.Token';
//ENTITY (tree) ACTIONS
export const SCRIPTS_ENTITY_ACTION_DELETE_ALIAS = 'Umb.EntityAction.Scripts.Delete';
export const SCRIPTS_ENTITY_ACTION_CREATE_NEW_ALIAS = 'Umb.EntityAction.ScriptsFolder.Create.New';
export const SCRIPTS_ENTITY_ACTION_DELETE_FOLDER_ALIAS = 'Umb.EntityAction.ScriptsFolder.DeleteFolder';
export const SCRIPTS_ENTITY_ACTION_CREATE_FOLDER_NEW_ALIAS = 'Umb.EntityAction.ScriptsFolder.CreateFolder';
//WORKSPACE
export const SCRIPTS_WORKSPACE_ALIAS = 'Umb.Workspace.Scripts';
export const SCRIPTS_WORKSPACE_ACTION_SAVE_ALIAS = 'Umb.WorkspaceAction.Scripts.Save';

View File

@@ -0,0 +1,12 @@
import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export class UmbCreateScriptAction<T extends { copy(): Promise<void> }> extends UmbEntityActionBase<T> {
constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string) {
super(host, repositoryAlias, unique);
}
async execute() {
history.pushState(null, '', `section/settings/workspace/script/create/${this.unique ?? 'null'}`);
}
}

View File

@@ -0,0 +1,74 @@
import {
SCRIPTS_REPOSITORY_ALIAS,
SCRIPTS_ENTITY_TYPE,
SCRIPTS_FOLDER_ENTITY_TYPE,
SCRIPTS_ROOT_ENTITY_TYPE,
SCRIPTS_FOLDER_EMPTY_ENTITY_TYPE,
SCRIPTS_ENTITY_ACTION_DELETE_ALIAS,
SCRIPTS_ENTITY_ACTION_CREATE_NEW_ALIAS,
SCRIPTS_ENTITY_ACTION_DELETE_FOLDER_ALIAS,
SCRIPTS_ENTITY_ACTION_CREATE_FOLDER_NEW_ALIAS,
} from '../config.js';
import { UmbCreateScriptAction } from './create/create-empty.action.js';
import {
UmbCreateFolderEntityAction,
UmbDeleteEntityAction,
UmbDeleteFolderEntityAction,
} from '@umbraco-cms/backoffice/entity-action';
import { ManifestEntityAction } from '@umbraco-cms/backoffice/extension-registry';
const scriptsViewActions: Array<ManifestEntityAction> = [
{
type: 'entityAction',
alias: SCRIPTS_ENTITY_ACTION_DELETE_ALIAS,
name: 'Delete Scripts Entity Action',
meta: {
icon: 'umb:trash',
label: 'Delete',
api: UmbDeleteEntityAction,
repositoryAlias: SCRIPTS_REPOSITORY_ALIAS,
entityTypes: [SCRIPTS_ENTITY_TYPE],
},
},
];
const scriptsFolderActions: Array<ManifestEntityAction> = [
{
type: 'entityAction',
alias: SCRIPTS_ENTITY_ACTION_CREATE_NEW_ALIAS,
name: 'Create Scripts Entity Under Directory Action',
meta: {
icon: 'umb:article',
label: 'New empty script',
api: UmbCreateScriptAction,
repositoryAlias: SCRIPTS_REPOSITORY_ALIAS,
entityTypes: [SCRIPTS_FOLDER_ENTITY_TYPE, SCRIPTS_FOLDER_EMPTY_ENTITY_TYPE, SCRIPTS_ROOT_ENTITY_TYPE],
},
},
{
type: 'entityAction',
alias: SCRIPTS_ENTITY_ACTION_DELETE_FOLDER_ALIAS,
name: 'Remove empty folder',
meta: {
icon: 'umb:trash',
label: 'Remove folder',
api: UmbDeleteFolderEntityAction,
repositoryAlias: SCRIPTS_REPOSITORY_ALIAS,
entityTypes: [SCRIPTS_FOLDER_EMPTY_ENTITY_TYPE],
},
},
{
type: 'entityAction',
alias: SCRIPTS_ENTITY_ACTION_CREATE_FOLDER_NEW_ALIAS,
name: 'Create empty folder',
meta: {
icon: 'umb:add',
label: 'Create folder',
api: UmbCreateFolderEntityAction,
repositoryAlias: SCRIPTS_REPOSITORY_ALIAS,
entityTypes: [SCRIPTS_FOLDER_EMPTY_ENTITY_TYPE, SCRIPTS_FOLDER_ENTITY_TYPE, SCRIPTS_ROOT_ENTITY_TYPE],
},
},
];
export const manifests = [...scriptsViewActions, ...scriptsFolderActions];

View File

@@ -0,0 +1,2 @@
export * from './repository/index.js';
export * from './config.js';

View File

@@ -0,0 +1,13 @@
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as menuItemManifests } from './menu-item/manifests.js';
import { manifests as treeManifests } from './tree/manifests.js';
import { manifests as entityActionsManifests } from './entity-actions/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
export const manifests = [
...repositoryManifests,
...menuItemManifests,
...treeManifests,
...entityActionsManifests,
...workspaceManifests,
];

View File

@@ -0,0 +1,19 @@
import { SCRIPTS_ENTITY_TYPE, SCRIPTS_MENU_ITEM_ALIAS, SCRIPTS_TREE_ALIAS } from '../config.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
const menuItem: ManifestTypes = {
type: 'menuItem',
kind: 'tree',
alias: SCRIPTS_MENU_ITEM_ALIAS,
name: 'Scripts Menu Item',
weight: 10,
meta: {
label: 'Scripts',
icon: 'umb:folder',
entityType: SCRIPTS_ENTITY_TYPE,
treeAlias: SCRIPTS_TREE_ALIAS,
menus: ['Umb.Menu.Templating'],
},
};
export const manifests = [menuItem];

View File

@@ -0,0 +1 @@
export * from './scripts.repository.js';

View File

@@ -0,0 +1,28 @@
import { SCRIPTS_REPOSITORY_ALIAS, SCRIPTS_STORE_ALIAS, SCRIPTS_TREE_STORE_ALIAS } from '../config.js';
import { UmbScriptsRepository } from './scripts.repository.js';
import { UmbScriptsStore } from './scripts.store.js';
import { UmbScriptsTreeStore } from './scripts.tree.store.js';
import { ManifestRepository, ManifestStore, ManifestTreeStore } from '@umbraco-cms/backoffice/extension-registry';
const repository: ManifestRepository = {
type: 'repository',
alias: SCRIPTS_REPOSITORY_ALIAS,
name: 'Scripts Repository',
class: UmbScriptsRepository,
};
const store: ManifestStore = {
type: 'store',
alias: SCRIPTS_STORE_ALIAS,
name: 'Scripts Store',
class: UmbScriptsStore,
};
const treeStore: ManifestTreeStore = {
type: 'treeStore',
alias: SCRIPTS_TREE_STORE_ALIAS,
name: 'Scripts Tree Store',
class: UmbScriptsTreeStore,
};
export const manifests = [repository, store, treeStore];

View File

@@ -0,0 +1,216 @@
import { SCRIPTS_ROOT_ENTITY_TYPE } from '../config.js';
import { UmbScriptsTreeServerDataSource } from './sources/scripts.tree.server.data.js';
import { UmbScriptsServerDataSource } from './sources/scripts.detail.server.data.js';
import { ScriptsGetFolderResponse, UmbScriptsFolderServerDataSource } from './sources/scripts.folder.server.data.js';
import { UMB_SCRIPTS_TREE_STORE_CONTEXT_TOKEN, UmbScriptsTreeStore } from './scripts.tree.store.js';
import {
DataSourceResponse,
UmbDataSourceErrorResponse,
UmbDetailRepository,
UmbFolderRepository,
UmbTreeRepository,
} from '@umbraco-cms/backoffice/repository';
import {
CreateFolderRequestModel,
CreateScriptRequestModel,
FileItemResponseModelBaseModel,
FileSystemTreeItemPresentationModel,
FolderModelBaseModel,
FolderResponseModel,
ProblemDetails,
ScriptResponseModel,
TextFileResponseModelBaseModel,
UpdateScriptRequestModel,
} from '@umbraco-cms/backoffice/backend-api';
import { UmbContextConsumerController } from '@umbraco-cms/backoffice/context-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { Observable } from '@umbraco-cms/backoffice/external/rxjs';
export class UmbScriptsRepository
implements
UmbTreeRepository<FileSystemTreeItemPresentationModel>,
UmbDetailRepository<CreateScriptRequestModel, string, UpdateScriptRequestModel, ScriptResponseModel, string>,
UmbFolderRepository
{
#init;
#host: UmbControllerHostElement;
#treeDataSource: UmbScriptsTreeServerDataSource;
#detailDataSource: UmbScriptsServerDataSource;
#folderDataSource: UmbScriptsFolderServerDataSource;
#treeStore?: UmbScriptsTreeStore;
constructor(host: UmbControllerHostElement) {
this.#host = host;
this.#treeDataSource = new UmbScriptsTreeServerDataSource(this.#host);
this.#detailDataSource = new UmbScriptsServerDataSource(this.#host);
this.#folderDataSource = new UmbScriptsFolderServerDataSource(this.#host);
this.#init = Promise.all([
new UmbContextConsumerController(this.#host, UMB_SCRIPTS_TREE_STORE_CONTEXT_TOKEN, (instance) => {
this.#treeStore = instance;
}),
]);
}
//#region FOLDER
createFolderScaffold(
parentId: string | null,
): Promise<{ data?: FolderResponseModel | undefined; error?: ProblemDetails | undefined }> {
const data: FolderResponseModel = {
name: '',
parentId,
};
return Promise.resolve({ data, error: undefined });
}
async createFolder(
requestBody: CreateFolderRequestModel,
): Promise<{ data?: string | undefined; error?: ProblemDetails | undefined }> {
await this.#init;
const req = {
parentPath: requestBody.parentId,
name: requestBody.name,
};
const promise = this.#folderDataSource.insert(req);
await promise;
this.requestTreeItemsOf(requestBody.parentId ? requestBody.parentId : null);
return promise;
}
async requestFolder(
unique: string,
): Promise<{ data?: ScriptsGetFolderResponse | undefined; error?: ProblemDetails | undefined }> {
await this.#init;
return this.#folderDataSource.get(unique);
}
updateFolder(
unique: string,
folder: FolderModelBaseModel,
): Promise<{ data?: FolderModelBaseModel | undefined; error?: ProblemDetails | undefined }> {
throw new Error('Method not implemented.');
}
async deleteFolder(path: string): Promise<{ error?: ProblemDetails | undefined }> {
await this.#init;
const { data } = await this.requestFolder(path);
const promise = this.#folderDataSource.delete(path);
await promise;
this.requestTreeItemsOf(data?.parentPath ? data?.parentPath : null);
return promise;
}
//#endregion
//#region TREE
async requestTreeRoot() {
await this.#init;
const data = {
id: null,
path: null,
type: SCRIPTS_ROOT_ENTITY_TYPE,
name: 'Scripts',
icon: 'umb:folder',
hasChildren: true,
};
return { data };
}
async requestRootTreeItems() {
await this.#init;
const { data, error } = await this.#treeDataSource.getRootItems();
if (data) {
this.#treeStore?.appendItems(data.items);
}
return { data, error, asObservable: () => this.#treeStore!.rootItems };
}
async requestTreeItemsOf(path: string | null) {
if (path === null || path === '/' || path === '') {
return this.requestRootTreeItems();
}
await this.#init;
const response = await this.#treeDataSource.getChildrenOf({ path, skip: 0, take: 100 });
const { data, error } = response;
if (data) {
this.#treeStore!.appendItems(data.items);
}
return { data, error, asObservable: () => this.#treeStore!.childrenOf(path) };
}
async requestTreeItems(keys: Array<string>) {
await this.#init;
if (!keys) {
const error: ProblemDetails = { title: 'Keys are missing' };
return { data: undefined, error };
}
const { data, error } = await this.#treeDataSource.getItem(keys);
return { data, error, asObservable: () => this.#treeStore!.items(keys) };
}
async rootTreeItems() {
await this.#init;
return this.#treeStore!.rootItems;
}
async treeItemsOf(parentPath: string | null) {
if (!parentPath) throw new Error('Parent Path is missing');
await this.#init;
return this.#treeStore!.childrenOf(parentPath);
}
async treeItems(paths: Array<string>) {
if (!paths) throw new Error('Paths are missing');
await this.#init;
return this.#treeStore!.items(paths);
}
//#endregion
//#region DETAILS
async requestByKey(path: string) {
if (!path) throw new Error('Path is missing');
await this.#init;
const { data, error } = await this.#detailDataSource.get(path);
return { data, error };
}
requestById(id: string): Promise<DataSourceResponse<any>> {
throw new Error('Method not implemented.');
}
byId(id: string): Promise<Observable<any>> {
throw new Error('Method not implemented.');
}
createScaffold(parentId: string | null, preset: string): Promise<DataSourceResponse<TextFileResponseModelBaseModel>> {
return this.#detailDataSource.createScaffold(parentId, preset);
}
async create(data: CreateScriptRequestModel): Promise<DataSourceResponse<any>> {
const promise = this.#detailDataSource.insert(data);
await promise;
this.requestTreeItemsOf(data.parentPath ? data.parentPath : null);
return promise;
}
save(id: string, requestBody: UpdateScriptRequestModel): Promise<UmbDataSourceErrorResponse> {
return this.#detailDataSource.update(id, requestBody);
}
async delete(id: string): Promise<UmbDataSourceErrorResponse> {
const promise = this.#detailDataSource.delete(id);
const parentPath = id.substring(0, id.lastIndexOf('/'));
this.requestTreeItemsOf(parentPath ? parentPath : null);
return promise;
}
requestItems(keys: Array<string>): Promise<DataSourceResponse<FileItemResponseModelBaseModel[]>> {
return this.#detailDataSource.getItems(keys);
}
//#endregion
}

View File

@@ -0,0 +1,45 @@
import { UMB_SCRIPTS_STORE_CONTEXT_TOKEN_ALIAS } from '../config.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbArrayState } from '@umbraco-cms/backoffice/observable-api';
import { UmbStoreBase } from '@umbraco-cms/backoffice/store';
import type { TemplateResponseModel } from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
/**
* @export
* @class UmbScriptsStore
* @extends {UmbStoreBase}
* @description - Data Store for scripts
*/
export class UmbScriptsStore extends UmbStoreBase {
/**
* Creates an instance of UmbScriptsStore.
* @param {UmbControllerHostInterface} host
* @memberof UmbScriptsStore
*/
constructor(host: UmbControllerHostElement) {
super(host, UMB_SCRIPTS_STORE_CONTEXT_TOKEN.toString(), new UmbArrayState<TemplateResponseModel>([], (x) => x.id));
}
/**
* Append a script to the store
* @param {Template} template
* @memberof UmbScriptsStore
*/
append(template: TemplateResponseModel) {
this._data.append([template]);
}
/**
* Removes scripts in the store with the given uniques
* @param {string[]} uniques
* @memberof UmbScriptsStore
*/
remove(uniques: string[]) {
this._data.remove(uniques);
}
}
export const UMB_SCRIPTS_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbScriptsStore>(
UMB_SCRIPTS_STORE_CONTEXT_TOKEN_ALIAS,
);

View File

@@ -0,0 +1,26 @@
import { UMB_SCRIPTS_TREE_STORE_CONTEXT_TOKEN_ALIAS } from '../config.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbFileSystemTreeStore } from '@umbraco-cms/backoffice/store';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export const UMB_SCRIPTS_TREE_STORE_CONTEXT_TOKEN = new UmbContextToken<UmbScriptsTreeStore>(
UMB_SCRIPTS_TREE_STORE_CONTEXT_TOKEN_ALIAS,
);
/**
* Tree Store for scripts
*
* @export
* @class
* @extends {UmbEntityTreeStore}
*/
export class UmbScriptsTreeStore extends UmbFileSystemTreeStore {
/**
* Creates an instance of UmbScriptsTreeStore.
* @param {UmbControllerHostInterface} host
* @memberof UmbScriptsTreeStore
*/
constructor(host: UmbControllerHostElement) {
super(host, UMB_SCRIPTS_TREE_STORE_CONTEXT_TOKEN.toString());
}
}

View File

@@ -0,0 +1,19 @@
import {
FileSystemTreeItemPresentationModel,
PagedFileSystemTreeItemPresentationModel,
} from '@umbraco-cms/backoffice/backend-api';
import type { DataSourceResponse } from '@umbraco-cms/backoffice/repository';
export interface ScriptsTreeDataSource {
getRootItems(): Promise<DataSourceResponse<PagedFileSystemTreeItemPresentationModel>>;
getChildrenOf({
path,
skip,
take,
}: {
path?: string | undefined;
skip?: number | undefined;
take?: number | undefined;
}): Promise<DataSourceResponse<PagedFileSystemTreeItemPresentationModel>>;
getItem(ids: Array<string>): Promise<DataSourceResponse<FileSystemTreeItemPresentationModel[]>>;
}

View File

@@ -0,0 +1,75 @@
import {
CreateScriptRequestModel,
CreateTextFileViewModelBaseModel,
ScriptItemResponseModel,
ScriptResource,
ScriptResponseModel,
UpdateScriptRequestModel,
} from '@umbraco-cms/backoffice/backend-api';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { DataSourceResponse, UmbDataSource } from '@umbraco-cms/backoffice/repository';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
export class UmbScriptsServerDataSource
implements UmbDataSource<CreateScriptRequestModel, string, UpdateScriptRequestModel, ScriptResponseModel, string>
{
#host: UmbControllerHostElement;
constructor(host: UmbControllerHostElement) {
this.#host = host;
}
createScaffold(
parentId: string | null,
preset?: string | Partial<CreateTextFileViewModelBaseModel> | undefined,
): Promise<DataSourceResponse<CreateTextFileViewModelBaseModel>> {
throw new Error('Method not implemented.');
}
/**
* Fetches a script with the given path from the server
* @param {string} path
* @return {*}
* @memberof UmbScriptsDetailServerDataSource
*/
get(path: string): Promise<DataSourceResponse<ScriptResponseModel>> {
if (!path) throw new Error('Path is missing');
return tryExecuteAndNotify(this.#host, ScriptResource.getScript({ path }));
}
/**
* Creates a new script
*
* @param {CreateScriptRequestModel} requestBody
* @return {*} {Promise<DataSourceResponse<string>>}
* @memberof UmbScriptsDetailServerDataSource
*/
insert(requestBody: CreateScriptRequestModel): Promise<DataSourceResponse<string>> {
return tryExecuteAndNotify(this.#host, ScriptResource.postScript({ requestBody }));
}
//TODO the parameters here are bit ugly, since unique is already in the request body parameter, but it has to be done to marry the UmbDataSource interface an backend API together... maybe come up with some nicer solution
/**
* Updates a script
*
* @param {string} [unique='']
* @param {UpdateScriptRequestModel} requestBody
* @return {*} {Promise<DataSourceResponse<any>>}
* @memberof UmbScriptsDetailServerDataSource
*/
update(unique = '', requestBody: UpdateScriptRequestModel): Promise<DataSourceResponse<any>> {
return tryExecuteAndNotify(this.#host, ScriptResource.putScript({ requestBody }));
}
/**
* Deletes a script
*
* @param {string} path
* @return {*} {Promise<DataSourceResponse>}
* @memberof UmbScriptsDetailServerDataSource
*/
delete(path: string): Promise<DataSourceResponse> {
return tryExecuteAndNotify(this.#host, ScriptResource.deleteScript({ path }));
}
getItems(keys: Array<string>): Promise<DataSourceResponse<ScriptItemResponseModel[]>> {
return tryExecuteAndNotify(this.#host, ScriptResource.getScriptItem({ path: keys }));
}
}

View File

@@ -0,0 +1,35 @@
import {
CreateFolderRequestModel,
FolderModelBaseModel,
FolderResponseModel,
ScriptResource,
} from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { DataSourceResponse, UmbFolderDataSource } from '@umbraco-cms/backoffice/repository';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
//! this is of any type in the backend-api
export type ScriptsGetFolderResponse = { path: string; parentPath: string; name: string };
export class UmbScriptsFolderServerDataSource implements UmbFolderDataSource {
#host: UmbControllerHostElement;
constructor(host: UmbControllerHostElement) {
this.#host = host;
}
createScaffold(parentId: string | null): Promise<DataSourceResponse<FolderResponseModel>> {
throw new Error('Method not implemented.');
}
get(unique: string): Promise<DataSourceResponse<ScriptsGetFolderResponse>> {
return tryExecuteAndNotify(this.#host, ScriptResource.getScriptFolder({ path: unique }));
}
insert(requestBody: CreateFolderRequestModel): Promise<DataSourceResponse<string>> {
return tryExecuteAndNotify(this.#host, ScriptResource.postScriptFolder({ requestBody }));
}
update(unique: string, data: CreateFolderRequestModel): Promise<DataSourceResponse<FolderModelBaseModel>> {
throw new Error('Method not implemented.');
}
delete(path: string): Promise<DataSourceResponse<unknown>> {
return tryExecuteAndNotify(this.#host, ScriptResource.deleteScriptFolder({ path }));
}
}

View File

@@ -0,0 +1,54 @@
import { ScriptsTreeDataSource } from './index.js';
import { ScriptResource, ProblemDetails } from '@umbraco-cms/backoffice/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
export class UmbScriptsTreeServerDataSource implements ScriptsTreeDataSource {
#host: UmbControllerHostElement;
constructor(host: UmbControllerHostElement) {
this.#host = host;
}
async getRootItems() {
return tryExecuteAndNotify(this.#host, ScriptResource.getTreeScriptRoot({}));
}
async getChildrenOf({
path,
skip,
take,
}: {
path?: string | undefined;
skip?: number | undefined;
take?: number | undefined;
}) {
if (!path) {
const error: ProblemDetails = { title: 'Path is missing' };
return error;
}
return tryExecuteAndNotify(
this.#host,
ScriptResource.getTreeScriptChildren({
path,
skip,
take,
}),
);
}
async getItem(path: Array<string>) {
if (!path) {
const error: ProblemDetails = { title: 'Paths are missing' };
return error;
}
return tryExecuteAndNotify(
this.#host,
ScriptResource.getScriptItem({
path,
}),
);
}
}

View File

@@ -0,0 +1,30 @@
import {
SCRIPTS_ENTITY_TYPE,
SCRIPTS_REPOSITORY_ALIAS,
SCRIPTS_ROOT_ENTITY_TYPE,
SCRIPTS_TREE_ALIAS,
SCRIPTS_TREE_ITEM_ALIAS,
} from '../config.js';
import type { ManifestTree, ManifestTreeItem } from '@umbraco-cms/backoffice/extension-registry';
const tree: ManifestTree = {
type: 'tree',
alias: SCRIPTS_TREE_ALIAS,
name: 'Scripts Tree',
weight: 30,
meta: {
repositoryAlias: SCRIPTS_REPOSITORY_ALIAS,
},
};
const treeItem: ManifestTreeItem = {
type: 'treeItem',
kind: 'fileSystem',
alias: SCRIPTS_TREE_ITEM_ALIAS,
name: 'Scripts Tree Item',
meta: {
entityTypes: [SCRIPTS_ROOT_ENTITY_TYPE, SCRIPTS_ENTITY_TYPE],
},
};
export const manifests = [tree, treeItem];

View File

@@ -0,0 +1,36 @@
import { SCRIPTS_ENTITY_TYPE, SCRIPTS_WORKSPACE_ACTION_SAVE_ALIAS, SCRIPTS_WORKSPACE_ALIAS } from '../config.js';
import { UmbSaveWorkspaceAction } from '@umbraco-cms/backoffice/workspace';
import type { ManifestWorkspace, ManifestWorkspaceAction } from '@umbraco-cms/backoffice/extension-registry';
const workspace: ManifestWorkspace = {
type: 'workspace',
alias: SCRIPTS_WORKSPACE_ALIAS,
name: 'Scripts Workspace',
loader: () => import('./scripts-workspace.element.js'),
meta: {
entityType: SCRIPTS_ENTITY_TYPE,
},
};
//TODO: this does not work for some reason
const workspaceActions: Array<ManifestWorkspaceAction> = [
{
type: 'workspaceAction',
alias: 'Umb.WorkspaceAction.Scripts.Save',
name: 'Save Script Workspace Action',
meta: {
label: 'Save',
look: 'primary',
color: 'positive',
api: UmbSaveWorkspaceAction,
},
conditions: [
{
alias: 'Umb.Condition.WorkspaceAlias',
match: workspace.alias,
},
],
},
];
export const manifests = [workspace, ...workspaceActions];

View File

@@ -0,0 +1,185 @@
import { UmbScriptsWorkspaceContext } from './scripts-workspace.context.js';
import type { UmbCodeEditorElement } from '@umbraco-cms/backoffice/code-editor';
import { UUITextStyles, UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
import { css, html, customElement, query, state, PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { UMB_MODAL_MANAGER_CONTEXT_TOKEN, UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { Subject, debounceTime } from '@umbraco-cms/backoffice/external/rxjs';
import { UMB_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/workspace';
import _ from 'lodash';
@customElement('umb-scripts-workspace-edit')
export class UmbScriptsWorkspaceEditElement extends UmbLitElement {
#name: string | undefined = '';
@state()
private get _name() {
return this.#name;
}
private set _name(value) {
this.#name = value?.replace('.js', '');
this.requestUpdate();
}
@state()
private _content?: string | null = '';
@state()
private _path?: string | null = '';
@state()
private _dirName?: string | null = '';
@state()
private _ready?: boolean = false;
@query('umb-code-editor')
private _codeEditor?: UmbCodeEditorElement;
#scriptsWorkspaceContext?: UmbScriptsWorkspaceContext;
private _modalContext?: UmbModalManagerContext;
#isNew = false;
private inputQuery$ = new Subject<string>();
constructor() {
super();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT_TOKEN, (instance) => {
this._modalContext = instance;
});
this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => {
this.#scriptsWorkspaceContext = workspaceContext as UmbScriptsWorkspaceContext;
this.observe(this.#scriptsWorkspaceContext.name, (name) => {
this._name = name;
});
this.observe(this.#scriptsWorkspaceContext.content, (content) => {
this._content = content;
});
this.observe(this.#scriptsWorkspaceContext.path, (path) => {
this._path = path;
});
this.observe(this.#scriptsWorkspaceContext.isNew, (isNew) => {
this.#isNew = !!isNew;
});
this.observe(this.#scriptsWorkspaceContext.isCodeEditorReady, (isReady) => {
this._ready = isReady;
});
this.inputQuery$.pipe(debounceTime(250)).subscribe((nameInputValue: string) => {
this.#scriptsWorkspaceContext?.setName(`${nameInputValue}.js`);
});
});
}
protected willUpdate(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
if (_changedProperties.has('_path')) {
this._dirName = this._path?.substring(0, this._path?.lastIndexOf('/'));
}
}
#onNameInput(event: Event) {
const target = event.target as UUIInputElement;
const value = target.value as string;
this.inputQuery$.next(value);
}
#onCodeEditorInput(event: Event) {
const target = event.target as UmbCodeEditorElement;
const value = target.code as string;
this.#scriptsWorkspaceContext?.setContent(value);
}
#renderCodeEditor() {
return html`<umb-code-editor
language="javascript"
id="content"
.code=${this._content ?? ''}
@input=${this.#onCodeEditorInput}></umb-code-editor>`;
}
render() {
return html`<umb-workspace-editor alias="Umb.Workspace.Scripts">
<div id="workspace-header" slot="header">
<uui-input
placeholder="Enter name..."
.value=${this._name}
@input=${this.#onNameInput}
label="template name"></uui-input>
<small>Scripts/${this._dirName}${this._name}.js</small>
</div>
<uui-box>
<!-- the div below in the header is to make the box display nicely with code editor -->
<div slot="header"></div>
${this._ready
? this.#renderCodeEditor()
: html`<div id="loader-container">
<uui-loader></uui-loader>
</div>`}
</uui-box>
<div slot="footer-info">
<!-- TODO: Shortcuts Modal? -->
<uui-button label="Show keyboard shortcuts">
Keyboard Shortcuts
<uui-keyboard-shortcut>
<uui-key>ALT</uui-key>
+
<uui-key>shift</uui-key>
+
<uui-key>k</uui-key>
</uui-keyboard-shortcut>
</uui-button>
</div>
</umb-workspace-editor>`;
}
static styles = [
UUITextStyles,
css`
:host {
display: block;
width: 100%;
}
#loader-container {
display: grid;
place-items: center;
min-height: calc(100dvh - 260px);
}
umb-code-editor {
--editor-height: calc(100dvh - 260px);
}
uui-box {
min-height: calc(100dvh - 260px);
margin: var(--uui-size-layout-1);
--uui-box-default-padding: 0;
/* remove header border bottom as code editor looks better in this box */
--uui-color-divider-standalone: transparent;
}
#workspace-header {
width: 100%;
}
uui-input {
width: 100%;
}
`,
];
}
export default UmbScriptsWorkspaceEditElement;
declare global {
interface HTMLElementTagNameMap {
'umb-scripts-workspace-edit': UmbScriptsWorkspaceEditElement;
}
}

View File

@@ -0,0 +1,100 @@
import { ScriptDetails, SCRIPTS_WORKSPACE_ALIAS } from '../config.js';
import { UmbScriptsRepository } from '../repository/scripts.repository.js';
import { createObservablePart, UmbBooleanState, UmbDeepState } from '@umbraco-cms/backoffice/observable-api';
import { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbWorkspaceContext } from '@umbraco-cms/backoffice/workspace';
import { loadCodeEditor } from '@umbraco-cms/backoffice/code-editor';
import { TextFileResponseModelBaseModel, UpdateScriptRequestModel } from '@umbraco-cms/backoffice/backend-api';
export class UmbScriptsWorkspaceContext extends UmbWorkspaceContext<UmbScriptsRepository, ScriptDetails> {
#data = new UmbDeepState<ScriptDetails | undefined>(undefined);
data = this.#data.asObservable();
name = createObservablePart(this.#data, (data) => data?.name);
content = createObservablePart(this.#data, (data) => data?.content);
path = createObservablePart(this.#data, (data) => data?.path);
#isCodeEditorReady = new UmbBooleanState(false);
isCodeEditorReady = this.#isCodeEditorReady.asObservable();
constructor(host: UmbControllerHostElement) {
super(host, SCRIPTS_WORKSPACE_ALIAS, new UmbScriptsRepository(host));
this.#loadCodeEditor();
}
async #loadCodeEditor() {
try {
await loadCodeEditor();
this.#isCodeEditorReady.next(true);
} catch (error) {
console.error(error);
}
}
getData() {
return this.#data.getValue();
}
setName(value: string) {
this.#data.next({ ...this.#data.value, name: value });
}
setContent(value: string) {
this.#data.next({ ...this.#data.value, content: value });
}
async load(entityKey: string) {
const { data } = await this.repository.requestByKey(entityKey);
if (data) {
this.setIsNew(false);
this.#data.next(data);
}
}
async create(parentKey: string) {
const newScript: TextFileResponseModelBaseModel = {
name: '',
path: parentKey,
content: '',
};
this.#data.next(newScript);
this.setIsNew(true);
}
getEntityId(): string | undefined {
return this.getData()?.path;
}
public async save() {
const script = this.getData();
if (!script) {
return Promise.reject('Something went wrong, there is no data for script you want to save...');
}
if (this.getIsNew()) {
const createRequestBody = {
name: script.name,
content: script.content,
parentPath: script.path + '/',
};
this.repository.create(createRequestBody);
return Promise.resolve();
}
if (!script.path) return Promise.reject('There is no path');
const updateRequestBody: UpdateScriptRequestModel = {
name: script.name,
existingPath: script.path,
content: script.content,
};
this.repository.save(script.path, updateRequestBody);
return Promise.resolve();
}
destroy(): void {
throw new Error('Method not implemented.');
}
getEntityType(): string {
throw new Error('Method not implemented.');
}
}

View File

@@ -0,0 +1,52 @@
import { UmbScriptsWorkspaceContext } from './scripts-workspace.context.js';
import { UUITextStyles } from '@umbraco-cms/backoffice/external/uui';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { UmbRoute, IRoutingInfo, PageComponent } from '@umbraco-cms/backoffice/router';
import { UmbWorkspaceIsNewRedirectController } from '@umbraco-cms/backoffice/workspace';
@customElement('umb-scripts-workspace')
export class UmbScriptsWorkspaceElement extends UmbLitElement {
#scriptsWorkspaceContext = new UmbScriptsWorkspaceContext(this);
@state()
_routes: UmbRoute[] = [
{
path: 'create/:parentKey',
component: import('./scripts-workspace-edit.element.js'),
setup: async (component: PageComponent, info: IRoutingInfo) => {
const parentKey = info.match.params.parentKey;
const decodePath = decodeURIComponent(parentKey);
this.#scriptsWorkspaceContext.create(decodePath === 'null' ? '' : decodePath);
new UmbWorkspaceIsNewRedirectController(
this,
this.#scriptsWorkspaceContext,
this.shadowRoot!.querySelector('umb-router-slot')!,
);
},
},
{
path: 'edit/:key',
component: import('./scripts-workspace-edit.element.js'),
setup: (component: PageComponent, info: IRoutingInfo) => {
const key = info.match.params.key;
const decodePath = decodeURIComponent(key).replace('-js', '.js');
this.#scriptsWorkspaceContext.load(decodePath);
},
},
];
render() {
return html`<umb-router-slot .routes=${this._routes}></umb-router-slot>`;
}
static styles = [UUITextStyles, css``];
}
export default UmbScriptsWorkspaceElement;
declare global {
interface HTMLElementTagNameMap {
'umb-scripts-workspace': UmbScriptsWorkspaceElement;
}
}

View File

@@ -1,5 +1,5 @@
import { UMB_APP } from '@umbraco-cms/backoffice/app';
import { UMB_AUTH, type UmbLoggedInUser } from '@umbraco-cms/backoffice/auth';
import { UMB_APP } from '@umbraco-cms/backoffice/context';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, CSSResultGroup, html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbModalContext } from '@umbraco-cms/backoffice/modal';

View File

@@ -102,7 +102,7 @@ export class UmbAuthFlow {
openIdConnectUrl: string,
redirectUri: string,
clientId = 'umbraco-back-office',
scope = 'offline_access'
scope = 'offline_access',
) {
this.#openIdConnectUrl = openIdConnectUrl;
this.#redirectUri = redirectUri;
@@ -115,7 +115,7 @@ export class UmbAuthFlow {
this.#authorizationHandler = new RedirectRequestHandler(
this.#storageBackend,
new UmbNoHashQueryStringUtils(),
window.location
window.location,
);
// set notifier to deliver responses
@@ -156,7 +156,10 @@ export class UmbAuthFlow {
*/
async setInitialState() {
// Ensure there is a connection to the server
await this.fetchServiceConfiguration();
if (!this.#configuration) {
await this.fetchServiceConfiguration();
}
const tokenResponseJson = await this.#storageBackend.getItem(TOKEN_RESPONSE_NAME);
if (tokenResponseJson) {
const response = new TokenResponse(JSON.parse(tokenResponseJson));
@@ -216,7 +219,7 @@ export class UmbAuthFlow {
extras: extras,
},
undefined,
true
true,
);
this.#authorizationHandler.performAuthorizationRequest(this.#configuration, request);

View File

@@ -1 +0,0 @@
export * from './app.context.js';

View File

@@ -19,6 +19,9 @@
"baseUrl": ".",
"incremental": true,
"paths": {
// APPS
"@umbraco-cms/backoffice/app": ["src/apps/app"],
"@umbraco-cms/backoffice/external/lit": ["src/external/lit"],
"@umbraco-cms/backoffice/external/lodash": ["src/external/lodash"],
"@umbraco-cms/backoffice/external/monaco-editor": ["src/external/monaco-editor"],
@@ -40,7 +43,6 @@
// SHARED
"@umbraco-cms/backoffice/auth": ["src/shared/auth"],
"@umbraco-cms/backoffice/context": ["src/shared/context"],
"@umbraco-cms/backoffice/events": ["src/shared/umb-events"],
"@umbraco-cms/backoffice/icon": ["src/shared/icon-registry"],
"@umbraco-cms/backoffice/models": ["src/shared/models"],
@@ -75,6 +77,7 @@
"@umbraco-cms/backoffice/tree": ["src/packages/core/tree"],
"@umbraco-cms/backoffice/variant": ["src/packages/core/variant"],
"@umbraco-cms/backoffice/workspace": ["src/packages/core/workspace"],
"@umbraco-cms/backoffice/culture": ["src/packages/core/culture"],
"@umbraco-cms/backoffice/dictionary": ["./src/packages/dictionary/dictionary/index.ts"],

View File

@@ -20,6 +20,8 @@ export default {
imports: {
'src/': './src/',
'@umbraco-cms/backoffice/app': './src/apps/app/index.ts',
'@umbraco-cms/backoffice/external/lit': './src/external/lit/index.ts',
'@umbraco-cms/backoffice/external/lodash': './src/external/lodash/index.ts',
'@umbraco-cms/backoffice/external/monaco-editor': './src/external/monaco-editor/index.ts',
@@ -40,7 +42,6 @@ export default {
'@umbraco-cms/backoffice/observable-api': './src/libs/observable-api/index.ts',
'@umbraco-cms/backoffice/auth': './src/shared/auth/index.ts',
'@umbraco-cms/backoffice/context': './src/shared/context/index.ts',
'@umbraco-cms/backoffice/events': './src/shared/umb-events/index.ts',
'@umbraco-cms/backoffice/icon': './src/shared/icon-registry/index.ts',
'@umbraco-cms/backoffice/models': './src/shared/models/index.ts',
@@ -77,6 +78,7 @@ export default {
'@umbraco-cms/backoffice/tree': './src/packages/core/tree/index.ts',
'@umbraco-cms/backoffice/variant': './src/packages/core/variant/index.ts',
'@umbraco-cms/backoffice/workspace': './src/packages/core/workspace/index.ts',
'@umbraco-cms/backoffice/culture': './src/packages/core/culture/index.ts',
'@umbraco-cms/backoffice/dictionary': './src/packages/dictionary/dictionary/index.ts',