Merge branch 'main' into v14/feature/search-in-pickers

This commit is contained in:
Mads Rasmussen
2024-07-29 09:06:30 +02:00
committed by GitHub
853 changed files with 5678 additions and 4189 deletions

View File

@@ -2,7 +2,9 @@
This is the working repository of the upcoming new Backoffice to Umbraco CMS.
[![Build and test](https://github.com/umbraco/Umbraco.CMS.Backoffice/actions/workflows/build_test.yml/badge.svg)](https://github.com/umbraco/Umbraco.CMS.Backoffice/actions/workflows/build_test.yml)
[![Storybook](https://github.com/umbraco/Umbraco.CMS.Backoffice/actions/workflows/azure-static-web-apps-ambitious-stone-0033b3603.yml/badge.svg)](https://github.com/umbraco/Umbraco.CMS.Backoffice/actions/workflows/azure-static-web-apps-ambitious-stone-0033b3603.yml)
[![SonarCloud](https://sonarcloud.io/api/project_badges/measure?project=umbraco_Umbraco.CMS.Backoffice&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=umbraco_Umbraco.CMS.Backoffice)
## Installation instructions

View File

@@ -75,7 +75,7 @@ Before you start:
- [ ] Section: Info
- [ ] Relation Types
- [ ] Log Viewer
- [ ] Document Blueprints
- [x] Document Blueprints
- [ ] Languages
- [ ] Extensions
- [ ] Templates

View File

@@ -36,6 +36,7 @@ jobs:
- run: npm ci --no-audit --no-fund --prefer-offline
- run: npm run lint:errors
- run: npm run build:for:cms
- run: npm run check:paths
- run: npm run generate:jsonschema:dist
test:

View File

@@ -27,5 +27,9 @@
"exportall.config.folderListener": [],
"exportall.config.relExclusion": [],
"conventionalCommits.scopes": ["partial views"],
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"sonarlint.connectedMode.project": {
"connectionId": "umbraco",
"projectKey": "umbraco_Umbraco.CMS.Backoffice"
}
}

View File

@@ -18,6 +18,28 @@ console.log('\n-----------------------------------');
console.log('Results:');
console.log('-----------------------------------\n');
const hasError = checkPathLength(PROJECT_DIR);
if (hasError) {
console.log('\n-----------------------------------');
console.log(ERROR_COLOR, 'Path length check failed');
console.log('-----------------------------------\n');
if (IS_CI && processExitCode) {
process.exit(processExitCode);
}
} else {
console.log('\n-----------------------------------');
console.log(SUCCESS_COLOR, 'Path length check passed');
console.log('-----------------------------------\n');
}
// Functions
/**
* Recursively check the path length of all files in a directory.
* @param {string} dir - The directory to check for path lengths
* @returns {boolean}
*/
function checkPathLength(dir) {
const files = readdirSync(dir);
let hasError = false;
@@ -28,11 +50,11 @@ function checkPathLength(dir) {
hasError = true;
if (IS_AZURE_PIPELINES) {
console.error(`##vso[task.logissue type=warning;sourcepath=${filePath};]Path exceeds maximum length of ${MAX_PATH_LENGTH} characters: ${filePath} with ${filePath.length} characters`);
console.error(`##vso[task.logissue type=error;sourcepath=${mapFileToSourcePath(filePath)};]Path exceeds maximum length of ${MAX_PATH_LENGTH} characters: ${filePath} with ${filePath.length} characters`);
} else if (IS_GITHUB_ACTIONS) {
console.error(`::warning file=${filePath},title=Path exceeds ${MAX_PATH_LENGTH} characters::Paths should not be longer than ${MAX_PATH_LENGTH} characters to support WIN32 systems. The file ${filePath} exceeds that with ${filePath.length - MAX_PATH_LENGTH} characters.`);
console.error(`::error file=${mapFileToSourcePath(filePath)},title=Path exceeds ${MAX_PATH_LENGTH} characters::Paths should not be longer than ${MAX_PATH_LENGTH} characters to support WIN32 systems. The file ${filePath} exceeds that with ${filePath.length - MAX_PATH_LENGTH} characters.`);
} else {
console.error(`Path exceeds maximum length of ${MAX_PATH_LENGTH} characters: ${FILE_PATH_COLOR}`, filePath, filePath.length - MAX_PATH_LENGTH);
console.error(FILE_PATH_COLOR, mapFileToSourcePath(filePath), '(exceeds by', filePath.length - MAX_PATH_LENGTH, 'chars)');
}
}
@@ -47,17 +69,12 @@ function checkPathLength(dir) {
return hasError;
}
const hasError = checkPathLength(PROJECT_DIR, MAX_PATH_LENGTH);
if (hasError) {
console.error('\n-----------------------------------');
console.error(ERROR_COLOR, 'Path length check failed');
console.error('-----------------------------------\n');
if (IS_CI && processExitCode) {
process.exit(processExitCode);
}
} else {
console.log('\n-----------------------------------');
console.log(SUCCESS_COLOR, 'Path length check passed');
console.log('-----------------------------------\n');
/**
* Maps a file path to a source path for CI logs.
* @remark This might not always work as expected, especially on bundled files, but it's a best effort to map the file path to a source path.
* @param {string} file - The file path to map to a source path
* @returns {string}
*/
function mapFileToSourcePath(file) {
return file.replace(PROJECT_DIR, 'src').replace('.js', '.ts');
}

View File

@@ -1,3 +1,4 @@
/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
meta: {
docs: {

View File

@@ -4,8 +4,6 @@ import importPlugin from 'eslint-plugin-import';
import localRules from 'eslint-plugin-local-rules';
import wcPlugin from 'eslint-plugin-wc';
import litPlugin from 'eslint-plugin-lit';
import litA11yPlugin from 'eslint-plugin-lit-a11y';
import storybookPlugin from 'eslint-plugin-storybook';
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended';
import tseslint from 'typescript-eslint';
@@ -13,16 +11,21 @@ export default [
// Recommended config applied to all files
js.configs.recommended,
...tseslint.configs.recommended,
wcPlugin.configs['flat/recommended'],
litPlugin.configs['flat/recommended'],
localRules.configs.all,
eslintPluginPrettierRecommended,
// Global ignores
{
ignores: [
'**/eslint.config.js',
'**/rollup.config.js',
'**/vite.config.ts',
'src/external',
'src/packages/core/icon-registry/icons',
'src/packages/core/icon-registry/icons.ts',
'src/**/*.test.ts',
],
},
@@ -40,29 +43,21 @@ export default [
plugins: {
import: importPlugin,
'local-rules': localRules,
wc: wcPlugin,
lit: litPlugin,
'lit-a11y': litA11yPlugin,
storybook: storybookPlugin,
},
rules: {
semi: ['warn', 'always'],
"prettier/prettier": ["warn", {"endOfLine": "auto" }],
"prettier/prettier": ["warn", { "endOfLine": "auto" }],
'no-unused-vars': 'off', //Let '@typescript-eslint/no-unused-vars' catch the errors to allow unused function parameters (ex: in interfaces)
'no-var': 'error',
...importPlugin.configs.recommended.rules,
'import/namespace': 'off',
'import/no-unresolved': 'off',
'import/order': ['warn', { groups: ['builtin', 'parent', 'sibling', 'index', 'external'] }],
'import/no-self-import': 'error',
'import/no-cycle': ['error', { maxDepth: 6, allowUnsafeDynamicCyclicDependency: true }],
'local-rules/bad-type-import': 'error',
'local-rules/enforce-element-suffix-on-element-class-name': 'error',
'local-rules/enforce-umb-prefix-on-element-name': 'error',
'local-rules/ensure-relative-import-use-js-extension': 'error',
'local-rules/no-direct-api-import': 'warn',
'local-rules/prefer-import-aliases': 'error',
'import/no-named-as-default': 'off', // Does not work with eslint 9
'import/no-named-as-default-member': 'off', // Does not work with eslint 9
'local-rules/prefer-static-styles-last': 'warn',
'local-rules/umb-class-prefix': 'error',
'local-rules/no-relative-import-to-import-map-module': 'error',
'local-rules/enforce-umbraco-external-imports': [
'error',
{

View File

@@ -7,7 +7,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Umbraco</title>
<script src="node_modules/msw/lib/iife/index.js"></script>
<link rel="stylesheet" href="src/css/user-defined.css" />
<link rel="stylesheet" href="node_modules/@umbraco-ui/uui-css/dist/uui-css.css" />
<link rel="stylesheet" href="src/css/umb-css.css" />
<script type="module" src="index.ts"></script>

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@
"./block-rte": "./dist-cms/packages/block/block-rte/index.js",
"./block-type": "./dist-cms/packages/block/block-type/index.js",
"./block": "./dist-cms/packages/block/block/index.js",
"./code-editor": "./dist-cms/packages/templating/code-editor/index.js",
"./code-editor": "./dist-cms/packages/code-editor/index.js",
"./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",
@@ -56,6 +56,7 @@
"./menu": "./dist-cms/packages/core/menu/index.js",
"./modal": "./dist-cms/packages/core/modal/index.js",
"./models": "./dist-cms/packages/core/models/index.js",
"./multi-url-picker": "./dist-cms/packages/multi-url-picker/index.js",
"./notification": "./dist-cms/packages/core/notification/index.js",
"./object-type": "./dist-cms/packages/object-type/index.js",
"./package": "./dist-cms/packages/packages/package/index.js",
@@ -129,34 +130,37 @@
},
"workspaces": [
"./src/packages/block",
"./src/packages/code-editor",
"./src/packages/core",
"./src/packages/data-type",
"./src/packages/dictionary",
"./src/packages/documents",
"./src/packages/health-check",
"./src/packages/language",
"./src/packages/tags",
"./src/packages/umbraco-news",
"./src/packages/webhook",
"./src/packages/health-check",
"./src/packages/media",
"./src/packages/members",
"./src/packages/multi-url-picker",
"./src/packages/property-editors",
"./src/packages/tags",
"./src/packages/templating",
"./src/packages/property-editors",
"./src/packages/media",
"./src/packages/user"
"./src/packages/umbraco-news",
"./src/packages/user",
"./src/packages/webhook"
],
"scripts": {
"backoffice:test:e2e": "npx playwright test",
"build-storybook": "npm run wc-analyze && storybook build",
"build:for:cms": "npm run build && npm run build:workspaces && npm run generate:manifest && npm run package:validate && node ./devops/build/copy-to-cms.js",
"build:for:npm": "npm run build && npm run generate:manifest && npm run package:validate",
"build:for:npm": "npm run build -- --declaration && npm run generate:manifest && npm run package:validate",
"build:for:static": "vite build",
"build:vite": "tsc && vite build --mode staging",
"build:workspaces": "npm run build -ws --if-present",
"build": "tsc --project ./src/tsconfig.build.json && rollup -c ./src/rollup.config.js",
"build": "tsc --project ./src/tsconfig.build.json",
"postbuild": "rollup -c ./src/rollup.config.js",
"check": "npm run lint:errors && npm run compile && npm run build-storybook && npm run generate:jsonschema:dist",
"check:paths": "node ./devops/build/check-path-length.js src 140",
"check:paths": "node ./devops/build/check-path-length.js dist-cms 120",
"compile": "tsc",
"postinstall": "npm run generate:tsconfig",
"dev": "vite",
"dev:server": "VITE_UMBRACO_USE_MSW=off vite",
"dev:mock": "VITE_UMBRACO_USE_MSW=on vite",
@@ -187,7 +191,7 @@
"wc-analyze": "wca **/*.element.ts --outFile dist-cms/custom-elements.json",
"generate:tsconfig": "node ./devops/tsconfig/index.js",
"generate:manifest": "node ./devops/build/create-umbraco-package.js",
"package:validate": "node ./devops/package/validate-exports.js && npm run check:paths",
"package:validate": "node ./devops/package/validate-exports.js",
"generate:ui-api-docs": "typedoc --options typedoc.config.js"
},
"engines": {
@@ -197,32 +201,32 @@
"dependencies": {
"@types/diff": "^5.2.1",
"@types/dompurify": "^3.0.5",
"@types/uuid": "^9.0.8",
"@umbraco-ui/uui": "1.8.2",
"@umbraco-ui/uui-css": "1.8.0",
"@types/uuid": "^10.0.0",
"@umbraco-ui/uui": "^1.9.0",
"@umbraco-ui/uui-css": "^1.9.0",
"base64-js": "^1.5.1",
"diff": "^5.2.0",
"dompurify": "^3.1.4",
"dompurify": "^3.1.6",
"element-internals-polyfill": "^1.3.11",
"lit": "^3.1.3",
"marked": "^12.0.2",
"monaco-editor": "^0.48.0",
"lit": "^3.1.4",
"marked": "^13.0.2",
"monaco-editor": "^0.50.0",
"rxjs": "^7.8.1",
"tinymce": "^6.8.3",
"tinymce-i18n": "^24.5.8",
"uuid": "^9.0.1"
"tinymce-i18n": "^24.7.15",
"uuid": "^10.0.0"
},
"devDependencies": {
"@babel/core": "^7.24.3",
"@eslint/js": "^9.6.0",
"@hey-api/openapi-ts": "^0.48.1",
"@mdx-js/react": "^3.0.0",
"@babel/core": "^7.24.9",
"@eslint/js": "^9.7.0",
"@hey-api/openapi-ts": "^0.48.3",
"@mdx-js/react": "^3.0.1",
"@open-wc/testing": "^4.0.0",
"@playwright/test": "^1.45.1",
"@rollup/plugin-commonjs": "^25.0.7",
"@playwright/test": "^1.45.2",
"@rollup/plugin-commonjs": "^26.0.1",
"@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^15.2.3",
"@rollup/plugin-replace": "^5.0.5",
"@rollup/plugin-replace": "^5.0.7",
"@storybook/addon-a11y": "^7.6.17",
"@storybook/addon-actions": "^7.6.17",
"@storybook/addon-essentials": "^7.6.17",
@@ -230,47 +234,45 @@
"@storybook/mdx2-csf": "^1.1.0",
"@storybook/web-components": "^7.6.17",
"@storybook/web-components-vite": "^7.6.17",
"@types/chai": "^4.3.5",
"@types/chai": "^4.3.16",
"@types/eslint__js": "^8.42.3",
"@types/mocha": "^10.0.1",
"@types/mocha": "^10.0.7",
"@web/dev-server-esbuild": "^1.0.2",
"@web/dev-server-import-maps": "^0.2.0",
"@web/dev-server-rollup": "^0.6.3",
"@web/test-runner": "^0.18.1",
"@web/dev-server-import-maps": "^0.2.1",
"@web/dev-server-rollup": "^0.6.4",
"@web/test-runner": "^0.18.2",
"@web/test-runner-playwright": "^0.11.0",
"babel-loader": "^9.1.3",
"eslint": "^9.6.0",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-lit": "^1.14.0",
"eslint-plugin-lit-a11y": "^4.1.3",
"eslint-plugin-local-rules": "^3.0.2",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-storybook": "^0.8.0",
"eslint-plugin-wc": "^2.1.0",
"glob": "^10.3.10",
"globals": "^15.7.0",
"lucide-static": "^0.379.0",
"glob": "^11.0.0",
"globals": "^15.8.0",
"lucide-static": "^0.408.0",
"msw": "^1.3.2",
"playwright-msw": "^3.0.1",
"prettier": "3.3.2",
"prettier": "3.3.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"remark-gfm": "^3.0.1",
"rollup": "^4.14.1",
"rollup": "^4.18.1",
"rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-import-css": "^3.5.0",
"rollup-plugin-web-worker-loader": "^1.6.1",
"simple-icons": "^13.0.0",
"simple-icons": "^13.1.0",
"storybook": "^7.6.17",
"tiny-glob": "^0.2.9",
"tsc-alias": "^1.8.8",
"typedoc": "^0.26.3",
"tsc-alias": "^1.8.10",
"typedoc": "^0.26.4",
"typescript": "^5.5.3",
"typescript-eslint": "^7.15.0",
"typescript-json-schema": "^0.63.0",
"vite": "^5.2.9",
"vite-plugin-static-copy": "^1.0.5",
"typescript-eslint": "^7.16.1",
"typescript-json-schema": "^0.64.0",
"vite": "^5.3.4",
"vite-plugin-static-copy": "^1.0.6",
"vite-tsconfig-paths": "^4.3.2",
"web-component-analyzer": "^2.0.0"
},

View File

@@ -0,0 +1,54 @@
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { OpenAPI } from '@umbraco-cms/backoffice/external/backend-api';
import {
extractUmbNotificationColor,
isUmbNotifications,
UMB_NOTIFICATION_CONTEXT,
UMB_NOTIFICATION_HEADER,
} from '@umbraco-cms/backoffice/notification';
/**
* Controller that adds interceptors to the OpenAPI client
*/
export class UmbApiInterceptorController extends UmbControllerBase {
constructor(host: UmbControllerHost) {
super(host);
this.#addUmbNotificationsInterceptor();
}
/**
* Interceptor which checks responses for the umb-notifications header and displays them as a notification if any. Removes the umb-notifications from the headers.
*/
#addUmbNotificationsInterceptor() {
OpenAPI.interceptors.response.use((response) => {
const umbNotifications = response.headers.get(UMB_NOTIFICATION_HEADER);
if (!umbNotifications) return response;
const notifications = JSON.parse(umbNotifications);
if (!isUmbNotifications(notifications)) return response;
this.getContext(UMB_NOTIFICATION_CONTEXT).then((notificationContext) => {
for (const notification of notifications) {
notificationContext.peek(extractUmbNotificationColor(notification.type), {
data: { headline: notification.category, message: notification.message },
});
}
});
const newHeader = new Headers();
for (const header of response.headers.entries()) {
const [key, value] = header;
if (key !== UMB_NOTIFICATION_HEADER) newHeader.set(key, value);
}
const newResponse = new Response(response.body, {
headers: newHeader,
status: response.status,
statusText: response.statusText,
});
return newResponse;
});
}
}

View File

@@ -3,6 +3,7 @@ import type { UmbAppErrorElement } from './app-error.element.js';
import { UmbAppContext } from './app.context.js';
import { UmbServerConnection } from './server-connection.js';
import { UmbAppAuthController } from './app-auth.controller.js';
import { UmbApiInterceptorController } from './api-interceptor.controller.js';
import type { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
import { UmbAuthContext } from '@umbraco-cms/backoffice/auth';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
@@ -147,6 +148,8 @@ export class UmbAppElement extends UmbLitElement {
OpenAPI.BASE = window.location.origin;
new UmbApiInterceptorController(this);
new UmbBundleExtensionInitializer(this, umbExtensionsRegistry);
new UUIIconRegistryEssential().attach(this);

View File

@@ -13,6 +13,7 @@ import './components/index.js';
const CORE_PACKAGES = [
import('../../packages/block/umbraco-package.js'),
import('../../packages/code-editor/umbraco-package.js'),
import('../../packages/data-type/umbraco-package.js'),
import('../../packages/dictionary/umbraco-package.js'),
import('../../packages/documents/umbraco-package.js'),

View File

@@ -63,7 +63,7 @@ export class UmbUpgraderElement extends UmbLitElement {
if (error) {
this.errorMessage =
error instanceof ApiError ? (error.body as any).detail : error.message ?? 'Unknown error, please try again';
error instanceof ApiError ? (error.body as any).detail : (error.message ?? 'Unknown error, please try again');
} else {
history.pushState(null, '', 'section/content');
}

View File

@@ -2219,8 +2219,8 @@ export default {
forceHideContentEditor: 'Sakrij uređivač sadržaja',
forceHideContentEditorHelp:
'Sakrijte dugme za uređivanje sadržaja i uređivač sadržaja iz preklapanja Block Editor.',
girdInlineEditing: 'Inline editovanje',
girdInlineEditingHelp:
gridInlineEditing: 'Inline editovanje',
gridInlineEditingHelp:
'Omogućava inline uređivanje za prvo svojstvo. Dodatna svojstva se mogu uređivati u prekrivaču.',
blockHasChanges: 'Izmijenili ste ovaj sadržaj. Jeste li sigurni da ih želite odbaciti?',
confirmCancelBlockCreationHeadline: 'Odbaciti kreiranje?',

View File

@@ -2469,8 +2469,8 @@ export default {
getSampleDescription:
"Bydd hyn yn ychwanegu Blociau sylfaenol ac yn eich helpu i ddechrau gyda'r Golygydd Grid Bloc. Fe gewch Blociau ar gyfer Pennawd, Testun Cyfoethog, Delwedd, yn ogystal â Chynllun Dwy Golofn.",
getSampleButton: 'Gosod',
girdInlineEditing: 'Golygu mewnol',
girdInlineEditingHelp:
gridInlineEditing: 'Golygu mewnol',
gridInlineEditingHelp:
'Yn galluogi golygu mewnol ar gyfer yr Eiddo cyntaf. Gellir golygu priodweddau ychwanegol yn y droshaen.',
areaAllowedBlocksEmpty:
'Yn ddiofyn, caniateir pob math bloc mewn Ardal, Defnyddiwch yr opsiwn hwn i ganiatáu mathau dethol yn unig.',

View File

@@ -328,7 +328,9 @@ export default {
variantUnpublishNotAllowed: 'Unpublish is not allowed',
},
blueprints: {
createBlueprintFrom: 'Opret en ny indholdsskabelon fra <em>%0%</em>',
createBlueprintFrom: "Opret en ny indholdsskabelon fra '%0%'",
createBlueprintItemUnder: "Opret en ny indholdsskabelon under '%0%'",
createBlueprintFolderUnder: "Opret en ny mappe under '%0%'",
blankBlueprint: 'Blank',
selectBlueprint: 'Vælg en indholdsskabelon',
createdBlueprintHeading: 'Indholdsskabelon oprettet',
@@ -1861,6 +1863,8 @@ export default {
lastLogin: 'Seneste login',
lastPasswordChangeDate: 'Kodeord sidst ændret',
loginname: 'Brugernavn',
loginnameRequired: 'Påkrævet - indtast et brugernavn for denne bruger',
loginnameDescription: 'Brugernavnet bruges til at logge ind og til at identificere brugeren',
mediastartnode: 'Startnode i mediearkivet',
mediastartnodehelp: 'Begræns mediebiblioteket til en bestemt startnode',
mediastartnodes: 'Medie startnoder',
@@ -1963,7 +1967,12 @@ export default {
'2faCodeInput': 'Indtast din verifikationskode',
'2faCodeInputHelp': 'Indtast din verifikationskode fra din autentificeringsapp',
'2faInvalidCode': 'Den indtastede kode er ugyldig',
emailRequired: 'Required - enter an email address for this user',
emailRequired: 'Påkrævet - indtast en emailadresse for denne bruger',
emailDescription: (usernameIsEmail: boolean) => {
return usernameIsEmail
? 'Emailadressen bruges som brugernavn og til notifikationer og adgangskode gendannelse'
: 'Emailadressen bruges til notifikationer og adgangskode gendannelse';
},
duplicateLogin: 'A user with this login already exists',
nameRequired: 'Required - enter a name for this user',
passwordRequiresDigit: "The password must have at least one digit ('0'-'9')",
@@ -2102,7 +2111,7 @@ export default {
protectDescription: 'Opsæt offentlig adgang på %0%',
rightsDescription: 'Opsæt rettigheder på %0%',
sortDescription: 'Juster soterings rækkefølgen for %0%',
createblueprintDescription: 'Opret indholds skabelon baseret på %0%',
createblueprintDescription: 'Opret indholdsskabelon baseret på %0%',
openContextMenu: 'Åben kontext menu for',
currentLanguage: 'Aktivt sprog',
switchLanguage: 'Skift sprog til',
@@ -2230,7 +2239,7 @@ export default {
addCustomStylesheet: 'Tilføj stylesheet',
headlineEditorAppearance: 'Redigerings udseende',
headlineDataModels: 'Data modeller',
headlineCatalogueAppearance: 'katalog udseende',
headlineCatalogueAppearance: 'Katalog udseende',
labelBackgroundColor: 'Baggrunds farve',
labelIconColor: 'Ikon farve',
labelContentElementType: 'Indholds model',
@@ -2261,8 +2270,8 @@ export default {
headlineAdvanced: 'Avanceret',
forceHideContentEditor: 'Skjul indholdseditoren',
forceHideContentEditorHelp: 'Skjul indholds redigerings knappen samt indholdseditoren i Blok Redigerings vinduet',
girdInlineEditing: 'Direkte redigering',
girdInlineEditingHelp:
gridInlineEditing: 'Direkte redigering',
gridInlineEditingHelp:
'Tilføjer direkte redigering a det første felt. Yderligere felter optræder kun i redigerings vinduet.',
blockHasChanges: 'Du har lavet ændringer til dette indhold. Er du sikker på at du vil kassere dem?',
confirmCancelBlockCreationHeadline: 'Annuller oprettelse?',
@@ -2525,4 +2534,18 @@ export default {
routeNotFoundTitle: 'Ikke fundet',
routeNotFoundDescription: 'Den side du leder efter kunne ikke findes. Kontroller adressen og prøv igen.',
},
codeEditor: {
label: 'Code editor',
languageConfigLabel: 'Sprog',
languageConfigDescription: 'Vælg sprog til syntax highlighting og IntelliSense.',
heightConfigLabel: 'Højde',
heightConfigDescription: 'Indstil højden på editorvinduet i pixels.',
lineNumbersConfigLabel: 'Linjenumre',
lineNumbersConfigDescription: 'Vis linjenumre i editorvinduet.',
minimapConfigLabel: 'Minimap',
minimapConfigDescription: 'Vis en minimap i editorvinduet.',
wordWrapConfigLabel: 'Ordbrydning',
wordWrapConfigDescription:
'Slå ordbrydning til eller fra, så tekst automatisk brydes ved vinduets kant i stedet for at skabe en horisontal scrollbar.',
},
} as UmbLocalizationDictionary;

View File

@@ -335,7 +335,9 @@ export default {
variantUnpublishNotAllowed: 'Unpublish is not allowed',
},
blueprints: {
createBlueprintFrom: 'Create a new Document Blueprint from <em>%0%</em>',
createBlueprintFrom: "Create a new Document Blueprint from '%0%'",
createBlueprintItemUnder: "Create a new item under '%0%'",
createBlueprintFolderUnder: "Create a new folder under '%0%'",
blankBlueprint: 'Blank',
selectBlueprint: 'Select a Document Blueprint',
createdBlueprintHeading: 'Document Blueprint created',
@@ -384,7 +386,7 @@ export default {
create: {
chooseNode: 'Where do you want to create the new %0%',
createUnder: 'Create an item under',
createContentBlueprint: 'Select the Document Type you want to make a content blueprint for',
createContentBlueprint: 'Select the Document Type you want to make a Document Blueprint for',
enterFolderName: 'Enter a folder name',
updateData: 'Choose a type and a title',
noDocumentTypes:
@@ -1372,7 +1374,7 @@ export default {
editMultiContentPublishedText: '%0% documents published and visible on the website',
editVariantPublishedText: '%0% published and visible on the website',
editMultiVariantPublishedText: '%0% documents published for languages %1% and visible on the website',
editBlueprintSavedHeader: 'Content Blueprint saved',
editBlueprintSavedHeader: 'Document Blueprint saved',
editBlueprintSavedText: 'Changes have been successfully saved',
editContentSavedHeader: 'Content saved',
editContentSavedText: 'Remember to publish to make changes visible',
@@ -1785,7 +1787,7 @@ export default {
},
treeHeaders: {
content: 'Content',
contentBlueprints: 'Content Blueprints',
contentBlueprints: 'Document Blueprints',
media: 'Media',
cacheBrowser: 'Cache Browser',
contentRecycleBin: 'Recycle Bin',
@@ -1841,6 +1843,11 @@ export default {
changePhoto: 'Change photo',
configureMfa: 'Configure MFA',
emailRequired: 'Required - enter an email address for this user',
emailDescription: (usernameIsEmail: boolean) => {
return usernameIsEmail
? 'The email address is used for notifications, password recovery, and as the username for logging in'
: 'The email address is used for notifications and password recovery';
},
newPassword: 'New password',
newPasswordFormatLengthTip: 'Minimum %0% character(s) to go!',
newPasswordFormatNonAlphaTip: 'There should be at least %0% special character(s) in there.',
@@ -1872,6 +1879,8 @@ export default {
lastLogin: 'Last login',
lastPasswordChangeDate: 'Password last changed',
loginname: 'Username',
loginnameRequired: 'Required - enter a username for this user',
loginnameDescription: 'The username is used for logging in',
mediastartnode: 'Media start node',
mediastartnodehelp: 'Limit the media library to a specific start node',
mediastartnodes: 'Media start nodes',
@@ -2170,7 +2179,7 @@ export default {
protectDescription: 'Setup access restrictions on %0%',
rightsDescription: 'Setup Permissions on %0%',
sortDescription: 'Change sort order for %0%',
createblueprintDescription: 'Create Content Blueprint based on %0%',
createblueprintDescription: 'Create Document Blueprint based on %0%',
openContextMenu: 'Open context menu for',
currentLanguage: 'Current language',
switchLanguage: 'Switch language to',
@@ -2396,8 +2405,8 @@ export default {
headlineAdvanced: 'Advanced',
forceHideContentEditor: 'Hide content editor',
forceHideContentEditorHelp: 'Hide the content edit button and the content editor from the Block Editor overlay',
girdInlineEditing: 'Inline editing',
girdInlineEditingHelp:
gridInlineEditing: 'Inline editing',
gridInlineEditingHelp:
'Enables inline editing for the first Property. Additional properties can be edited in the overlay.',
blockHasChanges: 'You have made changes to this content. Are you sure you want to discard them?',
confirmCancelBlockCreationHeadline: 'Discard creation?',
@@ -2476,15 +2485,15 @@ export default {
addColumnSpanOption: 'Add spanning %0% columns option',
},
contentTemplatesDashboard: {
whatHeadline: 'What are Content Blueprints?',
whatHeadline: 'What are Document Blueprints?',
whatDescription:
'Content Blueprints are pre-defined content that can be selected when creating a new content node.',
createHeadline: 'How do I create a Content Blueprint?',
'Document Blueprints are pre-defined content that can be selected when creating a new content node.',
createHeadline: 'How do I create a Document Blueprint?',
createDescription:
'<p>There are two ways to create a Content Blueprint:</p><ul><li>Right-click a content node and select "Create Content Blueprint" to create a new Content Blueprint.</li><li>Right-click the Content Blueprints tree in the Settings section and select the Document Type you want to create a Content Blueprint for.</li></ul><p>Once given a name, editors can start using the Content Blueprint as a foundation for their new page.</p>',
manageHeadline: 'How do I manage Content Blueprints?',
'<p>There are two ways to create a Document Blueprint:</p><ul><li>Right-click a content node and select "Create Document Blueprint" to create a new Document Blueprint.</li><li>Right-click the Document Blueprints tree in the Settings section and select the Document Type you want to create a Document Blueprint for.</li></ul><p>Once given a name, editors can start using the Document Blueprint as a foundation for their new page.</p>',
manageHeadline: 'How do I manage Document Blueprints?',
manageDescription:
'You can edit and delete Content Blueprints from the "Content Blueprints" tree in the Settings section. Expand the Document Type which the Content Blueprint is based on and click it to edit or delete it.',
'You can edit and delete Document Blueprints from the "Document Blueprints" tree in the Settings section. Expand the Document Type which the Document Blueprint is based on and click it to edit or delete it.',
},
preview: {
endLabel: 'End',
@@ -2526,4 +2535,17 @@ export default {
routeNotFoundTitle: 'Not found',
routeNotFoundDescription: 'The requested route could not be found. Please check the URL and try again.',
},
codeEditor: {
label: 'Code editor',
languageConfigLabel: 'Language',
languageConfigDescription: 'Select the language for syntax highlighting and IntelliSense.',
heightConfigLabel: 'Height',
heightConfigDescription: 'Set the height of the code editor in pixels.',
lineNumbersConfigLabel: 'Line numbers',
lineNumbersConfigDescription: 'Show line numbers in the code editor.',
minimapConfigLabel: 'Minimap',
minimapConfigDescription: 'Show a minimap in the code editor.',
wordWrapConfigLabel: 'Word wrap',
wordWrapConfigDescription: 'Enable word wrapping in the code editor.',
},
} as UmbLocalizationDictionary;

View File

@@ -95,7 +95,7 @@ export default {
sort: 'Allow access to change the sort order for nodes',
translate: 'Allow access to translate a node',
update: 'Allow access to save a node',
createblueprint: 'Allow access to create a Content Template',
createblueprint: 'Allow access to create a Document Blueprint',
notify: 'Allow access to setup notifications for content nodes',
},
apps: {
@@ -343,14 +343,16 @@ export default {
selectAllVariants: 'Select all variants',
},
blueprints: {
createBlueprintFrom: 'Create a new Content Template from <em>%0%</em>',
createBlueprintFrom: "Create a new Document Blueprint from '%0%'",
createBlueprintItemUnder: "Create a new item under '%0%'",
createBlueprintFolderUnder: "Create a new folder under '%0%'",
blankBlueprint: 'Blank',
selectBlueprint: 'Select a Content Template',
createdBlueprintHeading: 'Content Template created',
createdBlueprintMessage: "A Content Template was created from '%0%'",
duplicateBlueprintMessage: 'Another Content Template with the same name already exists',
selectBlueprint: 'Select a Document Blueprint',
createdBlueprintHeading: 'Document Blueprint created',
createdBlueprintMessage: "A Document Blueprint was created from '%0%'",
duplicateBlueprintMessage: 'Another Document Blueprint with the same name already exists',
blueprintDescription:
'A Content Template is predefined content that an editor can select to use as the\n basis for creating new content\n ',
'A Document Blueprint is predefined content that an editor can select to use as the\n basis for creating new content\n ',
},
media: {
clickToUpload: 'Click to upload',
@@ -393,7 +395,7 @@ export default {
create: {
chooseNode: 'Where do you want to create the new %0%',
createUnder: 'Create an item under',
createContentBlueprint: 'Select the Document Type you want to make a content template for',
createContentBlueprint: 'Select the Document Type you want to make a Document Blueprint for',
enterFolderName: 'Enter a folder name',
updateData: 'Choose a type and a title',
noDocumentTypes:
@@ -1389,7 +1391,7 @@ export default {
editContentPublishedFailedByParent: 'Content could not be published, because a parent page is not published',
editContentPublishedHeader: 'Content published',
editContentPublishedText: 'and visible on the website',
editBlueprintSavedHeader: 'Content Template saved',
editBlueprintSavedHeader: 'Document Blueprint saved',
editBlueprintSavedText: 'Changes have been successfully saved',
editContentSavedHeader: 'Content saved',
editContentSavedText: 'Remember to publish to make changes visible',
@@ -1836,7 +1838,7 @@ export default {
},
treeHeaders: {
content: 'Content',
contentBlueprints: 'Content Templates',
contentBlueprints: 'Document Blueprints',
media: 'Media',
cacheBrowser: 'Cache Browser',
contentRecycleBin: 'Recycle Bin',
@@ -1875,6 +1877,8 @@ export default {
settingsGroup: 'Settings',
templatingGroup: 'Templating',
thirdPartyGroup: 'Third Party',
structureGroup: 'Structure',
advancedGroup: 'Advanced',
webhooks: 'Webhooks',
},
update: {
@@ -1894,6 +1898,11 @@ export default {
changePhoto: 'Change photo',
configureMfa: 'Configure MFA',
emailRequired: 'Required - enter an email address for this user',
emailDescription: (usernameIsEmail: boolean) => {
return usernameIsEmail
? 'The email address is used for notifications, password recovery, and as the username for logging in'
: 'The email address is used for notifications and password recovery';
},
newPassword: 'New password',
newPasswordFormatLengthTip: 'Minimum %0% character(s) to go!',
newPasswordFormatNonAlphaTip: 'There should be at least %0% special character(s) in there.',
@@ -1925,6 +1934,8 @@ export default {
lastLogin: 'Last login',
lastPasswordChangeDate: 'Password last changed',
loginname: 'Username',
loginnameRequired: 'Required - enter a username for this user',
loginnameDescription: 'The username is used for logging in',
mediastartnode: 'Media start node',
mediastartnodehelp: 'Limit the media library to a specific start node',
mediastartnodes: 'Media start nodes',
@@ -2227,7 +2238,7 @@ export default {
protectDescription: 'Setup access restrictions on %0%',
rightsDescription: 'Setup Permissions on %0%',
sortDescription: 'Change sort order for %0%',
createblueprintDescription: 'Create Content Template based on %0%',
createblueprintDescription: 'Create Document Blueprint based on %0%',
openContextMenu: 'Open context menu for',
currentLanguage: 'Current language',
switchLanguage: 'Switch language to',
@@ -2459,8 +2470,8 @@ export default {
headlineAdvanced: 'Advanced',
forceHideContentEditor: 'Hide content editor',
forceHideContentEditorHelp: 'Hide the content edit button and the content editor from the Block Editor overlay.',
girdInlineEditing: 'Inline editing',
girdInlineEditingHelp:
gridInlineEditing: 'Inline editing',
gridInlineEditingHelp:
'Enables inline editing for the first Property. Additional properties can be edited in the overlay.',
blockHasChanges: 'You have made changes to this content. Are you sure you want to discard them?',
confirmCancelBlockCreationHeadline: 'Discard creation?',
@@ -2541,15 +2552,15 @@ export default {
labelInlineMode: 'Display inline with text',
},
contentTemplatesDashboard: {
whatHeadline: 'What are Content Templates?',
whatHeadline: 'What are Document Blueprints?',
whatDescription:
'Content Templates are pre-defined content that can be selected when creating a new\n content node.\n ',
createHeadline: 'How do I create a Content Template?',
'Document Blueprints are pre-defined content that can be selected when creating a new\n content node.\n ',
createHeadline: 'How do I create a Document Blueprint?',
createDescription:
'\n <p>There are two ways to create a Content Template:</p>\n <ul>\n <li>Right-click a content node and select "Create Content Template" to create a new Content Template.</li>\n <li>Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.</li>\n </ul>\n <p>Once given a name, editors can start using the Content Template as a foundation for their new page.</p>\n ',
manageHeadline: 'How do I manage Content Templates?',
'\n <p>There are two ways to create a Document Blueprint:</p>\n <ul>\n <li>Right-click a content node and select "Create Document Blueprint" to create a new Document Blueprint.</li>\n <li>Right-click the Document Blueprints tree in the Settings section and select the Document Type you want to create a Document Blueprint for.</li>\n </ul>\n <p>Once given a name, editors can start using the Document Blueprint as a foundation for their new page.</p>\n ',
manageHeadline: 'How do I manage Document Blueprints?',
manageDescription:
'You can edit and delete Content Templates from the "Content Templates" tree in the\n Settings section. Expand the Document Type which the Content Template is based on and click it to edit or delete\n it.\n ',
'You can edit and delete Document Blueprints from the "Document Blueprints" tree in the\n Settings section. Expand the Document Type which the Document Blueprint is based on and click it to edit or delete\n it.\n ',
},
preview: {
endLabel: 'End',
@@ -2595,4 +2606,17 @@ export default {
routeNotFoundTitle: 'Not found',
routeNotFoundDescription: 'The requested route could not be found. Please check the URL and try again.',
},
codeEditor: {
label: 'Code editor',
languageConfigLabel: 'Language',
languageConfigDescription: 'Select the language for syntax highlighting and IntelliSense.',
heightConfigLabel: 'Height',
heightConfigDescription: 'Set the height of the code editor in pixels.',
lineNumbersConfigLabel: 'Line numbers',
lineNumbersConfigDescription: 'Show line numbers in the code editor.',
minimapConfigLabel: 'Minimap',
minimapConfigDescription: 'Show a minimap in the code editor.',
wordWrapConfigLabel: 'Word wrap',
wordWrapConfigDescription: 'Enable word wrapping in the code editor.',
},
} as UmbLocalizationDictionary;

View File

@@ -751,9 +751,13 @@ export default {
notifications: 'Notificaciones',
},
packager: {
actions: 'Acciones',
created: 'Creada',
createPackage: 'Crear paquete',
chooseLocalPackageText:
'Elige un paquete de tu máquina, seleccionando el botón Examinar<br />y localizando el paquete. Los paquetes de Umbraco normalmente tienen la extensión ".umb" o ".zip".',
packageLicense: 'Licencia',
installed: 'Instalada',
installedPackages: 'Paquetes instalados',
noPackages: 'No tienes instalado ningún paquete',
noPackagesDescription:
@@ -890,6 +894,7 @@ export default {
translation: 'Traducción',
users: 'Usuarios',
help: 'Ayuda',
packages: 'Paquetes',
},
help: {
theBestUmbracoVideoTutorials: 'Los mejores tutoriales en video para Umbraco',

View File

@@ -2279,8 +2279,8 @@ export default {
headlineAdvanced: 'Napredno',
forceHideContentEditor: 'Sakrij uređivač sadržaja',
forceHideContentEditorHelp: 'Sakrij gumb za uređivanje sadržaja i uređivač sadržaja iz preklapanja Block Editor.',
girdInlineEditing: 'Inline uređivanje',
girdInlineEditingHelp:
gridInlineEditing: 'Inline uređivanje',
gridInlineEditingHelp:
'Omogućava inline uređivanje za prvo svojstvo. Dodatna svojstva se mogu uređivati u prekrivaču.',
blockHasChanges: 'Izmijenili ste ovaj sadržaj. Jeste li sigurni da ih želite odbaciti?',
confirmCancelBlockCreationHeadline: 'Odbaciti kreiranje?',

View File

@@ -1 +0,0 @@
/* This file can be overridden by placing a file with the same name in the /wwwroot/umbraco/backoffice/css folder of the website */

File diff suppressed because one or more lines are too long

View File

@@ -438,6 +438,9 @@ export type CultureReponseModel = {
export type CurrenUserConfigurationResponseModel = {
keepUserLoggedIn: boolean;
/**
* @deprecated
*/
usernameIsEmail: boolean;
passwordConfiguration: PasswordConfigurationResponseModel;
};
@@ -633,6 +636,9 @@ export type DocumentConfigurationResponseModel = {
disableUnpublishWhenReferenced: boolean;
allowEditInvariantFromNonDefault: boolean;
allowNonExistingSegmentsCreation: boolean;
/**
* @deprecated
*/
reservedFieldNames: Array<(string)>;
};
@@ -730,6 +736,7 @@ export type DocumentTypeConfigurationResponseModel = {
dataTypesCanBeChanged: DataTypeChangeModeModel;
disableTemplates: boolean;
useSegments: boolean;
reservedFieldNames: Array<(string)>;
};
export type DocumentTypeItemResponseModel = {
@@ -1153,6 +1160,9 @@ export type MediaCollectionResponseModel = {
export type MediaConfigurationResponseModel = {
disableDeleteWhenReferenced: boolean;
disableUnpublishWhenReferenced: boolean;
/**
* @deprecated
*/
reservedFieldNames: Array<(string)>;
};
@@ -1219,6 +1229,10 @@ export type MediaTypeCompositionResponseModel = {
icon: string;
};
export type MediaTypeConfigurationResponseModel = {
reservedFieldNames: Array<(string)>;
};
export type MediaTypeItemResponseModel = {
id: string;
name: string;
@@ -1319,6 +1333,9 @@ export type MediaVariantResponseModel = {
};
export type MemberConfigurationResponseModel = {
/**
* @deprecated
*/
reservedFieldNames: Array<(string)>;
};
@@ -1372,6 +1389,10 @@ export type MemberTypeCompositionResponseModel = {
icon: string;
};
export type MemberTypeConfigurationResponseModel = {
reservedFieldNames: Array<(string)>;
};
export type MemberTypeItemResponseModel = {
id: string;
name: string;
@@ -2597,6 +2618,7 @@ export type UpgradeSettingsResponseModel = {
export type UserConfigurationResponseModel = {
canInviteUsers: boolean;
usernameIsEmail: boolean;
passwordConfiguration: PasswordConfigurationResponseModel;
};
@@ -3850,6 +3872,8 @@ export type PostMediaTypeAvailableCompositionsData = {
export type PostMediaTypeAvailableCompositionsResponse = Array<(AvailableMediaTypeCompositionResponseModel)>;
export type GetMediaTypeConfigurationResponse = MediaTypeConfigurationResponseModel;
export type PostMediaTypeFolderData = {
requestBody?: CreateFolderRequestModel;
};
@@ -4190,6 +4214,8 @@ export type PostMemberTypeAvailableCompositionsData = {
export type PostMemberTypeAvailableCompositionsResponse = Array<(AvailableMemberTypeCompositionResponseModel)>;
export type GetMemberTypeConfigurationResponse = MemberTypeConfigurationResponseModel;
export type GetTreeMemberTypeRootData = {
skip?: number;
take?: number;
@@ -8784,6 +8810,24 @@ export type $OpenApiTs = {
};
};
};
'/umbraco/management/api/v1/media-type/configuration': {
get: {
res: {
/**
* OK
*/
200: MediaTypeConfigurationResponseModel;
/**
* The resource is protected and requires an authentication token
*/
401: unknown;
/**
* The authenticated user do not have access to this resource
*/
403: unknown;
};
};
};
'/umbraco/management/api/v1/media-type/folder': {
post: {
req: PostMediaTypeFolderData;
@@ -9913,6 +9957,24 @@ export type $OpenApiTs = {
};
};
};
'/umbraco/management/api/v1/member-type/configuration': {
get: {
res: {
/**
* OK
*/
200: MemberTypeConfigurationResponseModel;
/**
* The resource is protected and requires an authentication token
*/
401: unknown;
/**
* The authenticated user do not have access to this resource
*/
403: unknown;
};
};
};
'/umbraco/management/api/v1/tree/member-type/root': {
get: {
req: GetTreeMemberTypeRootData;

View File

@@ -13,20 +13,6 @@ import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker.js?worker
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker.js?worker';
/* eslint-enable */
import { css, unsafeCSS } from '@umbraco-cms/backoffice/external/lit';
export const monacoEditorStyles = css`
${unsafeCSS(styles)}
`;
export const monacoJumpingCursorHack = css`
/* a hacky workaround this issue: https://github.com/microsoft/monaco-editor/issues/3217
should probably be removed when the issue is fixed */
.view-lines {
font-feature-settings: revert !important;
}
`;
const initializeWorkers = () => {
self.MonacoEnvironment = {
getWorker(workerId: string, label: string): Promise<Worker> | Worker {
@@ -50,3 +36,4 @@ const initializeWorkers = () => {
initializeWorkers();
export * as monaco from 'monaco-editor';
export { styles };

View File

@@ -70,7 +70,7 @@ export function queryString(): string {
* @returns Params
*/
export function query(): Query {
return toQuery(queryString().substr(1));
return toQuery(queryString().substring(1));
}
/**

View File

@@ -493,6 +493,23 @@ export const data: Array<UmbMockDataTypeModel> = [
},
],
},
{
name: 'Code Editor',
id: 'dt-codeEditor',
parent: null,
editorAlias: 'Umbraco.Plain.String',
editorUiAlias: 'Umb.PropertyEditorUi.CodeEditor',
hasChildren: false,
isFolder: false,
canIgnoreStartNodes: false,
isDeletable: true,
values: [
{
alias: 'language',
value: 'html',
},
],
},
{
name: 'Markdown Editor',
id: 'dt-markdownEditor',

View File

@@ -713,6 +713,30 @@ export const data: Array<UmbMockDocumentTypeModel> = [
labelOnTop: false,
},
},
{
id: '34',
container: {
id: 'all-properties-group-key',
},
alias: 'codeEditor',
name: 'Code Editor',
description: 'umb-code-editor configured with the `html` language.',
dataType: {
id: 'dt-codeEditor',
},
variesByCulture: false,
variesBySegment: false,
sortOrder: 0,
validation: {
mandatory: true,
mandatoryMessage: null,
regEx: null,
regExMessage: null,
},
appearance: {
labelOnTop: false,
},
},
],
containers: [
{

View File

@@ -82,6 +82,36 @@ export const data: Array<UmbMockDocumentModel> = [
`,
},
},
{
alias: 'codeEditor',
culture: null,
segment: null,
value: `<h1>Lorem ipsum dolor sit amet consectetuer adipiscing elit</h1>
<ul>
<li>Lorem ipsum dolor sit amet consectetuer.</li>
<li>Aenean commodo ligula eget dolor.</li>
<li>Aenean massa cum sociis natoque penatibus.</li>
</ul>
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing
elit. Aenean commodo ligula eget dolor.
</p>
<p>
<blockquote>
Lorem ipsum dolor sit amet, consectetuer
adipiscing elit. Aenean commodo ligula eget dolor.
Aenean massa <strong>strong</strong>. Cum sociis
natoque penatibus et magnis dis parturient montes,
nascetur ridiculus mus. Donec quam felis, ultricies
nec, pellentesque eu, pretium quis, sem. Nulla consequat
massa quis enim. Donec pede justo, fringilla vel,
aliquet nec, vulputate eget, arcu. In <em>em</em>
enim justo, rhoncus ut, imperdiet a, venenatis vitae,
justo. Nullam <a class="external ext" href="#">link</a>
dictum felis eu pede mollis pretium.
</blockquote>
</p>`,
},
{
alias: 'email',
culture: null,

View File

@@ -30,8 +30,8 @@ export class UmbBlockGridEntryContext
implements UmbBlockGridScalableContext
{
//
readonly columnSpan = this._layout.asObservablePart((x) => (x ? x.columnSpan ?? null : undefined));
readonly rowSpan = this._layout.asObservablePart((x) => (x ? x.rowSpan ?? null : undefined));
readonly columnSpan = this._layout.asObservablePart((x) => (x ? (x.columnSpan ?? null) : undefined));
readonly rowSpan = this._layout.asObservablePart((x) => (x ? (x.rowSpan ?? null) : undefined));
readonly layoutAreas = this._layout.asObservablePart((x) => x?.areas);
readonly columnSpanOptions = this._blockType.asObservablePart((x) => x?.columnSpanOptions ?? []);
readonly areaTypeGridColumns = this._blockType.asObservablePart((x) => x?.areaGridColumns);

View File

@@ -52,7 +52,7 @@ export class UmbPropertyEditorUIBlockGridLayoutStylesheetElement
const validationLimit = config?.getValueByAlias<UmbNumberRangeValueType>('validationLimit');
this._limitMin = validationLimit?.min ?? 0;
this._limitMax = this.#singleItemMode ? 1 : validationLimit?.max ?? Infinity;
this._limitMax = this.#singleItemMode ? 1 : (validationLimit?.max ?? Infinity);
}
@state()

View File

@@ -7,31 +7,33 @@ import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-
export class UmbBlockGridTypeWorkspaceViewAdvancedElement extends UmbLitElement implements UmbWorkspaceViewElement {
override render() {
return html`
<uui-box headline="Advanced">
<uui-box headline=${this.localize.term('blockEditor_headlineAdvanced')}>
<umb-property
label="Overlay size"
label=${this.localize.term('blockEditor_labelEditorSize')}
alias="editorSize"
property-editor-ui-alias="Umb.PropertyEditorUi.OverlaySize"></umb-property>
<umb-property
label="Inline editing"
label=${this.localize.term('blockEditor_gridInlineEditing')}
alias="inlineEditing"
property-editor-ui-alias="Umb.PropertyEditorUi.Toggle"></umb-property>
<umb-property
label="Hide content editor"
label=${this.localize.term('blockEditor_forceHideContentEditor')}
alias="hideContentEditor"
property-editor-ui-alias="Umb.PropertyEditorUi.Toggle"></umb-property>
</uui-box>
<uui-box headline="Catalogue appearance">
<uui-box headline=${this.localize.term('blockEditor_headlineCatalogueAppearance')}>
<umb-property
label="Background color"
label=${this.localize.term('blockEditor_labelBackgroundColor')}
alias="backgroundColor"
property-editor-ui-alias="Umb.PropertyEditorUi.TextBox"></umb-property>
property-editor-ui-alias="Umb.PropertyEditorUi.EyeDropper"
.config=${[{ alias: 'showAlpha', value: true }]}></umb-property>
<umb-property
label="Icon color"
label=${this.localize.term('blockEditor_labelIconColor')}
alias="iconColor"
property-editor-ui-alias="Umb.PropertyEditorUi.TextBox"></umb-property>
property-editor-ui-alias="Umb.PropertyEditorUi.EyeDropper"
.config=${[{ alias: 'showAlpha', value: true }]}></umb-property>
<umb-property
label="Thumbnail"
label=${this.localize.term('blockEditor_thumbnail')}
alias="thumbnail"
property-editor-ui-alias="Umb.PropertyEditorUi.StaticFilePicker"
.config=${[

View File

@@ -7,20 +7,20 @@ import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-
export class UmbBlockListTypeWorkspaceViewSettingsElement extends UmbLitElement implements UmbWorkspaceViewElement {
override render() {
return html`
<uui-box headline="Editor Appearance">
<uui-box headline=${this.localize.term('blockEditor_headlineEditorAppearance')}>
<umb-property
label="Label"
label=${this.localize.term('general_label')}
alias="label"
property-editor-ui-alias="Umb.PropertyEditorUi.TextBox"></umb-property>
<umb-property
label="Overlay size"
label=${this.localize.term('blockEditor_labelEditorSize')}
alias="editorSize"
property-editor-ui-alias="Umb.PropertyEditorUi.OverlaySize"></umb-property>
</uui-box>
<uui-box headline="Data models">
<uui-box headline=${this.localize.term('blockEditor_headlineDataModels')}>
<!-- TODO: implement readonly mode for umb-property -->
<umb-property
label="Content Model"
label=${this.localize.term('blockEditor_labelContentElementType')}
alias="contentElementTypeKey"
property-editor-ui-alias="Umb.PropertyEditorUi.DocumentTypePicker"
readonly
@@ -35,7 +35,7 @@ export class UmbBlockListTypeWorkspaceViewSettingsElement extends UmbLitElement
},
]}></umb-property>
<umb-property
label="Settings Model"
label=${this.localize.term('blockEditor_labelSettingsElementType')}
alias="settingsElementTypeKey"
property-editor-ui-alias="Umb.PropertyEditorUi.DocumentTypePicker"
.config=${[
@@ -49,17 +49,19 @@ export class UmbBlockListTypeWorkspaceViewSettingsElement extends UmbLitElement
},
]}></umb-property>
</uui-box>
<uui-box headline="Catalogue appearance">
<uui-box headline=${this.localize.term('blockEditor_headlineCatalogueAppearance')}>
<umb-property
label="Background color"
label=${this.localize.term('blockEditor_labelBackgroundColor')}
alias="backgroundColor"
property-editor-ui-alias="Umb.PropertyEditorUi.TextBox"></umb-property>
property-editor-ui-alias="Umb.PropertyEditorUi.EyeDropper"
.config=${[{ alias: 'showAlpha', value: true }]}></umb-property>
<umb-property
label="Icon color"
label=${this.localize.term('blockEditor_labelIconColor')}
alias="iconColor"
property-editor-ui-alias="Umb.PropertyEditorUi.TextBox"></umb-property>
property-editor-ui-alias="Umb.PropertyEditorUi.EyeDropper"
.config=${[{ alias: 'showAlpha', value: true }]}></umb-property>
<umb-property
label="Thumbnail"
label=${this.localize.term('blockEditor_thumbnail')}
alias="thumbnail"
property-editor-ui-alias="Umb.PropertyEditorUi.StaticFilePicker"
.config=${[
@@ -69,9 +71,9 @@ export class UmbBlockListTypeWorkspaceViewSettingsElement extends UmbLitElement
},
]}></umb-property>
</uui-box>
<uui-box headline="Advanced">
<uui-box headline=${this.localize.term('blockEditor_headlineAdvanced')}>
<umb-property
label="Hide Content Editor"
label=${this.localize.term('blockEditor_forceHideContentEditor')}
alias="forceHideContentEditorInOverlay"
property-editor-ui-alias="Umb.PropertyEditorUi.Toggle"></umb-property>
</uui-box>

View File

@@ -6,6 +6,8 @@ import type {
UmbBlockEditorCustomViewProperties,
UmbPropertyEditorUiElement,
} from '@umbraco-cms/backoffice/extension-registry';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import '../ref-rte-block/index.js';
/**
@@ -136,11 +138,6 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
override connectedCallback() {
super.connectedCallback();
this.classList.add('uui-font');
this.classList.add('uui-text');
this.setAttribute('contenteditable', 'false');
}
@@ -150,27 +147,29 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
#renderBlock() {
return html`
<umb-extension-slot
type="blockEditorCustomView"
default-element=${'umb-ref-rte-block'}
.props=${this._blockViewProps}
>${this.#renderRefBlock()}</umb-extension-slot
>
<uui-action-bar>
${this._showContentEdit && this._workspaceEditContentPath
? html`<uui-button label="edit" compact href=${this._workspaceEditContentPath}>
<uui-icon name="icon-edit"></uui-icon>
</uui-button>`
: ''}
${this._hasSettings && this._workspaceEditSettingsPath
? html`<uui-button label="Edit settings" compact href=${this._workspaceEditSettingsPath}>
<uui-icon name="icon-settings"></uui-icon>
</uui-button>`
: ''}
<uui-button label="delete" compact @click=${() => this.#context.requestDelete()}>
<uui-icon name="icon-remove"></uui-icon>
</uui-button>
</uui-action-bar>
<div class="uui-text uui-font">
<umb-extension-slot
type="blockEditorCustomView"
default-element=${'umb-ref-rte-block'}
.props=${this._blockViewProps}
>${this.#renderRefBlock()}</umb-extension-slot
>
<uui-action-bar>
${this._showContentEdit && this._workspaceEditContentPath
? html`<uui-button label="edit" compact href=${this._workspaceEditContentPath}>
<uui-icon name="icon-edit"></uui-icon>
</uui-button>`
: ''}
${this._hasSettings && this._workspaceEditSettingsPath
? html`<uui-button label="Edit settings" compact href=${this._workspaceEditSettingsPath}>
<uui-icon name="icon-settings"></uui-icon>
</uui-button>`
: ''}
<uui-button label="delete" compact @click=${() => this.#context.requestDelete()}>
<uui-icon name="icon-remove"></uui-icon>
</uui-button>
</uui-action-bar>
</div>
`;
}
@@ -179,6 +178,7 @@ export class UmbBlockRteEntryElement extends UmbLitElement implements UmbPropert
}
static override styles = [
UmbTextStyles,
css`
:host {
position: relative;

View File

@@ -12,8 +12,8 @@ export class UmbBlockRteEntryContext extends UmbBlockEntryContext<
UmbBlockRteTypeModel,
UmbBlockRteLayoutModel
> {
readonly displayInline = this._layout.asObservablePart((x) => (x ? x.displayInline ?? false : undefined));
readonly displayInlineConfig = this._blockType.asObservablePart((x) => (x ? x.displayInline ?? false : undefined));
readonly displayInline = this._layout.asObservablePart((x) => (x ? (x.displayInline ?? false) : undefined));
readonly displayInlineConfig = this._blockType.asObservablePart((x) => (x ? (x.displayInline ?? false) : undefined));
readonly forceHideContentEditorInOverlay = this._blockType.asObservablePart(
(x) => !!x?.forceHideContentEditorInOverlay,

View File

@@ -57,11 +57,13 @@ export class UmbBlockRteTypeWorkspaceViewSettingsElement extends UmbLitElement i
<umb-property
label="Background color"
alias="backgroundColor"
property-editor-ui-alias="Umb.PropertyEditorUi.TextBox"></umb-property>
property-editor-ui-alias="Umb.PropertyEditorUi.EyeDropper"
.config=${[{ alias: 'showAlpha', value: true }]}></umb-property>
<umb-property
label="Icon color"
alias="iconColor"
property-editor-ui-alias="Umb.PropertyEditorUi.TextBox"></umb-property>
property-editor-ui-alias="Umb.PropertyEditorUi.EyeDropper"
.config=${[{ alias: 'showAlpha', value: true }]}></umb-property>
<umb-property
label="Thumbnail"
alias="thumbnail"

View File

@@ -99,7 +99,7 @@ export class UmbBlockTypeCardElement extends UmbLitElement {
.background=${this.backgroundColor}>
${this._iconFile
? html`<img src=${this._iconFile} alt="" />`
: html`<umb-icon name=${this._fallbackIcon ?? ''} style="color:${this.iconColor}"></umb-icon>`}
: html`<umb-icon name=${this._fallbackIcon ?? ''} color=${ifDefined(this.iconColor)}></umb-icon>`}
<slot name="actions" slot="actions"> </slot>
</uui-card-block-type>
`;

View File

@@ -85,7 +85,7 @@ export abstract class UmbBlockEntryContext<
public readonly blockType = this._blockType.asObservable();
public readonly contentElementTypeKey = this._blockType.asObservablePart((x) => x?.contentElementTypeKey);
public readonly settingsElementTypeKey = this._blockType.asObservablePart((x) =>
x ? x.settingsElementTypeKey ?? undefined : null,
x ? (x.settingsElementTypeKey ?? undefined) : null,
);
_layout = new UmbObjectState<BlockLayoutType | undefined>(undefined);
@@ -126,7 +126,7 @@ export abstract class UmbBlockEntryContext<
#settings = new UmbObjectState<UmbBlockDataType | undefined>(undefined);
public readonly settings = this.#settings.asObservable();
private readonly settingsDataContentTypeKey = this.#settings.asObservablePart((x) =>
x ? x.contentTypeKey ?? undefined : null,
x ? (x.contentTypeKey ?? undefined) : null,
);
abstract readonly showContentEdit: Observable<boolean>;

View File

@@ -141,6 +141,70 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
'observeLayoutInitially',
);
this.#observeBlockData(unique);
if (this.#liveEditingMode) {
this.#establishLiveSync();
}
}
async create(contentElementTypeId: string) {
await this.#retrieveBlockEntries;
await this.#retrieveModalContext;
if (!this.#blockEntries) {
throw new Error('Block Entries not found');
return;
}
if (!this.#modalContext) {
throw new Error('Modal Context not found');
return;
}
// TODO: Missing some way to append more layout data... this could be part of modal data, (or context api?)
this.setIsNew(true);
const blockCreated = await this.#blockEntries.create(
contentElementTypeId,
{},
this.#modalContext.data as UmbBlockWorkspaceData,
);
if (!blockCreated) {
throw new Error('Block Entries could not create block');
}
// TODO: We should investigate if it makes sense to gather
if (!this.#liveEditingMode) {
this.#layout.setValue(blockCreated.layout as LayoutDataType);
this.content.setData(blockCreated.content);
if (blockCreated.settings) {
this.settings.setData(blockCreated.settings);
}
} else {
// Insert already, cause we are in live editing mode:
const blockInserted = await this.#blockEntries.insert(
blockCreated.layout,
blockCreated.content,
blockCreated.settings,
this.#modalContext.data as UmbBlockWorkspaceData,
);
if (!blockInserted) {
throw new Error('Block Entries could not insert block');
}
const unique = blockCreated.layout.contentUdi;
this.#observeBlockData(unique);
this.#establishLiveSync();
}
}
#observeBlockData(unique: string) {
if (!this.#blockEntries) {
throw new Error('Block Entries not found');
return;
}
this.observe(
this.#blockEntries.layoutOf(unique),
(layoutData) => {
@@ -194,56 +258,6 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
},
'observeLayout',
);
if (this.#liveEditingMode) {
this.#establishLiveSync();
}
}
async create(contentElementTypeId: string) {
await this.#retrieveBlockEntries;
await this.#retrieveModalContext;
if (!this.#blockEntries) {
throw new Error('Block Entries not found');
return;
}
if (!this.#modalContext) {
throw new Error('Modal Context not found');
return;
}
// TODO: Missing some way to append more layout data... this could be part of modal data, (or context api?)
this.setIsNew(true);
const blockCreated = await this.#blockEntries.create(
contentElementTypeId,
{},
this.#modalContext.data as UmbBlockWorkspaceData,
);
if (!blockCreated) {
throw new Error('Block Entries could not create block');
}
this.#layout.setValue(blockCreated.layout as LayoutDataType);
this.content.setData(blockCreated.content);
if (blockCreated.settings) {
this.settings.setData(blockCreated.settings);
}
if (this.#liveEditingMode) {
// Insert already, cause we are in live editing mode:
const blockInserted = await this.#blockEntries.insert(
blockCreated.layout,
blockCreated.content,
blockCreated.settings,
this.#modalContext.data as UmbBlockWorkspaceData,
);
if (!blockInserted) {
throw new Error('Block Entries could not insert block');
}
this.#establishLiveSync();
}
}
#establishLiveSync() {

View File

@@ -8,10 +8,11 @@ import type {
UmbCodeEditorHost,
UmbCodeEditorRange,
UmbCodeEditorSelection,
} from './code-editor.model.js';
} from './models/code-editor.model.js';
import { themes } from './themes/index.js';
import { monaco } from '@umbraco-cms/backoffice/external/monaco-editor';
import { UmbChangeEvent, UmbInputEvent } from '@umbraco-cms/backoffice/event';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
//TODO - consider firing change event on blur
@@ -27,9 +28,11 @@ import { UmbChangeEvent, UmbInputEvent } from '@umbraco-cms/backoffice/event';
* @export
* @class UmbCodeEditor
*/
export class UmbCodeEditorController {
export class UmbCodeEditorController extends UmbControllerBase {
#host: UmbCodeEditorHost;
#editor?: monaco.editor.IStandaloneCodeEditor;
/**
* The monaco editor object. This is the actual monaco editor object. It is exposed for advanced usage, but mind the fact that editor might be swapped in the future for a different library, so use on your own responsibility. For more information see [monaco editor API](https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.IStandaloneCodeEditor.html).
*
@@ -41,6 +44,7 @@ export class UmbCodeEditorController {
}
#options: CodeEditorConstructorOptions = {};
/**
* The options used to create the editor.
*
@@ -63,6 +67,7 @@ export class UmbCodeEditorController {
};
#position: UmbCodeEditorCursorPosition | null = null;
/**
* Provides the current position of the cursor.
*
@@ -72,7 +77,9 @@ export class UmbCodeEditorController {
get position() {
return this.#position;
}
#secondaryPositions: UmbCodeEditorCursorPosition[] = [];
/**
* Provides positions of all the secondary cursors.
*
@@ -102,6 +109,7 @@ export class UmbCodeEditorController {
this.#editor.setValue(newValue ?? '');
}
}
/**
* Provides the current model of the editor. For advanced usage. Bare in mind that in case of the monaco library being swapped in the future, this might not be available. For more information see [monaco editor model API](https://microsoft.github.io/monaco-editor/docs.html#interfaces/editor.ITextModel.html).
*
@@ -112,6 +120,7 @@ export class UmbCodeEditorController {
if (!this.#editor) return null;
return this.#editor.getModel();
}
/**
* Creates an instance of UmbCodeEditor. You should instantiate this class through the `UmbCodeEditorHost` interface and that should happen when inside DOM nodes of the host container are available, otherwise the editor will not be able to initialize, for example in lit `firstUpdated` lifecycle hook. It will make host emit change and input events when the value of the editor changes.
* @param {UmbCodeEditorHost} host
@@ -119,6 +128,7 @@ export class UmbCodeEditorController {
* @memberof UmbCodeEditor
*/
constructor(host: UmbCodeEditorHost, options?: CodeEditorConstructorOptions) {
super(host);
this.#options = { ...options };
this.#host = host;
this.#registerThemes();
@@ -144,6 +154,7 @@ export class UmbCodeEditorController {
this.#editor?.onDidChangeModel(() => {
this.#host.dispatchEvent(new UmbChangeEvent());
});
this.#editor?.onDidChangeCursorPosition((e) => {
this.#position = e.position;
this.#secondaryPositions = e.secondaryPositions;
@@ -184,14 +195,8 @@ export class UmbCodeEditorController {
const mergedOptions = { ...this.#defaultMonacoOptions, ...this.#mapOptions(options) };
this.#editor = monaco.editor.create(this.#host.container, {
...mergedOptions,
value: this.#host.code ?? '',
language: this.#host.language,
theme: this.#host.theme,
readOnly: this.#host.readonly,
ariaLabel: this.#host.label,
});
this.#editor = monaco.editor.create(this.#host.container, mergedOptions);
this.#initiateEvents();
}
/**

View File

@@ -1,12 +1,29 @@
import { UmbCodeEditorController } from './code-editor.controller.js';
import type { CodeEditorLanguage, CodeEditorSearchOptions, UmbCodeEditorHost } from './code-editor.model.js';
import { CodeEditorTheme } from './code-editor.model.js';
import type { UmbCodeEditorController } from '../code-editor.controller.js';
import {
type CodeEditorConstructorOptions,
CodeEditorTheme,
UmbCodeEditorLoadedEvent,
type CodeEditorLanguage,
type CodeEditorSearchOptions,
type UmbCodeEditorHost,
} from '../models/index.js';
import { UMB_THEME_CONTEXT } from '@umbraco-cms/backoffice/themes';
import { monacoEditorStyles, monacoJumpingCursorHack } from '@umbraco-cms/backoffice/external/monaco-editor';
import type { PropertyValues, Ref } from '@umbraco-cms/backoffice/external/lit';
import { css, html, createRef, ref, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import {
createRef,
css,
customElement,
html,
property,
ref,
state,
unsafeCSS,
when,
} from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
const elementName = 'umb-code-editor';
/**
* A custom element that renders a code editor. Code editor is based on the Monaco Editor library.
* The element will listen to the theme context and update the theme accordingly.
@@ -20,8 +37,9 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
* @implements {UmbCodeEditorHost}
* @fires input - Fired when the value of the editor changes.
* @fires change - Fired when the entire model of editor is replaced.
* @fires loaded - Fired when the editor is loaded and ready to use.
*/
@customElement('umb-code-editor')
@customElement(elementName)
export class UmbCodeEditorElement extends UmbLitElement implements UmbCodeEditorHost {
private containerRef: Ref<HTMLElement> = createRef();
@@ -35,6 +53,7 @@ export class UmbCodeEditorElement extends UmbLitElement implements UmbCodeEditor
get editor() {
return this.#editor;
}
/**
* Theme of the editor. Default is light. Element will listen to the theme context and update the theme accordingly.
*
@@ -43,6 +62,7 @@ export class UmbCodeEditorElement extends UmbLitElement implements UmbCodeEditor
*/
@property()
theme: CodeEditorTheme = CodeEditorTheme.Light;
/**
* Language of the editor. Default is javascript.
*
@@ -51,16 +71,18 @@ export class UmbCodeEditorElement extends UmbLitElement implements UmbCodeEditor
*/
@property()
language: CodeEditorLanguage = 'javascript';
/**
* Label of the editor. Default is 'Code Editor'.
*
* @memberof UmbCodeEditorElement
*/
@property()
label = 'Code Editor';
label?: string;
//TODO - this should be called a value
#code = '';
/**
* Value of the editor. Default is empty string.
*
@@ -80,16 +102,56 @@ export class UmbCodeEditorElement extends UmbLitElement implements UmbCodeEditor
}
this.requestUpdate('code', oldValue);
}
/**
* Whether the editor is readonly. Default is false.
* Whether the editor is readonly.
*
* @memberof UmbCodeEditorElement
*/
@property({ type: Boolean, attribute: 'readonly' })
readonly = false;
/**
* Whether to show line numbers.
*
* @memberof UmbCodeEditorElement
*/
@property({ type: Boolean, attribute: 'disable-line-numbers' })
disableLineNumbers = false;
/**
* Whether to show minimap.
*
* @memberof UmbCodeEditorElement
*/
@property({ type: Boolean, attribute: 'disable-minimap' })
disableMinimap = false;
/**
* Whether to enable word wrap. Default is false.
*
* @memberof UmbCodeEditorElement
*/
@property({ type: Boolean, attribute: 'word-wrap' })
wordWrap = false;
/**
* Whether to enable folding. Default is true.
*
* @memberof UmbCodeEditorElement
*/
@property({ type: Boolean, attribute: 'disable-folding' })
disableFolding = false;
@state()
private _loading = true;
@state()
private _styles?: string;
constructor() {
super();
this.consumeContext(UMB_THEME_CONTEXT, (instance) => {
this.observe(
instance.theme,
@@ -101,19 +163,49 @@ export class UmbCodeEditorElement extends UmbLitElement implements UmbCodeEditor
});
}
override firstUpdated() {
this.#editor = new UmbCodeEditorController(this);
override async firstUpdated() {
const { styles } = await import('@umbraco-cms/backoffice/external/monaco-editor');
this._styles = styles;
const { UmbCodeEditorController } = await import('../code-editor.controller.js');
// Options
this.#editor = new UmbCodeEditorController(this, this.#constructorOptions());
this._loading = false;
this.dispatchEvent(new UmbCodeEditorLoadedEvent());
}
protected override updated(_changedProperties: PropertyValues<this>): void {
if (_changedProperties.has('theme') || _changedProperties.has('language')) {
this.#editor?.updateOptions({
theme: this.theme,
language: this.language,
});
if (
_changedProperties.has('theme') ||
_changedProperties.has('language') ||
_changedProperties.has('disableLineNumbers') ||
_changedProperties.has('disableMinimap') ||
_changedProperties.has('wordWrap') ||
_changedProperties.has('readonly') ||
_changedProperties.has('code') ||
_changedProperties.has('label') ||
_changedProperties.has('disableFolding')
) {
this.#editor?.updateOptions(this.#constructorOptions());
}
}
#constructorOptions(): CodeEditorConstructorOptions {
return {
language: this.language,
theme: this.theme,
ariaLabel: this.label ?? this.localize.term('codeEditor_label'),
lineNumbers: !this.disableLineNumbers,
minimap: !this.disableMinimap,
wordWrap: this.wordWrap ? 'on' : 'off',
readOnly: this.readonly,
folding: !this.disableFolding,
value: this.code,
};
}
#translateTheme(theme: string) {
switch (theme) {
case 'umb-light-theme':
@@ -149,16 +241,34 @@ export class UmbCodeEditorElement extends UmbLitElement implements UmbCodeEditor
}
override render() {
return html` <div id="editor-container" ${ref(this.containerRef)}></div> `;
return html`
${this.#renderStyles()}
${when(this._loading, () => html`<div id="loader-container"><uui-loader></uui-loader></div>`)}
<div id="editor-container" ${ref(this.containerRef)}></div>
`;
}
#renderStyles() {
if (!this._styles) return;
return html`
<style>
${unsafeCSS(this._styles)}
</style>
`;
}
static override styles = [
monacoEditorStyles,
monacoJumpingCursorHack,
css`
:host {
display: block;
}
#loader-container {
display: grid;
place-items: center;
min-height: calc(100dvh - 260px);
}
#editor-container {
width: var(--editor-width);
height: var(--editor-height, 100%);
@@ -167,6 +277,12 @@ export class UmbCodeEditorElement extends UmbLitElement implements UmbCodeEditor
--vscode-scrollbarSlider-background: var(--uui-color-disabled-contrast);
--vscode-scrollbarSlider-hoverBackground: rgba(100, 100, 100, 0.7);
--vscode-scrollbarSlider-activeBackground: rgba(0, 0, 0, 0.6);
/* a hacky workaround this issue: https://github.com/microsoft/monaco-editor/issues/3217
should probably be removed when the issue is fixed */
.view-lines {
font-feature-settings: revert !important;
}
}
`,
];
@@ -174,6 +290,6 @@ export class UmbCodeEditorElement extends UmbLitElement implements UmbCodeEditor
declare global {
interface HTMLElementTagNameMap {
'umb-code-editor': UmbCodeEditorElement;
[elementName]: UmbCodeEditorElement;
}
}

View File

@@ -0,0 +1,247 @@
import type { CodeEditorLanguage } from '../models/code-editor.model.js';
import { CodeEditorTheme } from '../models/code-editor.model.js';
import type { UmbCodeEditorElement } from './code-editor.element.js';
import type { Meta, StoryObj } from '@storybook/web-components';
import { html } from '@umbraco-cms/backoffice/external/lit';
import './code-editor.element.js';
const meta: Meta<UmbCodeEditorElement> = {
title: 'Components/Code Editor',
component: 'umb-code-editor',
decorators: [(story) => html`<div style="--editor-height: 400px">${story()}</div>`],
parameters: { layout: 'fullscreen' },
argTypes: {
theme: {
control: 'select',
options: [
CodeEditorTheme.Dark,
CodeEditorTheme.Light,
CodeEditorTheme.HighContrastLight,
CodeEditorTheme.HighContrastLight,
],
},
},
};
const codeSnippets: Record<CodeEditorLanguage, string> = {
csharp: `using System;
namespace HelloWorld;
public class Program
{
public static void Main()
{
Console.WriteLine("Hello World");
}
}`,
javascript: `// Returns "banana"
('b' + 'a' + + 'a' + 'a').toLowerCase();`,
css: `:host {
display: flex;
background-color: var(--uui-color-background);
width: 100%;
height: 100%;
flex-direction: column;
}
#header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
height: 70px;
background-color: var(--uui-color-surface);
border-bottom: 1px solid var(--uui-color-border);
box-sizing: border-box;
}
#headline {
display: block;
margin: 0 var(--uui-size-layout-1);
}
#tabs {
margin-left: auto;
}`,
html: `<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>
<h1>This is a Heading</h1>
<p>This is a paragraph.</p>
</body>
</html>`,
razor: `@using Umbraco.Extensions
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem>
@{
if (Model?.Areas.Any() != true) { return; }
}
<div class="umb-block-grid__area-container"
style="--umb-block-grid--area-grid-columns: @(Model.AreaGridColumns?.ToString() ?? Model.GridColumns?.ToString() ?? "12");">
@foreach (var area in Model.Areas)
{
@await Html.GetBlockGridItemAreaHtmlAsync(area)
}
</div>`,
markdown: `
You will like those projects!
---
# h1 Heading 8-)
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Horizontal Rules
___
---
***
## Typographic replacements
Enable typographer option to see result.
(c) (C) (r) (R) (tm) (TM) (p) (P) +-
test.. test... test..... test?..... test!....
!!!!!! ???? ,, -- ---
"Smartypants, double quotes" and 'single quotes'`,
typescript: `import { UmbTemplateRepository } from '../repository/template.repository.js';
import { UmbWorkspaceContextBase } from '../../../shared/components/workspace/workspace-context/workspace-context.js';
import { UmbObjectState } from '@umbraco-cms/observable-api';
import { TemplateModel } from '@umbraco-cms/backend-api';
import { UmbControllerHostElement } from '@umbraco-cms/controller';
export class UmbTemplateWorkspaceContext extends UmbWorkspaceContext<UmbTemplateRepository, TemplateModel> {
#data = new UmbObjectState<TemplateModel | undefined>(undefined);
data = this.#data.asObservable();
name = this.#data.asObservablePart((data) => data?.name);
content = this.#data.asObservablePart((data) => data?.content);
constructor(host: UmbControllerHostElement) {
super(host, 'Umb.Workspace.Template', new UmbTemplateRepository(host));
}
getData() {
return this.#data.getValue();
}
setName(value: string) {
this.#data.setValue({ ...this.#data.value, name: value });
}
setContent(value: string) {
this.#data.setValue({ ...this.#data.value, content: value });
}
async load(entityId: string) {
const { data } = await this.repository.requestByKey(entityId);
if (data) {
this.setIsNew(false);
this.#data.setValue(data);
}
}
async createScaffold(parentId: string | null) {
const { data } = await this.repository.createScaffold(parentId);
if (!data) return;
this.setIsNew(true);
this.#data.setValue(data);
}
}`,
json: `{
"compilerOptions": {
"module": "esnext",
"target": "esnext",
"lib": ["es2020", "dom", "dom.iterable"],
"declaration": true,
"emitDeclarationOnly": true,
"noEmitOnError": true,
"outDir": "./types",
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"moduleResolution": "node",
"isolatedModules": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": false,
"skipLibCheck": true,
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"@umbraco-cms/css": ["libs/css/custom-properties.css"],
"@umbraco-cms/modal": ["src/core/modal"],
"@umbraco-cms/models": ["libs/models"],
"@umbraco-cms/backend-api": ["libs/backend-api"],
"@umbraco-cms/context-api": ["libs/context-api"],
"@umbraco-cms/controller": ["libs/controller"],
"@umbraco-cms/element": ["libs/element"],
"@umbraco-cms/extension-api": ["libs/extension-api"],
"@umbraco-cms/extension-registry": ["libs/extension-registry"],
"@umbraco-cms/notification": ["libs/notification"],
"@umbraco-cms/observable-api": ["libs/observable-api"],
"@umbraco-cms/events": ["libs/events"],
"@umbraco-cms/entity-action": ["libs/entity-action"],
"@umbraco-cms/workspace": ["libs/workspace"],
"@umbraco-cms/utils": ["libs/utils"],
"@umbraco-cms/router": ["libs/router"],
"@umbraco-cms/sorter": ["libs/sorter"],
"@umbraco-cms/test-utils": ["libs/test-utils"],
"@umbraco-cms/repository": ["libs/repository"],
"@umbraco-cms/resources": ["libs/resources"],
"@umbraco-cms/store": ["libs/store"],
"@umbraco-cms/components/*": ["src/backoffice/components/*"],
"@umbraco-cms/sections/*": ["src/backoffice/sections/*"]
}
},
"include": ["src/**/*.ts", "apps/**/*.ts", "libs/**/*.ts", "e2e/**/*.ts"],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}`,
};
export default meta;
type Story = StoryObj<UmbCodeEditorElement>;
const [Csharp, Javascript, Css, Html, Razor, Markdown, Typescript, Json]: Story[] = Object.keys(codeSnippets).map(
(language) => {
return {
args: {
language: language as CodeEditorLanguage,
code: codeSnippets[language as CodeEditorLanguage],
},
};
},
);
const Themes: Story = {
args: {
language: 'javascript',
code: codeSnippets.javascript,
theme: CodeEditorTheme.Dark,
},
};
export { Csharp, Javascript, Css, Html, Razor, Markdown, Typescript, Json, Themes };

View File

@@ -0,0 +1 @@
export * from './code-editor.element.js';

View File

@@ -0,0 +1,11 @@
export * from './components/index.js';
export * from './models/index.js';
export type { UmbCodeEditorController } from './code-editor.controller.js';
/**
* @deprecated Use `import from '@umbraco-cms/backoffice/code-editor';` directly.
* This function will be removed in Umbraco 15.
*/
export function loadCodeEditor() {
return import('@umbraco-cms/backoffice/code-editor');
}

View File

@@ -0,0 +1,4 @@
import { manifest as propertyEditorManifest } from './property-editor/manifests.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes> = [propertyEditorManifest];

View File

@@ -0,0 +1,7 @@
export class UmbCodeEditorLoadedEvent extends Event {
public static readonly TYPE = 'loaded';
public constructor() {
super(UmbCodeEditorLoadedEvent.TYPE, { bubbles: true, cancelable: false });
}
}

View File

@@ -1,4 +1,14 @@
export type CodeEditorLanguage = 'razor' | 'typescript' | 'javascript' | 'css' | 'markdown' | 'json' | 'html';
import type { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
export type CodeEditorLanguage =
| 'csharp'
| 'razor'
| 'typescript'
| 'javascript'
| 'css'
| 'markdown'
| 'json'
| 'html';
export enum CodeEditorTheme {
Light = 'umb-light',
@@ -7,13 +17,13 @@ export enum CodeEditorTheme {
HighContrastDark = 'umb-hc-dark',
}
export interface UmbCodeEditorHost extends HTMLElement {
export interface UmbCodeEditorHost extends UmbLitElement {
container: HTMLElement;
language: CodeEditorLanguage;
theme: CodeEditorTheme;
code: string;
readonly: boolean;
label: string;
label?: string;
}
export interface UmbCodeEditorCursorPosition {

View File

@@ -0,0 +1,2 @@
export * from './code-editor-loaded.event.js';
export * from './code-editor.model.js';

View File

@@ -0,0 +1,8 @@
{
"name": "@umbraco-backoffice/code-editor",
"private": true,
"type": "module",
"scripts": {
"build": "vite build"
}
}

View File

@@ -0,0 +1,71 @@
import type { ManifestPropertyEditorUi } from '@umbraco-cms/backoffice/extension-registry';
export const manifest: ManifestPropertyEditorUi = {
type: 'propertyEditorUi',
alias: 'Umb.PropertyEditorUi.CodeEditor',
name: 'Code Editor Property Editor UI',
element: () => import('./property-editor-ui-code-editor.element.js'),
meta: {
label: 'Code Editor',
propertyEditorSchemaAlias: 'Umbraco.Plain.String',
icon: 'icon-code',
group: 'common',
settings: {
properties: [
{
alias: 'language',
label: '#codeEditor_languageConfigLabel',
description: '{#codeEditor_languageConfigDescription}',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Dropdown',
config: [
{
alias: 'items',
value: [
{ name: 'C#', value: 'csharp' },
{ name: 'CSS', value: 'css' },
{ name: 'HTML', value: 'html' },
{ name: 'JavaScript', value: 'javascript' },
{ name: 'JSON', value: 'json' },
{ name: 'Markdown', value: 'markdown' },
{ name: 'Razor (CSHTML)', value: 'razor' },
{ name: 'TypeScript', value: 'typescript' },
],
},
],
},
{
alias: 'height',
label: '#codeEditor_heightConfigLabel',
description: '{#codeEditor_heightConfigDescription}',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Integer',
config: [{ alias: 'min', value: 0 }],
},
{
alias: 'lineNumbers',
label: '#codeEditor_lineNumbersConfigLabel',
description: '{#codeEditor_lineNumbersConfigDescription}',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle',
},
{
alias: 'minimap',
label: '#codeEditor_minimapConfigLabel',
description: '{#codeEditor_minimapConfigDescription}',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle',
},
{
alias: 'wordWrap',
label: '#codeEditor_wordWrapConfigLabel',
description: '{#codeEditor_wordWrapConfigDescription}',
propertyEditorUiAlias: 'Umb.PropertyEditorUi.Toggle',
},
],
defaultData: [
{ alias: 'language', value: 'javascript' },
{ alias: 'height', value: 400 },
{ alias: 'lineNumbers', value: true },
{ alias: 'minimap', value: true },
{ alias: 'wordWrap', value: false },
],
},
},
};

View File

@@ -0,0 +1,83 @@
import type { CodeEditorLanguage } from '../models/index.js';
import type { UmbCodeEditorElement } from '../components/code-editor.element.js';
import { css, customElement, html, property, state, styleMap } from '@umbraco-cms/backoffice/external/lit';
import { UmbInputEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbPropertyValueChangeEvent } from '@umbraco-cms/backoffice/property-editor';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import '../components/code-editor.element.js';
const elementName = 'umb-property-editor-ui-code-editor';
@customElement(elementName)
export class UmbPropertyEditorUICodeEditorElement extends UmbLitElement implements UmbPropertyEditorUiElement {
#defaultLanguage: CodeEditorLanguage = 'javascript';
@state()
private _language?: CodeEditorLanguage = this.#defaultLanguage;
@state()
private _height = 400;
@state()
private _lineNumbers = true;
@state()
private _minimap = true;
@state()
private _wordWrap = false;
@property()
value = '';
@property({ attribute: false })
public set config(config: UmbPropertyEditorConfigCollection | undefined) {
if (!config) return;
this._language = config?.getValueByAlias<CodeEditorLanguage>('language') ?? this.#defaultLanguage;
this._height = Number(config?.getValueByAlias('height')) || 400;
this._lineNumbers = config?.getValueByAlias('lineNumbers') ?? false;
this._minimap = config?.getValueByAlias('minimap') ?? false;
this._wordWrap = config?.getValueByAlias('wordWrap') ?? false;
}
#onChange(event: UmbInputEvent & { target: UmbCodeEditorElement }) {
if (!(event instanceof UmbInputEvent)) return;
this.value = event.target.code;
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
override render() {
return html`
<umb-code-editor
style=${styleMap({ height: `${this._height}px` })}
.language=${this._language ?? this.#defaultLanguage}
.code=${this.value ?? ''}
?disable-line-numbers=${!this._lineNumbers}
?disable-minimap=${!this._minimap}
?word-wrap=${this._wordWrap}
@input=${this.#onChange}>
</umb-code-editor>
`;
}
static override styles = [
css`
umb-code-editor {
border-radius: var(--uui-border-radius);
border: 1px solid var(--uui-color-divider-emphasis);
}
`,
];
}
export { UmbPropertyEditorUICodeEditorElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbPropertyEditorUICodeEditorElement;
}
}

View File

@@ -0,0 +1,17 @@
import type { UmbPropertyEditorUICodeEditorElement } from './property-editor-ui-code-editor.element.js';
import type { Meta, StoryObj } from '@storybook/web-components';
import { html } from '@umbraco-cms/backoffice/external/lit';
import './property-editor-ui-code-editor.element.js';
const meta: Meta<UmbPropertyEditorUICodeEditorElement> = {
title: 'Property Editor UIs/Code Editor',
component: 'umb-property-editor-ui-code-editor',
id: 'umb-property-editor-ui-code-editor',
decorators: [(story) => html`<div style="--editor-height: 400px">${story()}</div>`],
};
export default meta;
type Story = StoryObj<UmbPropertyEditorUICodeEditorElement>;
export const Overview: Story = {};

View File

@@ -1,4 +1,4 @@
import type { CodeEditorTheme } from '../code-editor.model.js';
import type { CodeEditorTheme } from '../models/code-editor.model.js';
import { UmbCodeEditorThemeHighContrastLight } from './code-editor.hc-light.theme.js';
import { UmbCodeEditorThemeHighContrastDark } from './code-editor.hc-dark.theme.js';
import { UmbCodeEditorThemeLight } from './code-editor.light.theme.js';

View File

@@ -0,0 +1,8 @@
export const extensions = [
{
name: 'Umbraco Code Editor Bundle',
alias: 'Umb.Bundle.UmbracoCodeEditor',
type: 'bundle',
js: () => import('./manifests.js'),
},
];

View File

@@ -0,0 +1,12 @@
import { defineConfig } from 'vite';
import { rmSync } from 'fs';
import { getDefaultConfig } from '../../vite-config-base';
const dist = '../../../dist-cms/packages/code-editor';
// delete the unbundled dist folder
rmSync(dist, { recursive: true, force: true });
export default defineConfig({
...getDefaultConfig({ dist }),
});

View File

@@ -65,17 +65,16 @@ export class UmbCollectionActionButtonElement extends UmbLitElement {
}
override render() {
const label = this.manifest?.meta.label ? this.localize.string(this.manifest.meta.label) : this.manifest?.name;
return html`
<uui-button
id="action-button"
@click=${this._onClick}
look="outline"
color="default"
label=${ifDefined(
this.manifest?.meta.label ? this.localize.string(this.manifest.meta.label) : this.manifest?.name,
)}
href="${ifDefined(this.manifest?.meta.href)}"
.state=${this._buttonState}></uui-button>
look="outline"
label=${ifDefined(label)}
href=${ifDefined(this.manifest?.meta.href)}
.state=${this._buttonState}
@click=${this._onClick}></uui-button>
`;
}
}

View File

@@ -82,7 +82,7 @@ export class UmbCollectionSelectionActionsElement extends UmbLitElement {
<uui-button
@click=${this._handleClearSelection}
@keydown=${this._handleKeyDown}
label="Clear"
label=${this.localize.term('buttons_clearSelection')}
look="secondary"></uui-button>
${this._renderSelectionCount()}
</div>

View File

@@ -110,6 +110,13 @@ export class UmbCollectionViewBundleElement extends UmbLitElement {
#onClick(view: UmbCollectionViewLayout) {
this.#collectionContext?.setLastSelectedView(this._entityUnique, view.alias);
setTimeout(() => {
// TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this._popover?.hidePopover();
}, 100);
}
override render() {
@@ -151,15 +158,12 @@ export class UmbCollectionViewBundleElement extends UmbLitElement {
css`
:host {
--uui-button-content-align: left;
--uui-menu-item-flat-structure: 1;
}
.filter-dropdown {
padding: var(--uui-size-space-3);
}
umb-icon {
display: inline-block;
}
`,
];
}

View File

@@ -1,5 +1,5 @@
import type { ManifestCollection } from '@umbraco-cms/backoffice/extension-registry';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { ManifestCollection } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbPaginationManager } from '@umbraco-cms/backoffice/utils';
export interface UmbCollectionBulkActionPermissions {

View File

@@ -18,6 +18,7 @@ import {
* @slot - Slot for main content
* @slot icon - Slot for icon
* @slot name - Slot for name
* @slot header - Slot for header element
* @slot footer - Slot for footer element
* @slot footer-info - Slot for info in the footer
* @slot actions - Slot for actions in the footer

View File

@@ -1,6 +1,7 @@
import { extractUmbColorVariable } from '../../resources/extractUmbColorVariable.function.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { html, customElement, property, state, ifDefined, css } from '@umbraco-cms/backoffice/external/lit';
import { html, customElement, property, state, ifDefined, css, styleMap } from '@umbraco-cms/backoffice/external/lit';
import type { StyleInfo } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
/**
@@ -10,45 +11,60 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
*/
@customElement('umb-icon')
export class UmbIconElement extends UmbLitElement {
#color?: string;
#fallbackColor?: string;
@state()
private _icon?: string;
@state()
private _color?: string;
private _style: StyleInfo = {};
/**
* Color alias or a color code directly.\
* If a color has been set via the name property, this property will override it.
* */
@property({ type: String })
public set color(value: string) {
if (!value) return;
this.#setColorStyle(value);
this.#color = value;
this.#updateColorStyle();
}
public get color(): string {
return this._color ?? '';
}
#setColorStyle(value: string) {
const alias = value.replace('color-', '');
const variable = extractUmbColorVariable(alias);
this._color = alias ? (variable ? `--uui-icon-color: var(${variable})` : `--uui-icon-color: ${alias}`) : undefined;
public get color(): string | undefined {
return this.#color || this.#fallbackColor;
}
/**
* The icon name. Can be appended with a color.\
* Example **icon-heart color-red**
*/
@property({ type: String })
public set name(value: string | undefined) {
const [icon, alias] = value ? value.split(' ') : [];
if (alias) {
this.#setColorStyle(alias);
} else {
this._color = undefined;
}
const [icon, color] = value ? value.split(' ') : [];
this.#fallbackColor = color;
this._icon = icon;
this.#updateColorStyle();
}
public get name(): string | undefined {
return this._icon;
}
#updateColorStyle() {
const value = this.#color || this.#fallbackColor;
if (!value) {
this._style = { '--uui-icon-color': 'inherit' };
return;
}
const color = value.replace('color-', '');
const variable = extractUmbColorVariable(color);
const styling = variable ? `var(${variable})` : color;
this._style = { '--uui-icon-color': styling };
}
override render() {
return html`<uui-icon name=${ifDefined(this._icon)} style=${ifDefined(this._color)}></uui-icon>`;
return html`<uui-icon name=${ifDefined(this._icon)} style=${styleMap(this._style)}></uui-icon>`;
}
static override styles = [

View File

@@ -119,7 +119,7 @@ export class UmbInputNumberRangeElement extends UmbFormControlMixin(UmbLitElemen
label=${this.maxLabel}
min=${ifDefined(this.validationRange?.min)}
max=${ifDefined(this.validationRange?.max)}
placeholder=${this.validationRange?.max === Infinity ? '∞' : this.validationRange?.max ?? ''}
placeholder=${this.validationRange?.max === Infinity ? '∞' : (this.validationRange?.max ?? '')}
.value=${this._maxValue}
@input=${this.#onMaxInput}></uui-input>
`;

View File

@@ -8,6 +8,7 @@ import {
query,
state,
} from '@umbraco-cms/backoffice/external/lit';
import { clamp } from '@umbraco-cms/backoffice/utils';
/**
* Custom element for a split panel with adjustable divider.
@@ -89,13 +90,9 @@ export class UmbSplitPanelElement extends LitElement {
}
}
#clamp(value: number, min: number, max: number) {
return Math.min(Math.max(value, min), max);
}
#setPosition(pos: number) {
const { width } = this.mainElement.getBoundingClientRect();
const localPos = this.#clamp(pos, 0, width);
const localPos = clamp(pos, 0, width);
const percentagePos = (localPos / width) * 100;
this.position = percentagePos + '%';
}
@@ -127,7 +124,7 @@ export class UmbSplitPanelElement extends LitElement {
const move = (event: PointerEvent) => {
const { clientX } = event;
const { left, width } = this.mainElement.getBoundingClientRect();
const localPos = this.#clamp(clientX - left, 0, width);
const localPos = clamp(clientX - left, 0, width);
const mappedPos = mapXAxisToSnap(localPos, width);
this.#lockedPanelWidth = this.lock === 'start' ? mappedPos : width - mappedPos;

View File

@@ -279,7 +279,7 @@ export class UmbTableElement extends LitElement {
const element = document.createElement('umb-ufm-render') as UmbUfmRenderElement;
element.inline = true;
element.markdown = column.labelTemplate;
element.value = value;
element.value = { value };
return element;
}

View File

@@ -1,19 +1,17 @@
import type { UmbPropertyEditorConfig } from '../../../property-editor/index.js';
import type { UmbPropertyTypeModel } from '../../types.js';
import { css, customElement, html, ifDefined, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbContentPropertyContext } from '@umbraco-cms/backoffice/content';
import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type';
import { UmbDataTypeDetailRepository } from '@umbraco-cms/backoffice/data-type';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, ifDefined, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbDataTypeDetailModel } from '@umbraco-cms/backoffice/data-type';
import type { UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
@customElement('umb-property-type-based-property')
export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
@property({ type: Object, attribute: false })
public get property(): UmbPropertyTypeModel | undefined {
return this._property;
}
public set property(value: UmbPropertyTypeModel | undefined) {
const oldProperty = this._property;
this._property = value;
@@ -21,6 +19,9 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
this._observeDataType(this._property?.dataType.unique);
}
}
public get property(): UmbPropertyTypeModel | undefined {
return this._property;
}
private _property?: UmbPropertyTypeModel;
@property({ type: String, attribute: 'data-path' })
@@ -73,16 +74,19 @@ export class UmbPropertyTypeBasedPropertyElement extends UmbLitElement {
}
override render() {
return this._propertyEditorUiAlias && this._property?.alias
? html`<umb-property
.dataPath=${this.dataPath}
.alias=${this._property.alias}
.label=${this._property.name}
.description=${this._property.description ?? undefined}
.appearance=${this._property.appearance}
property-editor-ui-alias=${ifDefined(this._propertyEditorUiAlias)}
.config=${this._dataTypeData}></umb-property>`
: '';
if (!this._propertyEditorUiAlias || !this._property?.alias) return;
return html`
<umb-property
.dataPath=${this.dataPath}
.alias=${this._property.alias}
.label=${this._property.name}
.description=${this._property.description ?? undefined}
.appearance=${this._property.appearance}
property-editor-ui-alias=${ifDefined(this._propertyEditorUiAlias)}
.config=${this._dataTypeData}
.validation=${this._property.validation}>
</umb-property>
`;
}
static override styles = [

View File

@@ -1,5 +1,5 @@
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbContentWorkspaceContext } from './content-workspace-context.interface.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export const UMB_CONTENT_WORKSPACE_CONTEXT = new UmbContextToken<
UmbContentWorkspaceContext,

View File

@@ -0,0 +1,7 @@
import type { UmbRepositoryErrorResponse } from '../../../repository/types.js';
import type { UmbBulkDuplicateToRequestArgs } from './types.js';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
export interface UmbBulkDuplicateToRepository extends UmbApi {
requestBulkDuplicateTo(args: UmbBulkDuplicateToRequestArgs): Promise<UmbRepositoryErrorResponse>;
}

View File

@@ -0,0 +1,23 @@
import { UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST } from '../../default/default.action.kind.js';
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifest: UmbBackofficeManifestKind = {
type: 'kind',
alias: 'Umb.Kind.EntityBulkAction.DuplicateTo',
matchKind: 'duplicateTo',
matchType: 'entityBulkAction',
manifest: {
...UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST.manifest,
type: 'entityBulkAction',
kind: 'duplicateTo',
api: () => import('./duplicate-to.action.js'),
weight: 700,
forEntityTypes: [],
meta: {
icon: 'icon-enter',
label: '#actions_copyTo',
bulkDuplicateRepositoryAlias: '',
treeAlias: '',
},
},
};

View File

@@ -0,0 +1,66 @@
import type { UmbBulkDuplicateToRepository } from './duplicate-to-repository.interface.js';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action';
import {
UmbRequestReloadChildrenOfEntityEvent,
UmbRequestReloadStructureForEntityEvent,
} from '@umbraco-cms/backoffice/entity-action';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UMB_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/tree';
import type { MetaEntityBulkActionDuplicateToKind } from '@umbraco-cms/backoffice/extension-registry';
export class UmbMediaDuplicateEntityBulkAction extends UmbEntityBulkActionBase<MetaEntityBulkActionDuplicateToKind> {
async execute() {
if (this.selection?.length === 0) return;
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_TREE_PICKER_MODAL, {
data: {
foldersOnly: this.args.meta.foldersOnly,
hideTreeRoot: this.args.meta.hideTreeRoot,
treeAlias: this.args.meta.treeAlias,
},
});
const value = await modalContext.onSubmit().catch(() => undefined);
if (!value?.selection?.length) return;
const destinationUnique = value.selection[0];
if (destinationUnique === undefined) throw new Error('Destination Unique is not available');
const bulkDuplicateRepository = await createExtensionApiByAlias<UmbBulkDuplicateToRepository>(
this,
this.args.meta.bulkDuplicateRepositoryAlias,
);
if (!bulkDuplicateRepository) throw new Error('Bulk Duplicate Repository is not available');
await bulkDuplicateRepository.requestBulkDuplicateTo({
uniques: this.selection,
destination: { unique: destinationUnique },
});
const entityContext = await this.getContext(UMB_ENTITY_CONTEXT);
if (!entityContext) throw new Error('Entity Context is not available');
const entityType = entityContext.getEntityType();
const unique = entityContext.getUnique();
if (entityType && unique !== undefined) {
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) throw new Error('Event Context is not available');
const args = { entityType, unique };
const reloadChildren = new UmbRequestReloadChildrenOfEntityEvent(args);
eventContext.dispatchEvent(reloadChildren);
const reloadStructure = new UmbRequestReloadStructureForEntityEvent(args);
eventContext.dispatchEvent(reloadStructure);
}
}
}
export { UmbMediaDuplicateEntityBulkAction as api };

View File

@@ -0,0 +1,2 @@
export type { UmbBulkDuplicateToRepository } from './duplicate-to-repository.interface.js';
export type { UmbBulkDuplicateToRequestArgs } from './types.js';

View File

@@ -0,0 +1,4 @@
import { manifest as duplicateToKindManifest } from './duplicate-to.action.kind.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [duplicateToKindManifest];

View File

@@ -0,0 +1,6 @@
export interface UmbBulkDuplicateToRequestArgs {
uniques: Array<string>;
destination: {
unique: string | null;
};
}

View File

@@ -0,0 +1,3 @@
export * from './duplicate-to/index.js';
export * from './move-to/index.js';
export * from './trash/index.js';

View File

@@ -0,0 +1,2 @@
export type { UmbBulkMoveToRepository } from './move-to-repository.interface.js';
export type { UmbBulkMoveToRequestArgs } from './types.js';

View File

@@ -0,0 +1,4 @@
import { manifest as moveToKindManifest } from './move-to.action.kind.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [moveToKindManifest];

View File

@@ -0,0 +1,7 @@
import type { UmbRepositoryErrorResponse } from '../../../repository/types.js';
import type { UmbBulkMoveToRequestArgs } from './types.js';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
export interface UmbBulkMoveToRepository extends UmbApi {
requestBulkMoveTo(args: UmbBulkMoveToRequestArgs): Promise<UmbRepositoryErrorResponse>;
}

View File

@@ -0,0 +1,23 @@
import { UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST } from '../../default/default.action.kind.js';
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifest: UmbBackofficeManifestKind = {
type: 'kind',
alias: 'Umb.Kind.EntityBulkAction.MoveTo',
matchKind: 'moveTo',
matchType: 'entityBulkAction',
manifest: {
...UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST.manifest,
type: 'entityBulkAction',
kind: 'moveTo',
api: () => import('./move-to.action.js'),
weight: 700,
forEntityTypes: [],
meta: {
icon: 'icon-enter',
label: '#actions_move',
bulkMoveRepositoryAlias: '',
treeAlias: '',
},
},
};

View File

@@ -0,0 +1,63 @@
import type { UmbBulkMoveToRepository } from './move-to-repository.interface.js';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action';
import {
UmbRequestReloadChildrenOfEntityEvent,
UmbRequestReloadStructureForEntityEvent,
} from '@umbraco-cms/backoffice/entity-action';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UMB_TREE_PICKER_MODAL } from '@umbraco-cms/backoffice/tree';
import type { MetaEntityBulkActionMoveToKind } from '@umbraco-cms/backoffice/extension-registry';
export class UmbMediaMoveEntityBulkAction extends UmbEntityBulkActionBase<MetaEntityBulkActionMoveToKind> {
async execute() {
if (this.selection?.length === 0) return;
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const modalContext = modalManager.open(this, UMB_TREE_PICKER_MODAL, {
data: {
foldersOnly: this.args.meta.foldersOnly,
hideTreeRoot: this.args.meta.hideTreeRoot,
treeAlias: this.args.meta.treeAlias,
},
});
const value = await modalContext.onSubmit().catch(() => undefined);
if (!value?.selection?.length) return;
const destinationUnique = value.selection[0];
if (destinationUnique === undefined) throw new Error('Destination Unique is not available');
const bulkMoveRepository = await createExtensionApiByAlias<UmbBulkMoveToRepository>(
this,
this.args.meta.bulkMoveRepositoryAlias,
);
if (!bulkMoveRepository) throw new Error('Bulk Move Repository is not available');
await bulkMoveRepository.requestBulkMoveTo({ uniques: this.selection, destination: { unique: destinationUnique } });
const entityContext = await this.getContext(UMB_ENTITY_CONTEXT);
if (!entityContext) throw new Error('Entity Context is not available');
const entityType = entityContext.getEntityType();
const unique = entityContext.getUnique();
if (entityType && unique !== undefined) {
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) throw new Error('Event Context is not available');
const args = { entityType, unique };
const reloadChildren = new UmbRequestReloadChildrenOfEntityEvent(args);
eventContext.dispatchEvent(reloadChildren);
const reloadStructure = new UmbRequestReloadStructureForEntityEvent(args);
eventContext.dispatchEvent(reloadStructure);
}
}
}
export { UmbMediaMoveEntityBulkAction as api };

View File

@@ -0,0 +1,6 @@
export interface UmbBulkMoveToRequestArgs {
uniques: Array<string>;
destination: {
unique: string | null;
};
}

View File

@@ -0,0 +1,2 @@
export type { UmbBulkTrashRepository } from './trash-repository.interface.js';
export type { UmbBulkTrashRequestArgs } from './types.js';

View File

@@ -0,0 +1,4 @@
import { manifest as trashKindManifest } from './trash.action.kind.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [trashKindManifest];

View File

@@ -0,0 +1,7 @@
import type { UmbRepositoryErrorResponse } from '../../../repository/types.js';
import type { UmbBulkTrashRequestArgs } from './types.js';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
export interface UmbBulkTrashRepository extends UmbApi {
requestBulkTrash(args: UmbBulkTrashRequestArgs): Promise<UmbRepositoryErrorResponse>;
}

View File

@@ -0,0 +1,22 @@
import { UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST } from '../../default/default.action.kind.js';
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifest: UmbBackofficeManifestKind = {
type: 'kind',
alias: 'Umb.Kind.EntityBulkAction.Trash',
matchKind: 'trash',
matchType: 'entityBulkAction',
manifest: {
...UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST.manifest,
type: 'entityBulkAction',
kind: 'trash',
api: () => import('./trash.action.js'),
weight: 700,
forEntityTypes: [],
meta: {
icon: 'icon-trash',
label: '#actions_trash',
bulkTrashRepositoryAlias: '',
},
},
};

View File

@@ -0,0 +1,53 @@
import type { UmbBulkTrashRepository } from './trash-repository.interface.js';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';
import { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action';
import {
UmbRequestReloadChildrenOfEntityEvent,
UmbRequestReloadStructureForEntityEvent,
} from '@umbraco-cms/backoffice/entity-action';
import { UMB_ACTION_EVENT_CONTEXT } from '@umbraco-cms/backoffice/action';
import { UMB_ENTITY_CONTEXT } from '@umbraco-cms/backoffice/entity';
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
import type { MetaEntityBulkActionTrashKind } from '@umbraco-cms/backoffice/extension-registry';
export class UmbMediaTrashEntityBulkAction extends UmbEntityBulkActionBase<MetaEntityBulkActionTrashKind> {
async execute() {
if (this.selection?.length === 0) return;
await umbConfirmModal(this._host, {
headline: `Trash`,
content: `Are you sure you want to move ${this.selection.length} ${this.selection.length === 1 ? 'item' : 'items'} to the recycle bin?`,
color: 'danger',
confirmLabel: 'Trash',
});
const bulkTrashRepository = await createExtensionApiByAlias<UmbBulkTrashRepository>(
this,
this.args.meta.bulkTrashRepositoryAlias,
);
if (!bulkTrashRepository) throw new Error('Bulk Trash Repository is not available');
await bulkTrashRepository.requestBulkTrash({ uniques: this.selection });
const entityContext = await this.getContext(UMB_ENTITY_CONTEXT);
if (!entityContext) throw new Error('Entity Context is not available');
const entityType = entityContext.getEntityType();
const unique = entityContext.getUnique();
if (entityType && unique !== undefined) {
const eventContext = await this.getContext(UMB_ACTION_EVENT_CONTEXT);
if (!eventContext) throw new Error('Event Context is not available');
const args = { entityType, unique };
const reloadChildren = new UmbRequestReloadChildrenOfEntityEvent(args);
eventContext.dispatchEvent(reloadChildren);
const reloadStructure = new UmbRequestReloadStructureForEntityEvent(args);
eventContext.dispatchEvent(reloadStructure);
}
}
}
export { UmbMediaTrashEntityBulkAction as api };

View File

@@ -0,0 +1,3 @@
export interface UmbBulkTrashRequestArgs {
uniques: Array<string>;
}

View File

@@ -0,0 +1,20 @@
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST: UmbBackofficeManifestKind = {
type: 'kind',
alias: 'Umb.Kind.EntityBulkAction.Default',
matchKind: 'default',
matchType: 'entityBulkAction',
manifest: {
type: 'entityBulkAction',
kind: 'default',
weight: 1000,
element: () => import('../entity-bulk-action.element.js'),
meta: {
icon: '',
label: 'Default Entity Bulk Action',
},
},
};
export const manifest = UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST;

View File

@@ -0,0 +1,4 @@
import { manifest as defaultKindManifest } from './default.action.kind.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [defaultKindManifest];

View File

@@ -0,0 +1,3 @@
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
export interface UmbEntityBulkActionElement extends UmbControllerHostElement {}

View File

@@ -1,16 +1,25 @@
import type { UmbEntityBulkActionBase } from './entity-bulk-action-base.js';
import type { UmbEntityBulkAction } from './entity-bulk-action.interface.js';
import type { UmbEntityBulkActionElement } from './entity-bulk-action-element.interface.js';
import { html, customElement, property, when } from '@umbraco-cms/backoffice/external/lit';
import { UmbActionExecutedEvent } from '@umbraco-cms/backoffice/event';
import { html, ifDefined, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import type { ManifestEntityBulkAction, MetaEntityBulkAction } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type {
ManifestEntityBulkAction,
MetaEntityBulkActionDefaultKind,
} from '@umbraco-cms/backoffice/extension-registry';
@customElement('umb-entity-bulk-action')
export class UmbEntityBulkActionElement<
MetaType extends MetaEntityBulkAction = MetaEntityBulkAction,
ApiType extends UmbEntityBulkActionBase<MetaType> = UmbEntityBulkActionBase<MetaType>,
> extends UmbLitElement {
const elementName = 'umb-entity-bulk-action';
@customElement(elementName)
export class UmbEntityBulkActionDefaultElement<
MetaType extends MetaEntityBulkActionDefaultKind = MetaEntityBulkActionDefaultKind,
ApiType extends UmbEntityBulkAction<MetaType> = UmbEntityBulkAction<MetaType>,
>
extends UmbLitElement
implements UmbEntityBulkActionElement
{
@property({ attribute: false })
manifest?: ManifestEntityBulkAction<MetaEntityBulkAction>;
manifest?: ManifestEntityBulkAction<MetaType>;
api?: ApiType;
@@ -22,16 +31,19 @@ export class UmbEntityBulkActionElement<
}
override render() {
return html`<uui-button
@click=${this.#onClick}
label=${ifDefined(this.manifest?.meta.label)}
color="default"
look="secondary"></uui-button>`;
return html`
<uui-button color="default" look="secondary" @click=${this.#onClick}>
${when(this.manifest?.meta.icon, () => html`<uui-icon name=${this.manifest!.meta.icon}></uui-icon>`)}
<span>${this.localize.string(this.manifest?.meta.label ?? '')}</span>
</uui-button>
`;
}
}
export default UmbEntityBulkActionDefaultElement;
declare global {
interface HTMLElementTagNameMap {
'umb-entity-bulk-action': UmbEntityBulkActionElement;
[elementName]: UmbEntityBulkActionDefaultElement;
}
}

View File

@@ -1,4 +1,8 @@
export * from './types.js';
export * from './common/index.js';
export * from './entity-bulk-action-base.js';
export * from './entity-bulk-action.element.js';
export * from './entity-bulk-action.interface.js';
export type * from './entity-bulk-action-element.interface.js';
export { UMB_ENTITY_BULK_ACTION_DEFAULT_KIND_MANIFEST } from './default/default.action.kind.js';

View File

@@ -0,0 +1,12 @@
import { manifests as defaultEntityBulkActionManifests } from './default/manifests.js';
import { manifests as duplicateEntityBulkActionManifests } from './common/duplicate-to/manifests.js';
import { manifests as moveToEntityBulkActionManifests } from './common/move-to/manifests.js';
import { manifests as trashEntityBulkActionManifests } from './common/trash/manifests.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
...defaultEntityBulkActionManifests,
...duplicateEntityBulkActionManifests,
...moveToEntityBulkActionManifests,
...trashEntityBulkActionManifests,
];

View File

@@ -1,6 +1,6 @@
import type { ConditionTypes } from '../conditions/types.js';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import type { UmbEntityBulkActionBase } from '@umbraco-cms/backoffice/entity-bulk-action';
import type { UmbEntityBulkActionElement } from '../../entity-bulk-action/entity-bulk-action-element.interface.js';
import type { UmbEntityBulkAction } from '@umbraco-cms/backoffice/entity-bulk-action';
import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbraco-cms/backoffice/extension-api';
/**
@@ -8,14 +8,31 @@ import type { ManifestElementAndApi, ManifestWithDynamicConditions } from '@umbr
* For example for content you may wish to move one or more documents in bulk
*/
export interface ManifestEntityBulkAction<MetaType extends MetaEntityBulkAction = MetaEntityBulkAction>
extends ManifestElementAndApi<UmbControllerHostElement, UmbEntityBulkActionBase<MetaType>>,
extends ManifestElementAndApi<UmbEntityBulkActionElement, UmbEntityBulkAction<MetaType>>,
ManifestWithDynamicConditions<ConditionTypes> {
type: 'entityBulkAction';
forEntityTypes: Array<string>;
meta: MetaType;
}
export interface MetaEntityBulkAction {
export interface MetaEntityBulkAction {}
export interface ManifestEntityBulkActionDefaultKind extends ManifestEntityBulkAction<MetaEntityBulkActionDefaultKind> {
type: 'entityBulkAction';
kind: 'default';
}
export interface MetaEntityBulkActionDefaultKind extends MetaEntityBulkAction {
/**
* An icon to represent the action to be performed
*
* @examples [
* "icon-box",
* "icon-grid"
* ]
*/
icon: string;
/**
* The friendly name of the action to perform
*
@@ -26,3 +43,40 @@ export interface MetaEntityBulkAction {
*/
label?: string;
}
// DUPLICATE TO
export interface ManifestEntityBulkActionDuplicateToKind
extends ManifestEntityBulkAction<MetaEntityBulkActionDuplicateToKind> {
type: 'entityBulkAction';
kind: 'duplicateTo';
}
export interface MetaEntityBulkActionDuplicateToKind extends ManifestEntityBulkAction {
bulkDuplicateRepositoryAlias: string;
hideTreeRoot?: boolean;
foldersOnly?: boolean;
treeAlias: string;
}
// MOVE TO
export interface ManifestEntityBulkActionMoveToKind extends ManifestEntityBulkAction<MetaEntityBulkActionMoveToKind> {
type: 'entityBulkAction';
kind: 'moveTo';
}
export interface MetaEntityBulkActionMoveToKind extends MetaEntityBulkActionDefaultKind {
bulkMoveRepositoryAlias: string;
hideTreeRoot?: boolean;
foldersOnly?: boolean;
treeAlias: string;
}
// TRASH
export interface ManifestEntityBulkActionTrashKind extends ManifestEntityBulkAction<MetaEntityBulkActionTrashKind> {
type: 'entityBulkAction';
kind: 'trash';
}
export interface MetaEntityBulkActionTrashKind extends MetaEntityBulkActionDefaultKind {
bulkTrashRepositoryAlias: string;
}

View File

@@ -1,8 +1,8 @@
import type { ManifestApi, UmbApi } from '@umbraco-cms/backoffice/extension-api';
import type { Tokens } from '@umbraco-cms/backoffice/external/marked';
import type { UfmToken } from '@umbraco-cms/backoffice/ufm';
export interface UmbUfmComponentApi extends UmbApi {
render(token: Tokens.Generic): string | undefined;
render(token: UfmToken): string | undefined;
}
export interface MetaUfmComponent {

View File

@@ -1098,7 +1098,7 @@
},
{
"name": "icon-home",
"file": "home.svg"
"file": "house.svg"
},
{
"name": "icon-hourglass",
@@ -1810,8 +1810,7 @@
},
{
"name": "icon-readonly",
"file": "ban.svg",
"legacy": true
"file": "pencil-off.svg"
},
{
"name": "icon-receipt-alt",

View File

@@ -1504,7 +1504,7 @@ name: "icon-re-post",
path: () => import("./icons/icon-re-post.js"),
},{
name: "icon-readonly",
legacy: true,
path: () => import("./icons/icon-readonly.js"),
},{
name: "icon-receipt-alt",

View File

@@ -1,4 +1,4 @@
export default `<!-- @license lucide-static v0.379.0 - ISC -->
export default `<!-- @license lucide-static v0.408.0 - ISC -->
<svg
class="lucide lucide-activity"
xmlns="http://www.w3.org/2000/svg"

Some files were not shown because too many files have changed in this diff Show More