Merge remote-tracking branch 'origin/main' into feature/image-cropper-editor-modal

This commit is contained in:
Lone Iversen
2024-05-24 13:08:51 +02:00
785 changed files with 3099 additions and 2389 deletions

View File

@@ -24,4 +24,7 @@ export class WorkspaceContextCounter extends UmbControllerBase {
export const api = WorkspaceContextCounter;
// Declare a Context Token that other elements can use to request the WorkspaceContextCounter:
export const EXAMPLE_COUNTER_CONTEXT = new UmbContextToken<WorkspaceContextCounter>('example.workspaceContext.counter');
export const EXAMPLE_COUNTER_CONTEXT = new UmbContextToken<WorkspaceContextCounter>(
'UmbWorkspaceContext',
'example.workspaceContext.counter',
);

View File

@@ -1,4 +1,4 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en-us" dir="ltr">
<head>
<base href="/" />
@@ -7,6 +7,7 @@
<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

@@ -11,6 +11,7 @@
"./controller-api": "./dist-cms/libs/controller-api/index.js",
"./element-api": "./dist-cms/libs/element-api/index.js",
"./extension-api": "./dist-cms/libs/extension-api/index.js",
"./formatting-api": "./dist-cms/libs/formatting-api/index.js",
"./localization-api": "./dist-cms/libs/localization-api/index.js",
"./observable-api": "./dist-cms/libs/observable-api/index.js",
"./action": "./dist-cms/packages/core/action/index.js",
@@ -41,7 +42,7 @@
"./extension-registry": "./dist-cms/packages/core/extension-registry/index.js",
"./icon": "./dist-cms/packages/core/icon-registry/index.js",
"./id": "./dist-cms/packages/core/id/index.js",
"./imaging": "./dist-cms/packages/core/imaging/index.js",
"./imaging": "./dist-cms/packages/media/imaging/index.js",
"./language": "./dist-cms/packages/language/index.js",
"./lit-element": "./dist-cms/packages/core/lit-element/index.js",
"./localization": "./dist-cms/packages/core/localization/index.js",
@@ -170,21 +171,21 @@
"npm": ">=10.1 < 11"
},
"dependencies": {
"@types/diff": "^5.0.9",
"@types/diff": "^5.2.1",
"@types/dompurify": "^3.0.5",
"@types/uuid": "^9.0.8",
"@umbraco-ui/uui": "1.8.0-rc.3",
"@umbraco-ui/uui-css": "1.8.0-rc.0",
"@umbraco-ui/uui": "1.8.1",
"@umbraco-ui/uui-css": "1.8.0",
"base64-js": "^1.5.1",
"diff": "^5.2.0",
"dompurify": "^3.0.9",
"element-internals-polyfill": "^1.3.10",
"lit": "^3.1.2",
"marked": "^12.0.0",
"monaco-editor": "^0.46.0",
"dompurify": "^3.1.4",
"element-internals-polyfill": "^1.3.11",
"lit": "^3.1.3",
"marked": "^12.0.2",
"monaco-editor": "^0.48.0",
"rxjs": "^7.8.1",
"tinymce": "^6.8.3",
"tinymce-i18n": "^24.1.29",
"tinymce-i18n": "^24.5.8",
"uuid": "^9.0.1"
},
"devDependencies": {
@@ -206,7 +207,7 @@
"@storybook/web-components-vite": "^7.6.17",
"@types/chai": "^4.3.5",
"@types/mocha": "^10.0.1",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/eslint-plugin": "^7.1.0",
"@typescript-eslint/parser": "^7.1.0",
"@web/dev-server-esbuild": "^1.0.2",
"@web/dev-server-import-maps": "^0.2.0",
@@ -224,7 +225,7 @@
"eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-wc": "^2.0.4",
"glob": "^10.3.10",
"lucide-static": "^0.367.0",
"lucide-static": "^0.379.0",
"msw": "^1.3.2",
"playwright-msw": "^3.0.1",
"prettier": "3.2.5",
@@ -235,12 +236,12 @@
"rollup-plugin-esbuild": "^6.1.1",
"rollup-plugin-import-css": "^3.5.0",
"rollup-plugin-web-worker-loader": "^1.6.1",
"simple-icons": "^11.11.0",
"simple-icons": "^11.15.0",
"storybook": "^7.6.17",
"tiny-glob": "^0.2.9",
"tsc-alias": "^1.8.8",
"typedoc": "^0.25.10",
"typescript": "^5.3.3",
"typescript": "^5.4.5",
"typescript-json-schema": "^0.63.0",
"vite": "^5.2.9",
"vite-plugin-static-copy": "^1.0.2",

View File

@@ -4,6 +4,7 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { setStoredPath } from '@umbraco-cms/backoffice/utils';
export class UmbAppAuthController extends UmbControllerBase {
#authContext?: typeof UMB_AUTH_CONTEXT.TYPE;
@@ -65,6 +66,14 @@ export class UmbAppAuthController extends UmbControllerBase {
throw new Error('[Fatal] Auth context is not available');
}
// Save the current state
let currentUrl = window.location.href;
const searchParams = new URLSearchParams(window.location.search);
if (searchParams.has('returnPath')) {
currentUrl = decodeURIComponent(searchParams.get('returnPath') || currentUrl);
}
setStoredPath(currentUrl);
// Figure out which providers are available
const availableProviders = await firstValueFrom(this.#authContext.getAuthProviders(umbExtensionsRegistry));

View File

@@ -4,7 +4,7 @@ import { UmbAppContext } from './app.context.js';
import { UmbServerConnection } from './server-connection.js';
import { UmbAppAuthController } from './app-auth.controller.js';
import type { UMB_AUTH_CONTEXT } from '@umbraco-cms/backoffice/auth';
import { UMB_STORAGE_REDIRECT_URL, UmbAuthContext } from '@umbraco-cms/backoffice/auth';
import { UmbAuthContext } from '@umbraco-cms/backoffice/auth';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import { UUIIconRegistryEssential } from '@umbraco-cms/backoffice/external/uui';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -18,6 +18,7 @@ import {
umbExtensionsRegistry,
} from '@umbraco-cms/backoffice/extension-registry';
import { filter, first, firstValueFrom } from '@umbraco-cms/backoffice/external/rxjs';
import { hasOwnOpener, retrieveStoredPath } from '@umbraco-cms/backoffice/utils';
@customElement('umb-app')
export class UmbAppElement extends UmbLitElement {
@@ -76,7 +77,7 @@ export class UmbAppElement extends UmbLitElement {
// The authorization request will be completed in the active window (main or popup) and the authorization signal will be sent.
// If we are in a popup window, the storage event in UmbAuthContext will catch the signal and close the window.
// If we are in the main window, the signal will be caught right here and the user will be redirected to the root.
if (window.opener) {
if (hasOwnOpener(this.backofficePath)) {
(component as UmbAppErrorElement).errorMessage = hasCode
? this.localize.term('errors_externalLoginSuccess')
: this.localize.term('errors_externalLoginFailed');
@@ -87,13 +88,14 @@ export class UmbAppElement extends UmbLitElement {
this.observe(this.#authContext.authorizationSignal, () => {
// Redirect to the saved state or root
let currentRoute = '';
const savedRoute = sessionStorage.getItem(UMB_STORAGE_REDIRECT_URL);
if (savedRoute) {
sessionStorage.removeItem(UMB_STORAGE_REDIRECT_URL);
currentRoute = savedRoute.endsWith('logout') ? currentRoute : savedRoute;
const url = retrieveStoredPath();
const isBackofficePath = url?.pathname.startsWith(this.backofficePath) ?? true;
if (isBackofficePath) {
history.replaceState(null, '', url?.toString() ?? '');
} else {
window.location.href = url?.toString() ?? this.backofficePath;
}
history.replaceState(null, '', currentRoute);
});
}
@@ -173,9 +175,13 @@ export class UmbAppElement extends UmbLitElement {
// Try to initialise the auth flow and get the runtime status
try {
// If the runtime level is "install" we should clear any cached tokens
// If the runtime level is "install" or ?status=false is set, we should clear any cached tokens
// else we should try and set the auth status
if (this.#serverConnection.getStatus() === RuntimeLevelModel.INSTALL) {
const searchParams = new URLSearchParams(window.location.search);
if (
(searchParams.has('status') && searchParams.get('status') === 'false') ||
this.#serverConnection.getStatus() === RuntimeLevelModel.INSTALL
) {
await this.#authContext.clearTokenStorage();
} else {
await this.#setAuthStatus();

View File

@@ -44,6 +44,7 @@ export class UmbBackofficeHeaderLogoElement extends UmbLitElement {
UmbTextStyles,
css`
#logo {
display: var(--umb-header-logo-display, inline);
--uui-button-padding-top-factor: 1;
--uui-button-padding-bottom-factor: 0.5;
margin-right: var(--uui-size-space-2);

View File

@@ -20,7 +20,7 @@ export class UmbBackofficeHeaderElement extends UmbLitElement {
}
#appHeader {
background-color: var(--uui-color-header-surface);
background-color: var(--umb-header-background-color, var(--uui-color-header-surface));
display: flex;
align-items: center;
justify-content: space-between;

View File

@@ -858,6 +858,7 @@ export default {
upload: 'Upload',
url: 'URL',
user: 'Bruger',
users: 'Brugere',
username: 'Brugernavn',
value: 'Værdi',
view: 'Vis',
@@ -876,6 +877,10 @@ export default {
saving: 'Gemmer...',
current: 'nuværende',
embed: 'Indlejring',
addEditLink: 'Tilføj/Rediger Link',
removeLink: 'Fjern Link',
mediaPicker: 'Mediebibliotek',
viewSourceCode: 'Vis kildekode',
selected: 'valgt',
other: 'Andet',
articles: 'Artikler',
@@ -1789,6 +1794,7 @@ export default {
partialViews: 'Partial Views',
partialViewMacros: 'Partial View makrofiler',
repositories: 'Installer fra "repository"',
relations: 'Relationer',
runway: 'Installer Runway',
runwayModules: 'Runway-moduler',
scripting: 'Scripting filer',
@@ -1839,6 +1845,7 @@ export default {
failedPasswordAttempts: 'Fejlede loginforsøg',
goToProfile: 'Gå til brugerprofil',
groupsHelp: 'Tilføj grupper for at tildele adgang og tilladelser',
invite: 'Invitér',
inviteAnotherUser: 'Invitér anden bruger',
inviteUserHelp:
'Invitér nye brugere til at give dem adgang til Umbraco. En invitation vil blive sendt\n via e-mail til brugeren med oplysninger om, hvordan man logger ind i Umbraco.\n ',
@@ -1881,6 +1888,9 @@ export default {
searchAllChildren: "Søg alle 'børn'",
languagesHelp: 'Tilføj sprog for at give brugerne adgang til at redigere',
sectionsHelp: 'Tilføj sektioner for at give brugerne adgang',
allowAccessToAllLanguages: 'Tillad adgang til alle sprog',
allowAccessToAllDocuments: 'Tillad adgang til alle dokumenter',
allowAccessToAllMedia: 'Tillad adgang til alle medier',
selectUserGroup: (multiple: boolean) => {
return multiple ? 'Vælg brugergrupper' : 'Vælg brugergruppe';
},
@@ -1898,6 +1908,7 @@ export default {
username: 'Navn',
userPermissions: 'Brugertilladelser',
usergroup: 'Brugergruppe',
usergroups: 'Brugergrupper',
userInvited: 'er blevet inviteret',
userInvitedSuccessHelp:
'En invitation er blevet sendt til den nye bruger med oplysninger om, hvordan man\n logger ind i Umbraco.\n ',
@@ -1955,7 +1966,6 @@ export default {
passwordRequiresUniqueChars: 'The password must use at least %0% different characters',
passwordRequiresUpper: "The password must have at least one uppercase ('A'-'Z')",
passwordTooShort: 'The password must be at least %0% characters long',
allowAccessToAllLanguages: 'Allow access to all languages',
userHasPassword: 'The user already has a password set',
userHasGroup: "The user is already in group '%0%'",
userLockoutNotEnabled: 'Lockout is not enabled for this user',
@@ -2459,6 +2469,8 @@ export default {
getStarted: 'To get you started',
},
settingsDashboard: {
documentationHeader: 'Dokumentation',
documentationDescription: 'Læs mere om at arbejde med elementerne i Indstillinger i vores Dokumentation.',
communityHeader: 'Community',
communityDescription: 'Stil et spørgsmål i community forummet eller i vores Discord community',
trainingHeader: 'Træning',
@@ -2479,6 +2491,20 @@ export default {
fallbackDescription:
"Thank you for choosing Umbraco - we think this could be the beginning of something\n beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast\n as possible.\n ",
},
welcomeDashboard: {
ourUmbracoHeadline: 'Our Umbraco - Det Venligste Fællesskab',
ourUmbracoDescription:
'Our Umbraco, den officielle fællesskabsplatform, er dit komplette sted for alt, hvad der vedrører Umbraco. Uanset om du har brug for svar på spørgsmål, spændende tilføjelser eller vejledninger til at udføre noget i Umbraco, er verdens bedste og venligste fællesskab kun et klik væk.',
ourUmbracoButton: 'Besøg Our Umbraco',
documentationHeadline: 'Dokumentation',
documentationDescription: 'Find svarene på alle dine Umbraco-spørgsmål',
communityHeadline: 'Fællesskab',
communityDescription: 'Få support og inspiration fra engagerede Umbraco-eksperter',
resourcesHeadline: 'Ressourcer',
resourcesDescription: "Gratis video tutorials til at kickstarte din rejse med CMS'et",
trainingHeadline: 'Træning',
trainingDescription: 'Praktisk træning og officielle certificeringer fra Umbraco',
},
analytics: {
consentForAnalytics: 'Consent for telemetry data',
analyticsLevelSavedSuccess: 'Telemetry level saved!',

View File

@@ -878,6 +878,7 @@ export default {
upload: 'Upload',
url: 'URL',
user: 'User',
users: 'Users',
username: 'Username',
validate: 'Validate',
value: 'Value',
@@ -896,6 +897,10 @@ export default {
saving: 'Saving...',
current: 'current',
embed: 'Embed',
addEditLink: 'Add/Edit Link',
removeLink: 'Remove Link',
mediaPicker: 'Media Picker',
viewSourceCode: 'View Source Code',
selected: 'selected',
other: 'Other',
articles: 'Articles',
@@ -1803,6 +1808,7 @@ export default {
partialViews: 'Partial Views',
partialViewMacros: 'Partial View Macro Files',
repositories: 'Install from repository',
relations: 'Relations',
runway: 'Install Runway',
runwayModules: 'Runway modules',
scripting: 'Scripting Files',
@@ -1854,6 +1860,7 @@ export default {
failedPasswordAttempts: 'Failed login attempts',
goToProfile: 'Go to user profile',
groupsHelp: 'Add groups to assign access and permissions',
invite: 'Invite',
inviteAnotherUser: 'Invite another user',
inviteUserHelp:
'Invite new users to give them access to Umbraco. An invite email will be sent to the\n user with information on how to log in to Umbraco. Invites last for 72 hours.\n ',
@@ -1928,6 +1935,7 @@ export default {
userNotInGroup: "The user is not in group '%0%'",
userPermissions: 'User permissions',
usergroup: 'User group',
usergroups: 'User groups',
userInvited: 'has been invited',
userInvitedSuccessHelp:
'An invitation has been sent to the new user with details about how to log in to\n Umbraco.\n ',
@@ -2330,6 +2338,20 @@ export default {
fallbackDescription:
"Thank you for choosing Umbraco - we think this could be the beginning of something\n beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast\n as possible.\n ",
},
welcomeDashboard: {
ourUmbracoHeadline: 'Our Umbraco - The Friendliest Community',
ourUmbracoDescription:
"Our Umbraco, the official community site, is your one-stop-shop for everything Umbraco. Whether you need a question answered, cool plugins, or a guide of how to do something in Umbraco, the world's best and friendliest community is just a click away.",
ourUmbracoButton: 'Visit Our Umbraco',
documentationHeadline: 'Documentation',
documentationDescription: 'Find the answers to all your Umbraco questions',
communityHeadline: 'Community',
communityDescription: 'Get support and inspiration from driven Umbraco experts',
resourcesHeadline: 'Resources',
resourcesDescription: 'Free video tutorials to jumpstart your journey with the CMS',
trainingHeadline: 'Training',
trainingDescription: 'Real-life training and official Umbraco certifications',
},
blockEditor: {
headlineCreateBlock: 'Pick Element Type',
headlineAddSettingsElementType: 'Attach a settings Element Type',

View File

@@ -885,6 +885,7 @@ export default {
upload: 'Upload',
url: 'URL',
user: 'User',
users: 'Users',
username: 'Username',
value: 'Value',
view: 'View',
@@ -902,6 +903,10 @@ export default {
saving: 'Saving...',
current: 'current',
embed: 'Embed',
addEditLink: 'Add/Edit Link',
removeLink: 'Remove Link',
mediaPicker: 'Media Picker',
viewSourceCode: 'View Source Code',
selected: 'selected',
other: 'Other',
articles: 'Articles',
@@ -1677,6 +1682,7 @@ export default {
elementDoesNotSupport: 'This is not applicable for an Element Type',
propertyHasChanges: 'You have made changes to this property. Are you sure you want to discard them?',
displaySettingsHeadline: 'Appearance',
displaySettingsLabelOnLeft: 'Label to the left',
displaySettingsLabelOnTop: 'Label above (full-width)',
confirmDeleteTabMessage: 'Are you sure you want to delete the tab <strong>%0%</strong>?',
confirmDeleteGroupMessage: 'Are you sure you want to delete the group <strong>%0%</strong>?',
@@ -1847,6 +1853,7 @@ export default {
partialViews: 'Partial Views',
partialViewMacros: 'Partial View Macro Files',
repositories: 'Install from repository',
relations: 'Relations',
runway: 'Install Runway',
runwayModules: 'Runway modules',
scripting: 'Scripting Files',
@@ -1900,6 +1907,7 @@ export default {
failedPasswordAttempts: 'Failed login attempts',
goToProfile: 'Go to user profile',
groupsHelp: 'Add groups to assign access and permissions',
invite: 'Invite',
inviteAnotherUser: 'Invite another user',
inviteUserHelp:
'Invite new users to give them access to Umbraco. An invite email will be sent to the\n user with information on how to log in to Umbraco. Invites last for 72 hours.\n ',
@@ -1974,6 +1982,7 @@ export default {
userNotInGroup: "The user is not in group '%0%'",
userPermissions: 'User permissions',
usergroup: 'User group',
usergroups: 'User groups',
userInvited: 'has been invited',
userInvitedSuccessHelp:
'An invitation has been sent to the new user with details about how to log in to\n Umbraco.\n ',
@@ -2026,6 +2035,7 @@ export default {
},
validation: {
validation: 'Validation',
validateNothing: 'No validation',
validateAsEmail: 'Validate as an email address',
validateAsNumber: 'Validate as a number',
validateAsUrl: 'Validate as a URL',
@@ -2385,6 +2395,20 @@ export default {
fallbackDescription:
"Thank you for choosing Umbraco - we think this could be the beginning of something\n beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast\n as possible.\n ",
},
welcomeDashboard: {
ourUmbracoHeadline: 'Our Umbraco - The Friendliest Community',
ourUmbracoDescription:
"Our Umbraco, the official community site, is your one-stop-shop for everything Umbraco. Whether you need a question answered, cool plugins, or a guide of how to do something in Umbraco, the world's best and friendliest community is just a click away.",
ourUmbracoButton: 'Visit Our Umbraco',
documentationHeadline: 'Documentation',
documentationDescription: 'Find the answers to all your Umbraco questions',
communityHeadline: 'Community',
communityDescription: 'Get support and inspiration from driven Umbraco experts',
resourcesHeadline: 'Resources',
resourcesDescription: 'Free video tutorials to jumpstart your journey with the CMS',
trainingHeadline: 'Training',
trainingDescription: 'Real-life training and official Umbraco certifications',
},
blockEditor: {
headlineCreateBlock: 'Pick Element Type',
headlineAddSettingsElementType: 'Attach a settings Element Type',

Binary file not shown.

Before

Width:  |  Height:  |  Size: 559 KiB

After

Width:  |  Height:  |  Size: 287 KiB

View File

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

View File

@@ -901,6 +901,12 @@ export type EnableUserRequestModel = {
userIds: Array<ReferenceByIdModel>
};
export type EntityImportAnalysisResponseModel = {
entityType: string
alias?: string | null
key?: string | null
};
export enum EventMessageTypeModel {
DEFAULT = 'Default',
INFO = 'Info',
@@ -1005,6 +1011,14 @@ export type ImportDictionaryRequestModel = {
parent?: ReferenceByIdModel | null
};
export type ImportDocumentTypeRequestModel = {
file: ReferenceByIdModel
};
export type ImportMediaTypeRequestModel = {
file: ReferenceByIdModel
};
export type IndexResponseModel = {
name: string
healthStatus: HealthStatusResponseModel
@@ -3081,6 +3095,15 @@ PostDocumentTypeByIdCopy: {
id: string
requestBody?: CopyDocumentTypeRequestModel
};
GetDocumentTypeByIdExport: {
id: string
};
PutDocumentTypeByIdImport: {
id: string
requestBody?: ImportDocumentTypeRequestModel
};
PutDocumentTypeByIdMove: {
id: string
@@ -3112,6 +3135,10 @@ PutDocumentTypeFolderById: {
id: string
requestBody?: UpdateFolderResponseModel
};
PostDocumentTypeImport: {
requestBody?: ImportDocumentTypeRequestModel
};
GetItemDocumentType: {
id?: Array<string>
@@ -3152,6 +3179,8 @@ take?: number
,GetDocumentTypeByIdBlueprint: PagedDocumentTypeBlueprintItemResponseModel
,GetDocumentTypeByIdCompositionReferences: Array<DocumentTypeCompositionResponseModel>
,PostDocumentTypeByIdCopy: string
,GetDocumentTypeByIdExport: Blob | File
,PutDocumentTypeByIdImport: string
,PutDocumentTypeByIdMove: string
,GetDocumentTypeAllowedAtRoot: PagedAllowedDocumentTypeModel
,PostDocumentTypeAvailableCompositions: Array<AvailableDocumentTypeCompositionResponseModel>
@@ -3160,6 +3189,7 @@ take?: number
,GetDocumentTypeFolderById: FolderResponseModel
,DeleteDocumentTypeFolderById: string
,PutDocumentTypeFolderById: string
,PostDocumentTypeImport: string
,GetItemDocumentType: Array<DocumentTypeItemResponseModel>
,GetItemDocumentTypeSearch: PagedModelDocumentTypeItemResponseModel
,GetTreeDocumentTypeAncestors: Array<DocumentTypeTreeItemResponseModel>
@@ -3537,6 +3567,23 @@ width?: number
}
export type ImportData = {
payloads: {
GetImportAnalyze: {
temporaryFileId?: string
};
}
responses: {
GetImportAnalyze: EntityImportAnalysisResponseModel
}
}
export type IndexerData = {
payloads: {
@@ -3727,6 +3774,11 @@ GetItemMediaTypeAllowed: {
skip?: number
take?: number
};
GetItemMediaTypeFolders: {
skip?: number
take?: number
};
GetItemMediaTypeSearch: {
query?: string
@@ -3765,6 +3817,15 @@ PostMediaTypeByIdCopy: {
id: string
requestBody?: CopyMediaTypeRequestModel
};
GetMediaTypeByIdExport: {
id: string
};
PutMediaTypeByIdImport: {
id: string
requestBody?: ImportMediaTypeRequestModel
};
PutMediaTypeByIdMove: {
id: string
@@ -3796,6 +3857,10 @@ PutMediaTypeFolderById: {
id: string
requestBody?: UpdateFolderResponseModel
};
PostMediaTypeImport: {
requestBody?: ImportMediaTypeRequestModel
};
GetTreeMediaTypeAncestors: {
descendantId?: string
@@ -3820,6 +3885,7 @@ take?: number
responses: {
GetItemMediaType: Array<MediaTypeItemResponseModel>
,GetItemMediaTypeAllowed: PagedModelMediaTypeItemResponseModel
,GetItemMediaTypeFolders: PagedModelMediaTypeItemResponseModel
,GetItemMediaTypeSearch: PagedModelMediaTypeItemResponseModel
,PostMediaType: string
,GetMediaTypeById: MediaTypeResponseModel
@@ -3828,6 +3894,8 @@ take?: number
,GetMediaTypeByIdAllowedChildren: PagedAllowedMediaTypeModel
,GetMediaTypeByIdCompositionReferences: Array<MediaTypeCompositionResponseModel>
,PostMediaTypeByIdCopy: string
,GetMediaTypeByIdExport: Blob | File
,PutMediaTypeByIdImport: string
,PutMediaTypeByIdMove: string
,GetMediaTypeAllowedAtRoot: PagedAllowedMediaTypeModel
,PostMediaTypeAvailableCompositions: Array<AvailableMediaTypeCompositionResponseModel>
@@ -3835,6 +3903,7 @@ take?: number
,GetMediaTypeFolderById: FolderResponseModel
,DeleteMediaTypeFolderById: string
,PutMediaTypeFolderById: string
,PostMediaTypeImport: string
,GetTreeMediaTypeAncestors: Array<MediaTypeTreeItemResponseModel>
,GetTreeMediaTypeChildren: PagedMediaTypeTreeItemResponseModel
,GetTreeMediaTypeRoot: PagedMediaTypeTreeItemResponseModel

View File

@@ -1,7 +1,7 @@
import type { CancelablePromise } from './core/CancelablePromise';
import { OpenAPI } from './core/OpenAPI';
import { request as __request } from './core/request';
import type { CultureData, DataTypeData, DictionaryData, DocumentBlueprintData, DocumentTypeData, DocumentVersionData, DocumentData, DynamicRootData, HealthCheckData, HelpData, ImagingData, IndexerData, InstallData, LanguageData, LogViewerData, ManifestData, MediaTypeData, MediaData, MemberGroupData, MemberTypeData, MemberData, ModelsBuilderData, ObjectTypesData, OembedData, PackageData, PartialViewData, PreviewData, ProfilingData, PropertyTypeData, PublishedCacheData, RedirectManagementData, RelationTypeData, RelationData, ScriptData, SearcherData, SecurityData, SegmentData, ServerData, StaticFileData, StylesheetData, TagData, TelemetryData, TemplateData, TemporaryFileData, UpgradeData, UserDataData, UserGroupData, UserData, WebhookData } from './models';
import type { CultureData, DataTypeData, DictionaryData, DocumentBlueprintData, DocumentTypeData, DocumentVersionData, DocumentData, DynamicRootData, HealthCheckData, HelpData, ImagingData, ImportData, IndexerData, InstallData, LanguageData, LogViewerData, ManifestData, MediaTypeData, MediaData, MemberGroupData, MemberTypeData, MemberData, ModelsBuilderData, ObjectTypesData, OembedData, PackageData, PartialViewData, PreviewData, ProfilingData, PropertyTypeData, PublishedCacheData, RedirectManagementData, RelationTypeData, RelationData, ScriptData, SearcherData, SecurityData, SegmentData, ServerData, StaticFileData, StylesheetData, TagData, TelemetryData, TemplateData, TemporaryFileData, UpgradeData, UserDataData, UserGroupData, UserData, WebhookData } from './models';
export class CultureService {
@@ -1347,6 +1347,57 @@ requestBody
});
}
/**
* @returns unknown Success
* @throws ApiError
*/
public static getDocumentTypeByIdExport(data: DocumentTypeData['payloads']['GetDocumentTypeByIdExport']): CancelablePromise<DocumentTypeData['responses']['GetDocumentTypeByIdExport']> {
const {
id
} = data;
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/document-type/{id}/export',
path: {
id
},
errors: {
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns string Success
* @throws ApiError
*/
public static putDocumentTypeByIdImport(data: DocumentTypeData['payloads']['PutDocumentTypeByIdImport']): CancelablePromise<DocumentTypeData['responses']['PutDocumentTypeByIdImport']> {
const {
id,
requestBody
} = data;
return __request(OpenAPI, {
method: 'PUT',
url: '/umbraco/management/api/v1/document-type/{id}/import',
path: {
id
},
body: requestBody,
mediaType: 'application/json',
responseHeader: 'Umb-Notifications',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns string Success
* @throws ApiError
@@ -1535,6 +1586,30 @@ requestBody
});
}
/**
* @returns string Created
* @throws ApiError
*/
public static postDocumentTypeImport(data: DocumentTypeData['payloads']['PostDocumentTypeImport'] = {}): CancelablePromise<DocumentTypeData['responses']['PostDocumentTypeImport']> {
const {
requestBody
} = data;
return __request(OpenAPI, {
method: 'POST',
url: '/umbraco/management/api/v1/document-type/import',
body: requestBody,
mediaType: 'application/json',
responseHeader: 'Umb-Generated-Resource',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns unknown Success
* @throws ApiError
@@ -2946,6 +3021,33 @@ mode
}
export class ImportService {
/**
* @returns unknown Success
* @throws ApiError
*/
public static getImportAnalyze(data: ImportData['payloads']['GetImportAnalyze'] = {}): CancelablePromise<ImportData['responses']['GetImportAnalyze']> {
const {
temporaryFileId
} = data;
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/import/analyze',
query: {
temporaryFileId
},
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
404: `Not Found`,
},
});
}
}
export class IndexerService {
/**
@@ -3561,6 +3663,29 @@ take
});
}
/**
* @returns unknown Success
* @throws ApiError
*/
public static getItemMediaTypeFolders(data: MediaTypeData['payloads']['GetItemMediaTypeFolders'] = {}): CancelablePromise<MediaTypeData['responses']['GetItemMediaTypeFolders']> {
const {
skip,
take
} = data;
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/item/media-type/folders',
query: {
skip, take
},
errors: {
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
},
});
}
/**
* @returns unknown Success
* @throws ApiError
@@ -3764,6 +3889,57 @@ requestBody
});
}
/**
* @returns unknown Success
* @throws ApiError
*/
public static getMediaTypeByIdExport(data: MediaTypeData['payloads']['GetMediaTypeByIdExport']): CancelablePromise<MediaTypeData['responses']['GetMediaTypeByIdExport']> {
const {
id
} = data;
return __request(OpenAPI, {
method: 'GET',
url: '/umbraco/management/api/v1/media-type/{id}/export',
path: {
id
},
errors: {
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns string Success
* @throws ApiError
*/
public static putMediaTypeByIdImport(data: MediaTypeData['payloads']['PutMediaTypeByIdImport']): CancelablePromise<MediaTypeData['responses']['PutMediaTypeByIdImport']> {
const {
id,
requestBody
} = data;
return __request(OpenAPI, {
method: 'PUT',
url: '/umbraco/management/api/v1/media-type/{id}/import',
path: {
id
},
body: requestBody,
mediaType: 'application/json',
responseHeader: 'Umb-Notifications',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns string Success
* @throws ApiError
@@ -3936,6 +4112,30 @@ requestBody
});
}
/**
* @returns string Created
* @throws ApiError
*/
public static postMediaTypeImport(data: MediaTypeData['payloads']['PostMediaTypeImport'] = {}): CancelablePromise<MediaTypeData['responses']['PostMediaTypeImport']> {
const {
requestBody
} = data;
return __request(OpenAPI, {
method: 'POST',
url: '/umbraco/management/api/v1/media-type/import',
body: requestBody,
mediaType: 'application/json',
responseHeader: 'Umb-Generated-Resource',
errors: {
400: `Bad Request`,
401: `The resource is protected and requires an authentication token`,
403: `The authenticated user do not have access to this resource`,
404: `Not Found`,
},
});
}
/**
* @returns unknown Success
* @throws ApiError
@@ -6010,9 +6210,6 @@ export class PreviewService {
method: 'DELETE',
url: '/umbraco/management/api/v1/preview',
responseHeader: 'Umb-Notifications',
errors: {
401: `The resource is protected and requires an authentication token`,
},
});
}

View File

@@ -1,4 +1,3 @@
/* eslint local-rules/enforce-umbraco-external-imports: 0 */
import DOMPurify from 'dompurify';
export { DOMPurify };

View File

@@ -290,7 +290,7 @@ describe('UmbElementMixin', () => {
const check2: CheckType<A, string> = value as string;
const check3: CheckType<A, null> = value as null;
const check4: CheckType<A, undefined> = value as undefined;
expect(check).to.be.equal('hello');
expect(check).to.be.equal(null);
expect(check2 === check3 && check2 === check4).to.be.true; // Just to use the const for something.
});
// Because the source is potentially undefined, the controller could be undefined and the value of the callback method could be undefined [NL]

View File

@@ -11,7 +11,7 @@ export async function createExtensionElement<ElementType extends HTMLElement>(
const elementConstructor = await loadManifestElement<ElementType>(elementPropValue);
if (elementConstructor) {
return new elementConstructor();
} else {
} else if (!manifest.elementName) {
console.error(
`-- Extension of alias "${manifest.alias}" did not succeed creating an element class instance via the extension manifest property '${elementPropValue}'. The imported Element JS file did not export a 'element' or 'default'. Alternatively define the 'elementName' in the manifest.`,
manifest,

View File

@@ -0,0 +1,33 @@
import { DOMPurify } from '@umbraco-cms/backoffice/external/dompurify';
import { Marked } from '@umbraco-cms/backoffice/external/marked';
import { UmbControllerBase } from '@umbraco-cms/backoffice/class-api';
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
const UmbMarked = new Marked({ gfm: true, breaks: true });
const UmbDomPurify = DOMPurify(window);
const UmbDomPurifyConfig: DOMPurify.Config = { USE_PROFILES: { html: true } };
UmbDomPurify.addHook('afterSanitizeAttributes', function (node) {
// set all elements owning target to target=_blank
if ('target' in node) {
node.setAttribute('target', '_blank');
}
});
/**
* @description - Controller for formatting text.
*/
export class UmbFormattingController extends UmbControllerBase {
#localize = new UmbLocalizationController(this._host);
/**
* A method to localize the string input then transform any markdown to santized HTML.
*/
public transform(input?: string): string {
if (!input) return '';
const translated = this.#localize.string(input);
const markup = UmbMarked.parse(translated) as string;
const sanitized = UmbDomPurify.sanitize(markup, UmbDomPurifyConfig) as string;
return sanitized;
}
}

View File

@@ -0,0 +1,2 @@
export * from './formatting.controller.js';
export * from './localizeAndTransform.function.js';

View File

@@ -0,0 +1,6 @@
import { UmbFormattingController } from './formatting.controller.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
export function localizeAndTransform(host: UmbControllerHost, input: string): string {
return new UmbFormattingController(host).transform(input);
}

View File

@@ -35,6 +35,7 @@ export class UmbArrayState<T> extends UmbDeepState<T[]> {
*/
sortBy(sortMethod?: (a: T, b: T) => number) {
this.#sortMethod = sortMethod;
super.setValue(this.getValue().sort(this.#sortMethod));
return this;
}

View File

@@ -33,7 +33,7 @@ export class UmbBasicState<T> {
* console.log("Value is: ", myState.value);
*/
public get value(): BehaviorSubject<T>['value'] {
return this._subject.value;
return this.getValue();
}
/**

View File

@@ -13,15 +13,19 @@ import { UmbBasicState } from './basic-state.js';
* Additionally the Subject ensures the data is unique, not updating any Observes unless there is an actual change of the content.
*/
export class UmbDeepState<T> extends UmbBasicState<T> {
#mute?: boolean;
#value: T;
constructor(initialData: T) {
super(deepFreeze(initialData));
this.#value = this._subject.getValue();
}
/**
* @export
* @method createObservablePart
* @param {(mappable: T) => R} mappingFunction - Method to return the part for this Observable to return.
* @param {(previousResult: R, currentResult: R) => boolean} [memoizationFunction] - Method to Compare if the data has changed. Should return true when data is different.
* @returns {Observable<R>}
* @description - Creates an Observable from this State.
*/
asObservablePart<ReturnType>(
@@ -39,9 +43,63 @@ export class UmbDeepState<T> extends UmbBasicState<T> {
setValue(data: T): void {
if (!this._subject) return;
const frozenData = deepFreeze(data);
// Only update data if its different than current data.
if (!jsonStringComparison(frozenData, this._subject.getValue())) {
this.#value = frozenData;
// Only update data if its not muted and is different than current data. [NL]
if (!this.#mute && !jsonStringComparison(frozenData, this._subject.getValue())) {
this._subject.next(frozenData);
}
}
getValue(): T {
return this.#value;
}
/**
* @method mute
* @description - Set mute for this state.
*/
mute() {
if (this.#mute) return;
this.#mute = true;
}
/**
* @method unmute
* @description - Unset the mute of this state.
*/
unmute() {
if (!this.#mute) return;
this.#mute = false;
// Only update data if it is different than current data. [NL]
if (!jsonStringComparison(this.#value, this._subject.getValue())) {
this._subject?.next(this.#value);
}
}
/**
* @method isMuted
* @description - Check if the state is muted.
* @returns {boolean} - Returns true if the state is muted.
*/
isMuted() {
return this.#mute;
}
/**
* @method getMutePromise
* @description - Get a promise which resolves when the mute is unset.
* @returns {Promise<void>}
*/
getMutePromise() {
return new Promise<void>((resolve) => {
if (!this.#mute) {
resolve();
return;
}
const subscription = this._subject.subscribe(() => {
subscription.unsubscribe();
resolve();
});
});
}
}

View File

@@ -41,4 +41,50 @@ describe('UmbObjectState', () => {
subject.update({ key: 'change_this_first_should_not_trigger_update' });
subject.update({ another: 'myNewValue' });
});
it('replays the latests value when unmuted.', (done) => {
let amountOfCallbacks = 0;
const observer = subject.asObservable();
observer.subscribe((value) => {
amountOfCallbacks++;
if (amountOfCallbacks === 1) {
// First callback gives us the initialized value.
expect(value.key).to.be.equal('some');
}
if (amountOfCallbacks === 2) {
// Second callback gives us the first change.
expect(value.key).to.be.equal('firstChange');
}
if (amountOfCallbacks === 3) {
// Third callback gives us the last change before unmuted.
expect(value.key).to.be.equal('thirdChange');
done();
}
});
subject.update({ key: 'firstChange' });
subject.mute();
subject.update({ key: 'secondChange' });
subject.update({ key: 'thirdChange' });
subject.unmute();
});
/*
it('replays latests unmuted value when muted.', (done) => {
const observer = subject.asObservable();
observer.subscribe((value) => {
expect(value).to.be.equal(initialData);
});
subject.mute();
subject.update({ key: 'firstChange' });
observer.subscribe((value) => {
expect(value).to.be.equal(initialData);
done();
});
});
*/
});

View File

@@ -21,7 +21,7 @@ export class UmbObjectState<T> extends UmbDeepState<T> {
* myState.update({value: 'myNewValue'});
*/
update(partialData: Partial<T>) {
this.setValue({ ...this._subject.getValue(), ...partialData });
this.setValue({ ...this.getValue(), ...partialData });
return this;
}
}

View File

@@ -8,6 +8,7 @@ export * from './json-string-comparison.function.js';
export * from './merge-observables.function.js';
export * from './observe-multiple.function.js';
export * from './partial-update-frozen-array.function.js';
export * from './push-at-to-unique-array.function.js';
export * from './push-to-unique-array.function.js';
export * from './simple-hash-code.function.js';
export * from './strict-equality-memoization.function.js';

View File

@@ -17,7 +17,7 @@ import { UmbSorterController, type UmbSorterConfig, type resolvePlacementArgs }
*/
function resolvePlacementAsGrid(args: resolvePlacementArgs<UmbBlockGridLayoutModel, UmbBlockGridEntryElement>) {
// If this has areas, we do not want to move, unless we are at the edge
if (args.relatedModel.areas.length > 0 && isWithinRect(args.pointerX, args.pointerY, args.relatedRect, -10)) {
if (args.relatedModel.areas?.length > 0 && isWithinRect(args.pointerX, args.pointerY, args.relatedRect, -10)) {
return null;
}
@@ -162,10 +162,10 @@ export class UmbBlockGridEntriesElement extends UmbLitElement {
constructor() {
super();
this.observe(this.#context.layoutEntries, (layoutEntries) => {
const oldValue = this._layoutEntries;
//const oldValue = this._layoutEntries;
this.#sorter.setModel(layoutEntries);
this._layoutEntries = layoutEntries;
this.requestUpdate('layoutEntries', oldValue);
//this.requestUpdate('layoutEntries', oldValue);
});
this.observe(this.#context.amountOfAllowedBlockTypes, (length) => {
@@ -197,7 +197,7 @@ export class UmbBlockGridEntriesElement extends UmbLitElement {
});
}
// TODO: Missing ability to jump directly to creating a Block, when there is only one Block Type.
// TODO: Missing ability to jump directly to creating a Block, when there is only one Block Type. [NL]
render() {
return html`
${this._styleElement}
@@ -208,7 +208,7 @@ export class UmbBlockGridEntriesElement extends UmbLitElement {
(layoutEntry, index) =>
html`<umb-block-grid-entry
class="umb-block-grid__layout-item"
.index=${index}
index=${index}
.contentUdi=${layoutEntry.contentUdi}
.layout=${layoutEntry}>
</umb-block-grid-entry>`,

View File

@@ -1,6 +1,7 @@
import { UmbBlockGridEntryContext } from '../../context/block-grid-entry.context.js';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { html, css, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import type { PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockViewPropsType } from '@umbraco-cms/backoffice/block';
import type { UmbBlockGridLayoutModel } from '@umbraco-cms/backoffice/block-grid';
@@ -14,7 +15,7 @@ import '../block-scale-handler/index.js';
@customElement('umb-block-grid-entry')
export class UmbBlockGridEntryElement extends UmbLitElement implements UmbPropertyEditorUiElement {
//
@property({ type: Number })
@property({ type: Number, reflect: true })
public get index(): number | undefined {
return this.#context.getIndex();
}
@@ -37,6 +38,7 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
//
#context = new UmbBlockGridEntryContext(this);
#renderTimeout: number | undefined;
@state()
_columnSpan?: number;
@@ -51,7 +53,9 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
// If _createPath is undefined, its because no blocks are allowed to be created here[NL]
@state()
_createPath?: string;
_createBeforePath?: string;
@state()
_createAfterPath?: string;
@state()
_label = '';
@@ -68,6 +72,13 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
@state()
_canScale?: boolean;
@state()
_showInlineCreateBefore?: boolean;
@state()
_showInlineCreateAfter?: boolean;
@state()
_inlineCreateAboveWidth?: string;
// TODO: use this type on the Element Interface for the Manifest.
@state()
_blockViewProps: UmbBlockViewPropsType<UmbBlockGridLayoutModel> = { contentUdi: undefined!, urls: {} }; // Set to undefined cause it will be set before we render.
@@ -110,10 +121,15 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
});
// Paths:
this.observe(this.#context.createPath, (createPath) => {
const oldValue = this._createPath;
this._createPath = createPath;
this.requestUpdate('_createPath', oldValue);
this.observe(this.#context.createBeforePath, (createPath) => {
//const oldValue = this._createBeforePath;
this._createBeforePath = createPath;
//this.requestUpdate('_createPath', oldValue);
});
this.observe(this.#context.createAfterPath, (createPath) => {
//const oldValue = this._createAfterPath;
this._createAfterPath = createPath;
//this.requestUpdate('_createPath', oldValue);
});
this.observe(this.#context.workspaceEditContentPath, (path) => {
this._workspaceEditContentPath = path;
@@ -156,8 +172,52 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
this.setAttribute('data-content-element-type-alias', contentElementTypeAlias);
}
});
this.#callUpdateInlineCreateButtons();
}
protected updated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
super.updated(_changedProperties);
if (_changedProperties.has('_blockViewProps') || _changedProperties.has('_columnSpan')) {
this.#callUpdateInlineCreateButtons();
}
}
#callUpdateInlineCreateButtons() {
clearTimeout(this.#renderTimeout);
this.#renderTimeout = setTimeout(this.#updateInlineCreateButtons, 100) as unknown as number;
}
#updateInlineCreateButtons = () => {
// TODO: Could we optimize this, so it wont break?, cause currently we trust blindly that parentElement is '.umb-block-grid__layout-container' [NL]
const layoutContainer = this.parentElement;
if (!layoutContainer) return;
const layoutContainerRect = layoutContainer.getBoundingClientRect();
if (layoutContainerRect.width === 0) {
this._showInlineCreateBefore = false;
this._showInlineCreateAfter = false;
this._inlineCreateAboveWidth = undefined;
this.#renderTimeout = setTimeout(this.#updateInlineCreateButtons, 100) as unknown as number;
return;
}
const layoutItemRect = this.getBoundingClientRect();
if (layoutItemRect.right > layoutContainerRect.right - 5) {
this._showInlineCreateAfter = false;
} else {
this._showInlineCreateAfter = true;
}
if (layoutItemRect.left > layoutContainerRect.left + 5) {
this._showInlineCreateBefore = false;
this._inlineCreateAboveWidth = undefined;
} else {
this._inlineCreateAboveWidth = getComputedStyle(layoutContainer).width;
this._showInlineCreateBefore = true;
}
};
#renderInlineEditBlock() {
return html`<umb-block-grid-block-inline
.contentUdi=${this.contentUdi}
@@ -171,8 +231,13 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
#renderBlock() {
return this.contentUdi
? html`
${this._createPath
? html`<uui-button-inline-create href=${this._createPath}></uui-button-inline-create>`
${this._createBeforePath && this._showInlineCreateBefore
? html`<uui-button-inline-create
href=${this._createBeforePath}
label=${this.localize.term('blockEditor_addBlock')}
style=${this._inlineCreateAboveWidth
? `width: ${this._inlineCreateAboveWidth}`
: ''}></uui-button-inline-create>`
: nothing}
<div class="umb-block-grid__block" part="umb-block-grid__block">
<umb-extension-slot
@@ -204,6 +269,12 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
</umb-block-scale-handler>`
: nothing}
</div>
${this._createAfterPath && this._showInlineCreateAfter
? html`<uui-button-inline-create
vertical
label=${this.localize.term('blockEditor_addBlock')}
href=${this._createAfterPath}></uui-button-inline-create>`
: nothing}
`
: nothing;
}
@@ -223,6 +294,24 @@ export class UmbBlockGridEntryElement extends UmbLitElement implements UmbProper
top: var(--uui-size-2);
right: var(--uui-size-2);
}
uui-button-inline-create {
top: 0px;
position: absolute;
// Avoid showing inline-create in dragging-mode
--umb-block-grid__block--inline-create-button-display--condition: var(--umb-block-grid--dragging-mode) none;
display: var(--umb-block-grid__block--inline-create-button-display--condition);
}
uui-button-inline-create:not([vertical]) {
left: 0;
width: var(--umb-block-grid-editor--inline-create-width, 100%);
}
:host(:not([index='0'])) uui-button-inline-create:not([vertical]) {
top: calc(var(--umb-block-grid--row-gap, 0px) * -0.5);
}
uui-button-inline-create[vertical] {
right: calc(1px - (var(--umb-block-grid--column-gap, 0px) * 0.5));
}
:host([drag-placeholder]) {
opacity: 0.2;

View File

@@ -171,6 +171,8 @@ export class UmbBlockGridEntriesContext
'observeThisLayouts',
);
this.removeUmbControllerByAlias('observeAreaType');
const hostEl = this.getHostElement() as HTMLElement | undefined;
if (hostEl) {
hostEl.removeAttribute('data-area-alias');
@@ -196,7 +198,9 @@ export class UmbBlockGridEntriesContext
this.observe(
this.#parentEntry.layoutsOfArea(this.#areaKey),
(layouts) => {
this._layoutEntries.setValue(layouts);
if (layouts) {
this._layoutEntries.setValue(layouts);
}
},
'observeParentLayouts',
);
@@ -244,6 +248,20 @@ export class UmbBlockGridEntriesContext
return this._catalogueRouteBuilderState.getValue()?.({ view: 'clipboard', index: index });
}
/*
async setLayouts(layouts: Array<UmbBlockGridLayoutModel>) {
await this._retrieveManager;
if (this.#areaKey === null) {
this._manager?.setLayouts(layouts);
} else {
if (!this.#parentUnique || !this.#areaKey) {
throw new Error('ParentUnique or AreaKey not set');
}
this._manager?.setLayoutsOfArea(this.#parentUnique, this.#areaKey, layouts);
}
}
*/
async create(
contentElementTypeKey: string,
partialLayoutEntry?: Omit<UmbBlockGridLayoutModel, 'contentUdi'>,

View File

@@ -31,14 +31,18 @@ export class UmbBlockGridEntryContext
{
//
readonly columnSpan = this._layout.asObservablePart((x) => x?.columnSpan);
readonly rowSpan = this._layout.asObservablePart((x) => x?.rowSpan ?? 1);
readonly rowSpan = this._layout.asObservablePart((x) => x?.rowSpan);
readonly layoutAreas = this._layout.asObservablePart((x) => x?.areas);
readonly columnSpanOptions = this._blockType.asObservablePart((x) => x?.columnSpanOptions ?? []);
readonly areaTypeGridColumns = this._blockType.asObservablePart((x) => x?.areaGridColumns);
readonly areas = this._blockType.asObservablePart((x) => x?.areas ?? []);
readonly minMaxRowSpan = this._blockType.asObservablePart((x) => [x?.rowMinSpan ?? 1, x?.rowMaxSpan ?? 1]);
public getMinMaxRowSpan(): [number, number] {
readonly minMaxRowSpan = this._blockType.asObservablePart((x) =>
x ? [x.rowMinSpan ?? 1, x.rowMaxSpan ?? 1] : undefined,
);
public getMinMaxRowSpan(): [number, number] | undefined {
const x = this._blockType.getValue();
return [x?.rowMinSpan ?? 1, x?.rowMaxSpan ?? 1];
if (!x) return undefined;
return [x.rowMinSpan ?? 1, x.rowMaxSpan ?? 1];
}
readonly inlineEditingMode = this._blockType.asObservablePart((x) => x?.inlineEditing === true);
@@ -65,18 +69,8 @@ export class UmbBlockGridEntryContext
super(host, UMB_BLOCK_GRID_MANAGER_CONTEXT, UMB_BLOCK_GRID_ENTRIES_CONTEXT);
}
protected _gotLayout(layout: UmbBlockGridLayoutModel | undefined) {
if (layout) {
// TODO: Implement size correction to fit with configurations. both for columnSpan and rowSpan.
layout = { ...layout };
layout.columnSpan ??= 999;
layout.areas ??= [];
}
return layout;
}
layoutsOfArea(areaKey: string) {
return this._layout.asObservablePart((x) => x?.areas.find((x) => x.key === areaKey)?.items ?? []);
return this._layout.asObservablePart((x) => x?.areas?.find((x) => x.key === areaKey)?.items);
}
areaType(areaKey: string) {
@@ -87,7 +81,7 @@ export class UmbBlockGridEntryContext
const frozenValue = this._layout.value;
if (!frozenValue) return;
const areas = appendToFrozenArray(
frozenValue?.areas,
frozenValue?.areas ?? [],
{
key: areaKey,
items: layouts,
@@ -97,40 +91,71 @@ export class UmbBlockGridEntryContext
this._layout.update({ areas });
}
/**
* Set the column span of this entry.
* @param columnSpan {number} The new column span.
*/
setColumnSpan(columnSpan: number) {
if (!this._entries) return;
const layoutColumns = this._entries.getLayoutColumns();
if (!layoutColumns) return;
columnSpan = Math.max(1, Math.min(columnSpan, layoutColumns));
this._layout.update({ columnSpan });
columnSpan = this.#calcColumnSpan(columnSpan, this.getRelevantColumnSpanOptions(), layoutColumns);
if (columnSpan === this.getColumnSpan()) return;
const layoutValue = this._layout.getValue();
if (!layoutValue) return;
this._layout.setValue({
...layoutValue,
columnSpan,
});
}
/**
* Get the column span of this entry.
* @returns {number} The column span.
*/
getColumnSpan() {
return this._layout.getValue()?.columnSpan;
}
/**
* Set the row span of this entry.
* @param rowSpan {number} The new row span.
*/
setRowSpan(rowSpan: number) {
const blockType = this._blockType.getValue();
if (!blockType) return;
rowSpan = Math.max(blockType.rowMinSpan, Math.min(rowSpan, blockType.rowMaxSpan));
this._layout.update({ rowSpan });
const minMax = this.getMinMaxRowSpan();
if (!minMax) return;
rowSpan = Math.max(minMax[0], Math.min(rowSpan, minMax[1]));
if (rowSpan === this.getRowSpan()) return;
const layoutValue = this._layout.getValue();
if (!layoutValue) return;
this._layout.setValue({
...layoutValue,
rowSpan,
});
}
/**
* Get the row span of this entry.
* @returns {number} The row span.
*/
getRowSpan() {
return this._layout.getValue()?.rowSpan;
}
_gotManager() {}
_gotManager() {
this.#gotEntriesAndManager();
}
_gotEntries() {
this.scaleManager.setEntriesContext(this._entries);
if (!this._entries) return;
this.#gotEntriesAndManager();
// Retrieve scale options:
this.observe(
observeMultiple([this.minMaxRowSpan, this.columnSpanOptions, this._entries.layoutColumns]),
([minMaxRowSpan, columnSpanOptions, layoutColumns]) => {
if (!layoutColumns) return;
if (!layoutColumns || !minMaxRowSpan) return;
const relevantColumnSpanOptions = columnSpanOptions
? columnSpanOptions
.filter((x) => x.columnSpan <= layoutColumns)
@@ -147,6 +172,7 @@ export class UmbBlockGridEntryContext
'observeScaleOptions',
);
// Retrieve The Grid Columns for the Areas:
this.observe(
observeMultiple([this.areaTypeGridColumns, this._entries.layoutColumns]),
([areaTypeGridColumns, layoutColumns]) => {
@@ -154,32 +180,90 @@ export class UmbBlockGridEntryContext
},
'observeAreaGridColumns',
);
}
#gotEntriesAndManager() {
if (!this._entries || !this._manager) return;
// Secure areas fits options:
this.observe(
observeMultiple([this.columnSpan, this.relevantColumnSpanOptions, this._entries.layoutColumns]),
([columnSpan, relevantColumnSpanOptions, layoutColumns]) => {
if (!columnSpan || !layoutColumns) return;
if (relevantColumnSpanOptions.length > 0) {
// Correct columnSpan so it fits.
const newColumnSpan =
closestColumnSpanOption(columnSpan, relevantColumnSpanOptions, layoutColumns) ?? layoutColumns;
if (newColumnSpan !== columnSpan) {
//this.setColumnSpan(newColumnSpan);
this._layout.update({ columnSpan: newColumnSpan });
}
} else {
// Reset to the layoutColumns.
if (layoutColumns !== columnSpan) {
//this.setColumnSpan(layoutColumns);
this._layout.update({ columnSpan: layoutColumns });
}
observeMultiple([this.areas, this.layoutAreas]),
([areas, layoutAreas]) => {
if (!areas || !layoutAreas) return;
const areasAreIdentical =
areas.length === layoutAreas.length && areas.every((area) => layoutAreas.some((y) => y.key === area.key));
if (areasAreIdentical === false) {
const layoutValue = this._layout.getValue();
if (!layoutValue) return;
this._layout.setValue({
...layoutValue,
areas: layoutAreas.map((x) => (areas.find((y) => y.key === x.key) ? x : { key: x.key, items: [] })),
});
}
},
'observeAreaValidation',
);
// Secure columnSpan fits options:
this.observe(
observeMultiple([this.layout, this.columnSpan, this.relevantColumnSpanOptions, this._entries.layoutColumns]),
([layout, columnSpan, relevantColumnSpanOptions, layoutColumns]) => {
if (!layout || !layoutColumns) return;
const newColumnSpan = this.#calcColumnSpan(
columnSpan ?? layoutColumns,
relevantColumnSpanOptions,
layoutColumns,
);
if (newColumnSpan !== columnSpan) {
const layoutValue = this._layout.getValue();
if (!layoutValue) return;
this._layout.setValue({
...layoutValue,
columnSpan: newColumnSpan,
});
}
},
'observeColumnSpanValidation',
);
// Secure rowSpan fits options:
this.observe(
observeMultiple([this.minMaxRowSpan, this.rowSpan]),
([minMax, rowSpan]) => {
if (minMax) {
const newRowSpan = Math.max(minMax[0], Math.min(rowSpan ?? 1, minMax[1]));
if (newRowSpan !== rowSpan) {
const layoutValue = this._layout.getValue();
if (!layoutValue) return;
this._layout.setValue({
...layoutValue,
rowSpan: newRowSpan,
});
}
}
},
'observeRowSpanValidation',
);
}
_gotContentType(contentType: UmbContentTypeModel | undefined) {
this.#firstPropertyType.setValue(contentType?.properties[0]);
}
#calcColumnSpan(columnSpan: number, relevantColumnSpanOptions: number[], layoutColumns: number) {
if (relevantColumnSpanOptions.length > 0) {
// Correct to a columnSpan option.
const newColumnSpan =
closestColumnSpanOption(columnSpan, relevantColumnSpanOptions, layoutColumns) ?? layoutColumns;
if (newColumnSpan !== columnSpan) {
return newColumnSpan;
}
} else {
// Reset to the layoutColumns.
if (layoutColumns !== columnSpan) {
return layoutColumns;
}
}
return columnSpan;
}
}

View File

@@ -1,7 +1,7 @@
import type { UmbBlockGridLayoutModel, UmbBlockGridTypeModel } from '../types.js';
import type { UmbBlockGridWorkspaceData } from '../index.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import { UmbArrayState, appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api';
import { UmbArrayState, appendToFrozenArray, pushAtToUniqueArray } from '@umbraco-cms/backoffice/observable-api';
import { type UmbBlockDataType, UmbBlockManagerContext } from '@umbraco-cms/backoffice/block';
import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type';
@@ -43,9 +43,10 @@ export class UmbBlockGridManagerContext<
/**
* Inserts a layout entry into an area of a layout entry.
* @param layoutEntry The layout entry to insert.
* @param content The content data to insert.
* @param settings The settings data to insert.
* @param modalData The modal data.
* @param entries The layout entries to search within.
* @param parentUnique The parentUnique to search for.
* @param areaKey The areaKey to insert the layout entry into.
* @param index The index to insert the layout entry at.
* @returns a updated layout entries array if the insert was successful.
*
* @remarks
@@ -63,53 +64,61 @@ export class UmbBlockGridManagerContext<
): Array<UmbBlockGridLayoutModel> | undefined {
// I'm sorry, this code is not easy to read or maintain [NL]
let i: number = entries.length;
while (--i) {
const layoutEntry = entries[i];
if (layoutEntry.contentUdi === parentId) {
while (i--) {
const currentEntry = entries[i];
// Lets check if we found the right parent layout entry:
if (currentEntry.contentUdi === parentId) {
// Append the layout entry to be inserted and unfreeze the rest of the data:
const areas = currentEntry.areas.map((x) =>
x.key === areaKey
? {
...x,
items: pushAtToUniqueArray([...x.items], insert, (x) => x.contentUdi === insert.contentUdi, index),
}
: x,
);
return appendToFrozenArray(
entries,
{
...layoutEntry,
areas: layoutEntry.areas.map((x) =>
x.key === areaKey ? { ...x, items: appendToFrozenArray(x.items, insert) } : x,
),
...currentEntry,
areas,
},
(x) => x.contentUdi === layoutEntry.contentUdi,
(x) => x.contentUdi === currentEntry.contentUdi,
);
}
let y: number = layoutEntry.areas?.length;
while (--y) {
// Otherwise check if any items of the areas are the parent layout entry we are looking for. We do so based on parentId, recursively:
let y: number = currentEntry.areas?.length;
while (y--) {
// Recursively ask the items of this area to insert the layout entry, if something returns there was a match in this branch. [NL]
const correctedAreaItems = this.#appendLayoutEntryToArea(
insert,
layoutEntry.areas[y].items,
currentEntry.areas[y].items,
parentId,
areaKey,
index,
);
if (correctedAreaItems) {
// This area got a corrected set of items, lets append those to the area and unfreeze the surrounding data:
const area = layoutEntry.areas[y];
const area = currentEntry.areas[y];
return appendToFrozenArray(
entries,
{
...layoutEntry,
...currentEntry,
areas: appendToFrozenArray(
layoutEntry.areas,
currentEntry.areas,
{ ...area, items: correctedAreaItems },
(z) => z.key === area.key,
),
},
(x) => x.contentUdi === layoutEntry.contentUdi,
(x) => x.contentUdi === currentEntry.contentUdi,
);
}
}
}
// Find layout entry based on parentId, recursively, as it needs to check layout of areas as well:
return undefined;
}
// TODO: Remove dependency on modalData object here. [NL] Maybe change it into requiring the originData object instead.
insert(
layoutEntry: BlockLayoutType,
content: UmbBlockDataType,

View File

@@ -9,7 +9,7 @@ export interface UmbBlockGridScalableContext extends UmbControllerHost {
setRowSpan: (rowSpan: number) => void;
getColumnSpan: () => number | undefined;
getRowSpan: () => number | undefined;
getMinMaxRowSpan: () => [number, number];
getMinMaxRowSpan: () => [number, number] | undefined;
getRelevantColumnSpanOptions: () => Array<number> | undefined;
}
@@ -128,14 +128,17 @@ export class UmbBlockGridScaleManager extends UmbControllerBase {
let newColumnSpan = Math.max(blockEndCol - blockStartCol, 1);
const spanOptions = this._host.getRelevantColumnSpanOptions();
if (!spanOptions) return;
const columnOptions = this._host.getRelevantColumnSpanOptions();
if (!columnOptions) return;
// Find nearest allowed Column:
const bestColumnSpanOption = closestColumnSpanOption(newColumnSpan, spanOptions, layoutColumns - blockStartCol);
const bestColumnSpanOption = closestColumnSpanOption(newColumnSpan, columnOptions, layoutColumns - blockStartCol);
newColumnSpan = bestColumnSpanOption ?? layoutColumns;
const [rowMinSpan, rowMaxSpan] = this._host.getMinMaxRowSpan();
// Find allowed row spans:
const minMaxRowSpan = this._host.getMinMaxRowSpan();
if (!minMaxRowSpan) return;
const [rowMinSpan, rowMaxSpan] = minMaxRowSpan;
let newRowSpan = Math.round(Math.max(blockEndRow - blockStartRow, rowMinSpan));
if (rowMaxSpan != null) {
newRowSpan = Math.min(newRowSpan, rowMaxSpan);

View File

@@ -41,18 +41,18 @@ export class UmbPropertyEditorUIBlockGridColumnSpanElement extends UmbLitElement
this._columnsArray,
(index) => index,
(index) => {
const number = index + 1;
const colNumber = index + 1;
let classes = 'default';
if (this.value && this.value.length > 0) {
const applied = this.value.find((column) => column.columnSpan >= index);
const picked = this.value.find((column) => column.columnSpan === index);
const applied = this.value.find((column) => column.columnSpan >= colNumber);
const picked = this.value.find((column) => column.columnSpan === colNumber);
classes = picked ? 'picked applied' : applied ? 'applied' : 'default';
}
return html`<div class="${classes}" data-index=${index}>
<span>${number}</span>
<button type="button" aria-label=${number} @click=${() => this.#pickColumn(index)}></button>
<span>${colNumber}</span>
<button type="button" aria-label=${colNumber} @click=${() => this.#pickColumn(colNumber)}></button>
</div>`;
},
);

View File

@@ -4,14 +4,13 @@ import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { html, customElement, property, state, css, type PropertyValueMap } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import {
UmbPropertyValueChangeEvent,
type UmbPropertyEditorConfigCollection,
} from '@umbraco-cms/backoffice/property-editor';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type';
import type { UmbBlockGridTypeModel, UmbBlockGridValueModel } from '@umbraco-cms/backoffice/block-grid';
import type { NumberRangeValueType } from '@umbraco-cms/backoffice/models';
import '../../components/block-grid-entries/index.js';
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
/**
* @element umb-property-editor-ui-block-grid
@@ -63,8 +62,8 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement
this._value = buildUpValue as UmbBlockGridValueModel;
this.#context.setLayouts(this._value.layout[UMB_BLOCK_GRID_PROPERTY_EDITOR_ALIAS] ?? []);
this.#context.setContents(buildUpValue.contentData);
this.#context.setSettings(buildUpValue.settingsData);
this.#context.setContents(this._value.contentData);
this.#context.setSettings(this._value.settingsData);
}
public get value(): UmbBlockGridValueModel {
return this._value;
@@ -73,30 +72,24 @@ export class UmbPropertyEditorUIBlockGridElement extends UmbLitElement implement
constructor() {
super();
// TODO: Prevent initial notification from these observes:
this.observe(this.#context.layouts, (layouts) => {
this._value = { ...this._value, layout: { [UMB_BLOCK_GRID_PROPERTY_EDITOR_ALIAS]: layouts } };
this.#fireChangeEvent();
});
this.observe(this.#context.contents, (contents) => {
this._value = { ...this._value, contentData: contents };
this.#fireChangeEvent();
});
this.observe(this.#context.settings, (settings) => {
this._value = { ...this._value, settingsData: settings };
this.#fireChangeEvent();
// TODO: Prevent initial notification from these observes
this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => {
this.observe(
observeMultiple([this.#context.layouts, this.#context.contents, this.#context.settings]),
([layouts, contents, settings]) => {
this._value = {
...this._value,
layout: { [UMB_BLOCK_GRID_PROPERTY_EDITOR_ALIAS]: layouts },
contentData: contents,
settingsData: settings,
};
propertyContext.setValue(this._value);
},
'motherObserver',
);
});
}
#debounceChangeEvent?: boolean;
#fireChangeEvent = async () => {
if (this.#debounceChangeEvent) return;
this.#debounceChangeEvent = true;
await Promise.resolve();
this.dispatchEvent(new UmbPropertyValueChangeEvent());
this.#debounceChangeEvent = false;
};
protected firstUpdated(_changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void {
super.firstUpdated(_changedProperties);

View File

@@ -87,6 +87,11 @@ export class UmbBlockListEntriesContext extends UmbBlockEntriesContext<
return this._catalogueRouteBuilderState.getValue()?.({ view: 'clipboard', index: index });
}
async setLayouts(layouts: Array<UmbBlockListLayoutModel>) {
await this._retrieveManager;
this._manager?.setLayouts(layouts);
}
async create(
contentElementTypeKey: string,
partialLayoutEntry?: Omit<UmbBlockListLayoutModel, 'contentUdi'>,

View File

@@ -121,17 +121,6 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
constructor() {
super();
/*
this.consumeContext(UMB_PROPERTY_CONTEXT, (propertyContext) => {
this.observe(
propertyContext?.alias,
(alias) => {
this.#catalogueModal.setUniquePathValue('propertyAlias', alias);
},
'observePropertyAlias',
);
});
*/
this.observe(this.#entriesContext.layoutEntries, (layouts) => {
this._layouts = layouts;
// Update sorter.
@@ -167,34 +156,10 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
this.observe(this.#entriesContext.catalogueRouteBuilder, (routeBuilder) => {
this._catalogueRouteBuilder = routeBuilder;
});
/*
this.#catalogueModal = new UmbModalRouteRegistrationController(this, UMB_BLOCK_CATALOGUE_MODAL)
.addUniquePaths(['propertyAlias'])
.addAdditionalPath(':view/:index')
.onSetup((routingInfo) => {
const index = routingInfo.index ? parseInt(routingInfo.index) : -1;
return {
data: {
blocks: this._blocks ?? [],
openClipboard: routingInfo.view === 'clipboard',
blockOriginData: { index: index },
},
};
})
.observeRouteBuilder((routeBuilder) => {
this._catalogueRouteBuilder = routeBuilder;
});
*/
}
#debounceChangeEvent?: boolean;
#fireChangeEvent = async () => {
if (this.#debounceChangeEvent) return;
this.#debounceChangeEvent = true;
await Promise.resolve();
#fireChangeEvent = () => {
this.dispatchEvent(new UmbPropertyValueChangeEvent());
this.#debounceChangeEvent = false;
};
render() {
@@ -211,6 +176,7 @@ export class UmbPropertyEditorUIBlockListElement extends UmbLitElement implement
(x) => x.contentUdi,
(layoutEntry, index) =>
html`<uui-button-inline-create
label=${this._createButtonLabel}
href=${this._catalogueRouteBuilder?.({ view: 'create', index: index }) ?? ''}></uui-button-inline-create>
<umb-block-list-entry .contentUdi=${layoutEntry.contentUdi} .layout=${layoutEntry}>
</umb-block-list-entry> `,

View File

@@ -128,7 +128,7 @@ export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeWith
return 'block name content element type here...';
}
setName(name: string | undefined) {
alert('You cannot set a name of a block-type.');
console.warn('You cannot set a name of a block type.');
}
async propertyValueByAlias<ReturnType = unknown>(propertyAlias: string) {

View File

@@ -84,6 +84,9 @@ export abstract class UmbBlockEntriesContext<
layoutOf(contentUdi: string) {
return this._layoutEntries.asObservablePart((source) => source.find((x) => x.contentUdi === contentUdi));
}
getLayoutOf(contentUdi: string) {
return this._layoutEntries.getValue().find((x) => x.contentUdi === contentUdi);
}
setLayouts(layouts: Array<BlockLayoutType>) {
return this._layoutEntries.setValue(layouts);
}
@@ -118,10 +121,9 @@ export abstract class UmbBlockEntriesContext<
}
if (layout.settingsUdi) {
this._manager?.removeOneSettings(layout.settingsUdi);
this._manager!.removeOneSettings(layout.settingsUdi);
}
this._manager?.removeOneContent(contentUdi);
this._manager!.removeOneContent(contentUdi);
this._layoutEntries.removeOne(contentUdi);
}

View File

@@ -61,8 +61,10 @@ export abstract class UmbBlockEntryContext<
this.#index.setValue(index);
}
#createPath = new UmbStringState(undefined);
readonly createPath = this.#createPath.asObservable();
#createBeforePath = new UmbStringState(undefined);
readonly createBeforePath = this.#createBeforePath.asObservable();
#createAfterPath = new UmbStringState(undefined);
readonly createAfterPath = this.#createAfterPath.asObservable();
#contentElementTypeName = new UmbStringState(undefined);
public readonly contentElementTypeName = this.#contentElementTypeName.asObservable();
@@ -173,7 +175,7 @@ export abstract class UmbBlockEntryContext<
});
this.observe(this.index, () => {
this.#updateCreatePath();
this.#updateCreatePaths();
});
}
@@ -181,16 +183,18 @@ export abstract class UmbBlockEntryContext<
return this._layout.value?.contentUdi;
}
#updateCreatePath() {
#updateCreatePaths() {
const index = this.#index.value;
if (this._entries && index !== undefined) {
this.observe(
observeMultiple([this._entries.catalogueRouteBuilder, this._entries.canCreate]),
([catalogueRouteBuilder, canCreate]) => {
if (catalogueRouteBuilder && canCreate) {
this.#createPath.setValue(this._entries!.getPathForCreateBlock(index));
this.#createBeforePath.setValue(this._entries!.getPathForCreateBlock(index));
this.#createAfterPath.setValue(this._entries!.getPathForCreateBlock(index + 1));
} else {
this.#createPath.setValue(undefined);
this.#createBeforePath.setValue(undefined);
this.#createAfterPath.setValue(undefined);
}
},
'observeRouteBuilderCreate',
@@ -204,7 +208,7 @@ export abstract class UmbBlockEntryContext<
this.observe(
this._entries.layoutOf(this.#contentUdi),
(layout) => {
this._layout.setValue(this._gotLayout(layout));
this._layout.setValue(layout);
},
'observeParentLayout',
);
@@ -219,10 +223,6 @@ export abstract class UmbBlockEntryContext<
);
}
protected _gotLayout(layout: BlockLayoutType | undefined) {
return layout;
}
#gotManager() {
this.#observeBlockType();
this.#observeData();
@@ -231,7 +231,7 @@ export abstract class UmbBlockEntryContext<
abstract _gotManager(): void;
#gotEntries() {
this.#updateCreatePath();
this.#updateCreatePaths();
this.#observeLayout();
if (this._entries) {
this.observe(
@@ -249,13 +249,11 @@ export abstract class UmbBlockEntryContext<
abstract _gotEntries(): void;
#observeData() {
if (!this._manager) return;
const contentUdi = this._layout.value?.contentUdi;
if (!contentUdi) return;
if (!this._manager || !this.#contentUdi) return;
// observe content:
this.observe(
this._manager.contentOf(contentUdi),
this._manager.contentOf(this.#contentUdi),
(content) => {
this.#content.setValue(content);
},
@@ -361,9 +359,11 @@ export abstract class UmbBlockEntryContext<
}
async requestDelete() {
const blockName = this.getLabel();
// TODO: Localizations missing [NL]
await umbConfirmModal(this, {
headline: `Delete ${this.getLabel()}`,
content: 'Are you sure you want to delete this [INSERT BLOCK TYPE NAME]?',
headline: `Delete ${blockName}`,
content: `Are you sure you want to delete this ${blockName}?`,
confirmLabel: 'Delete',
color: 'danger',
});

View File

@@ -164,9 +164,9 @@ export abstract class UmbBlockManagerContext<
return this.#contents.value.find((x) => x.udi === contentUdi);
}
/*setOneLayout(layoutData: BlockLayoutType) {
return this._layouts.appendOne(layoutData);
}*/
setOneLayout(layoutData: BlockLayoutType) {
this._layouts.appendOne(layoutData);
}
setOneContent(contentData: UmbBlockDataType) {
this.#contents.appendOne(contentData);
}

View File

@@ -65,9 +65,30 @@ export class UmbBlockElementManager extends UmbControllerBase {
}
async setPropertyValue(alias: string, value: unknown) {
this.initiatePropertyValueChange();
await this.#getDataPromise;
this.#data.update({ [alias]: value });
this.finishPropertyValueChange();
}
#updateLock = 0;
initiatePropertyValueChange() {
this.#updateLock++;
this.#data.mute();
// TODO: When ready enable this code will enable handling a finish automatically by this implementation 'using myState.initiatePropertyValueChange()' (Relies on TS support of Using) [NL]
/*return {
[Symbol.dispose]: this.finishPropertyValueChange,
};*/
}
finishPropertyValueChange = () => {
this.#updateLock--;
this.#triggerPropertyValueChanges();
};
#triggerPropertyValueChanges() {
if (this.#updateLock === 0) {
this.#data.unmute();
}
}
public createPropertyDatasetContext(host: UmbControllerHost) {

View File

@@ -242,7 +242,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
#establishLiveSync() {
this.observe(this.layout, (layoutData) => {
if (layoutData) {
this.#blockEntries?.setOneLayout(layoutData);
this.#blockManager?.setOneLayout(layoutData);
}
});
this.observe(this.content.data, (contentData) => {
@@ -324,7 +324,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
} else {
// Update data:
this.#blockEntries.setOneLayout(layoutData);
this.#blockManager.setOneLayout(layoutData);
if (contentData) {
this.#blockManager.setOneContent(contentData);
}
@@ -350,7 +350,7 @@ export class UmbBlockWorkspaceContext<LayoutDataType extends UmbBlockLayoutBaseM
} else {
// TODO: Revert the layout, content & settings data to the original state.
if (this.#initialLayout) {
this.#blockEntries?.setOneLayout(this.#initialLayout);
this.#blockManager?.setOneLayout(this.#initialLayout);
}
if (this.#initialContent) {
this.#blockManager?.setOneContent(this.#initialContent);

View File

@@ -1,6 +1,6 @@
import type { UmbBackofficeExtensionRegistry, ManifestAuthProvider } from '../extension-registry/index.js';
import { UmbAuthFlow } from './auth-flow.js';
import { UMB_AUTH_CONTEXT, UMB_STORAGE_REDIRECT_URL, UMB_STORAGE_TOKEN_RESPONSE_NAME } from './auth.context.token.js';
import { UMB_AUTH_CONTEXT, UMB_STORAGE_TOKEN_RESPONSE_NAME } from './auth.context.token.js';
import type { UmbOpenApiConfiguration } from './models/openApiConfiguration.js';
import { OpenAPI } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
@@ -106,9 +106,6 @@ export class UmbAuthContext extends UmbContextBase<UmbAuthContext> {
) {
const redirectUrl = await this.#authFlow.makeAuthorizationRequest(identityProvider, usernameHint);
if (redirect) {
// Save the current state
sessionStorage.setItem(UMB_STORAGE_REDIRECT_URL, window.location.href);
location.href = redirectUrl;
return;
}

View File

@@ -44,7 +44,7 @@ export class UmbAppAuthModalElement extends UmbModalBaseElement<UmbModalAppAuthC
aria-hidden="true"
part="auth-logo-background" />
<div id="graphic" aria-hidden="true">
<img part="auth-logo" id="logo-on-image" src="/umbraco/backoffice/assets/umbraco_logo_blue.svg" alt="Logo" />
<img part="auth-logo" id="logo-on-image" src="/umbraco/backoffice/assets/umbraco_logo_white.svg" alt="Logo" />
<svg
id="curve-top"
width="1746"

View File

@@ -71,7 +71,9 @@ export class UmbCollectionActionButtonElement extends UmbLitElement {
@click=${this._onClick}
look="outline"
color="default"
label=${this.manifest?.meta.label || ''}
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>
`;

View File

@@ -120,11 +120,13 @@ export class UmbDefaultCollectionContext<
this.pagination.setPageSize(this.#config.pageSize);
}
const filterValue = this.#filter.getValue() as FilterModelType;
this.#filter.setValue({
...this.#defaultFilter,
...this.#config,
...this.#filter.getValue(),
skip: 0,
...filterValue,
skip: filterValue.skip ?? 0,
take: this.#config.pageSize,
});

View File

@@ -1,11 +1,12 @@
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit';
import { css, html, LitElement, customElement, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import type {
ManifestHeaderAppButtonKind,
UmbBackofficeManifestKind,
} from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
const manifest: UmbBackofficeManifestKind = {
type: 'kind',
@@ -21,7 +22,7 @@ const manifest: UmbBackofficeManifestKind = {
umbExtensionsRegistry.register(manifest);
@customElement('umb-header-app-button')
export class UmbHeaderAppButtonElement extends LitElement {
export class UmbHeaderAppButtonElement extends UmbLitElement {
public manifest?: ManifestHeaderAppButtonKind;
render() {
@@ -41,7 +42,11 @@ export class UmbHeaderAppButtonElement extends LitElement {
css`
uui-button {
font-size: 18px;
--uui-button-background-color: transparent;
--uui-button-background-color: var(--umb-header-app-button-background-color, transparent);
--uui-button-background-color-hover: var(
--umb-header-app-button-background-color-hover,
var(--uui-color-emphasis)
);
}
`,
];

View File

@@ -21,7 +21,6 @@ export * from './input-number-range/index.js';
export * from './input-radio-button-list/index.js';
export * from './input-slider/index.js';
export * from './input-toggle/index.js';
export * from './input-upload-field/index.js';
export * from './input-with-alias/input-with-alias.element.js';
export * from './multiple-color-picker-input/index.js';
export * from './multiple-text-string-input/index.js';

View File

@@ -94,12 +94,6 @@ export class UmbInputEntityElement extends UUIFormControlMixin(UmbLitElement, ''
constructor() {
super();
}
connectedCallback() {
super.connectedCallback();
if (!this.#pickerContext) return;
this.addValidator(
'rangeUnderflow',

View File

@@ -23,25 +23,25 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
// TODO: Or should they come from a extension point? [NL]
@state() private _customValidationOptions: Array<Option> = [
{
name: 'No validation',
name: this.localize.term('validation_validateNothing'),
value: '!NOVALIDATION!',
selected: true,
},
{
name: 'Validate as an email address',
name: this.localize.term('validation_validateAsEmail'),
value: '[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\\.[a-zA-Z0-9-.]+',
},
{
name: 'Validate as a number',
name: this.localize.term('validation_validateAsNumber'),
value: '^[0-9]*$',
},
{
name: 'Validate as an URL',
name: this.localize.term('validation_validateAsUrl'),
value: 'https?://[a-zA-Z0-9-.]+\\.[a-zA-Z]{2,}',
},
{
name: '...or enter a custom validation',
value: '',
name: this.localize.term('validation_enterCustomValidation'),
value: '.+',
},
];
@@ -85,15 +85,11 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
return option.selected;
});
if (newlySelected === undefined) {
this._customValidationOptions[4].selected = true;
this.updateValue({
validation: { ...this.value.validation, regEx: this._customValidationOptions[4].value },
});
} else {
this.updateValue({
validation: { ...this.value.validation, regEx: regEx },
});
this._customValidationOptions[this._customValidationOptions.length - 1].selected = true;
}
this.updateValue({
validation: { ...this.value.validation, regEx },
});
}
}
@@ -175,10 +171,6 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
const value = event.target.value.toString();
const regEx = value !== '!NOVALIDATION!' ? value : null;
this._customValidationOptions.forEach((option) => {
option.selected = option.value === regEx;
});
this.requestUpdate('_customValidationOptions');
this.updateValue({
validation: { ...this.value.validation, regEx },
});
@@ -232,10 +224,10 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
<uui-input
id="name-input"
name="name"
label="property name (TODO: Localize)"
label=${this.localize.term('placeholders_entername')}
@input=${this.#onNameChange}
.value=${this.value.name}
placeholder="Enter a name..."
placeholder=${this.localize.term('placeholders_entername')}
${umbFocus()}>
<!-- TODO: validation for bad characters -->
</uui-input>
@@ -244,7 +236,8 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
name="alias"
@input=${this.#onAliasChange}
.value=${this.value.alias}
placeholder="Enter alias..."
label=${this.localize.term('placeholders_enterAlias')}
placeholder=${this.localize.term('placeholders_enterAlias')}
?disabled=${this._aliasLocked}>
<!-- TODO: validation for bad characters -->
<div @click=${this.#onToggleAliasLock} @keydown=${() => ''} id="alias-lock" slot="prepend">
@@ -255,7 +248,8 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
id="description-input"
name="description"
@input=${this.#onDescriptionChange}
placeholder="Enter description..."
label=${this.localize.term('placeholders_enterDescription')}
placeholder=${this.localize.term('placeholders_enterDescription')}
.value=${this.value.description}></uui-textarea>
</div>
<umb-data-type-flow-input
@@ -263,15 +257,19 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
@change=${this.#onDataTypeIdChange}></umb-data-type-flow-input>
<hr />
<div class="container">
<b>Validation</b>
<b><umb-localize key="validation_validation">Validation</umb-localize></b>
${this.#renderMandatory()}
<p style="margin-bottom: 0">Custom validation</p>
<p style="margin-bottom: 0">
<umb-localize key="validation_customValidation">Custom validation</umb-localize>
</p>
${this.#renderCustomValidation()}
</div>
<hr />
${this.#renderVariationControls()}
<div class="container">
<b style="margin-bottom: var(--uui-size-space-3)">Appearance</b>
<b style="margin-bottom: var(--uui-size-space-3)">
<umb-localize key="contentTypeEditor_displaySettingsHeadline">Appearance</umb-localize>
</b>
<div id="appearances">${this.#renderAlignLeftIcon()} ${this.#renderAlignTopIcon()}</div>
</div>
</uui-box>
@@ -299,7 +297,9 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
<rect y="22" width="64" height="9" rx="4" fill="currentColor" fill-opacity="0.4" />
<rect x="106" width="94" height="60" rx="5" fill="currentColor" fill-opacity="0.4" />
</svg>
<label class="appearance-label"> Label on the left </label>
<label class="appearance-label">
<umb-localize key="contentTypeEditor_displaySettingsLabelOnLeft">Label to the left</umb-localize>
</label>
</button>`;
}
@@ -314,14 +314,18 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
<rect y="22" width="64" height="9" rx="4" fill="currentColor" fill-opacity="0.4" />
<rect y="42" width="140" height="36" rx="5" fill="currentColor" fill-opacity="0.4" />
</svg>
<label class="appearance-label"> Label on top </label>
<label class="appearance-label">
<umb-localize key="contentTypeEditor_displaySettingsLabelOnTop">Label above (full-width)</umb-localize>
</label>
</button>
`;
}
#renderMandatory() {
return html`<div style="display: flex; justify-content: space-between">
<label for="mandatory">Field is mandatory</label>
<label for="mandatory">
<umb-localize key="validation_fieldIsMandatory">Field is mandatory</umb-localize>
</label>
<uui-toggle
@change=${this.#onMandatoryChange}
id="mandatory"
@@ -335,7 +339,8 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
@change=${this.#onMandatoryMessageChange}
style="margin-top: var(--uui-size-space-1)"
id="mandatory-message"
placeholder="Enter a custom validation error message (optional)"></uui-input>`
placeholder=${this.localize.term('validation_mandatoryMessage')}
label=${this.localize.term('validation_mandatoryMessage')}></uui-input>`
: ''}`;
}
@@ -351,10 +356,14 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
name="pattern"
style="margin-bottom: var(--uui-size-space-1); margin-top: var(--uui-size-space-5);"
@change=${this.#onValidationRegExChange}
placeholder=${this.localize.term('validation_validationRegExp')}
label=${this.localize.term('validation_validationRegExp')}
.value=${this.value.validation?.regEx ?? ''}></uui-input>
<uui-textarea
name="pattern-message"
@change=${this.#onValidationMessageChange}
placeholder=${this.localize.term('validation_validationRegExpMessage')}
label=${this.localize.term('validation_validationRegExpMessage')}
.value=${this.value.validation?.regExMessage ?? ''}></uui-textarea>
`
: nothing} `;
@@ -363,7 +372,7 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
#renderVariationControls() {
return this._contentTypeVariesByCulture || this._contentTypeVariesBySegment
? html` <div class="container">
<b>Variation</b>
<b><umb-localize key="contentTypeEditor_variantsHeading">Allow variations</umb-localize></b>
${this._contentTypeVariesByCulture ? this.#renderVaryByCulture() : ''}
</div>
<hr />`
@@ -373,7 +382,7 @@ export class UmbPropertyTypeSettingsModalElement extends UmbModalBaseElement<
return html`<uui-toggle
@change=${this.#onVaryByCultureChange}
.checked=${this.value.variesByCulture ?? false}
label="Vary by culture"></uui-toggle> `;
label=${this.localize.term('contentTypeEditor_cultureVariantLabel')}></uui-toggle> `;
}
static styles = [

View File

@@ -1,4 +1,4 @@
export * from './workspace/index.js';
export { UmbContentPropertyContext } from './content-property.context.js';
export { UMB_CONTENT_PROPERTY_CONTEXT } from './content-property.context-token.js';
export { UmbContentPropertyContext } from './content-property.context.js';
export * from './property-dataset-context/content-property-dataset.context.js';
export * from './workspace/index.js';

View File

@@ -0,0 +1,148 @@
import type { UmbContentWorkspaceContext } from '../workspace/index.js';
import type { UmbNameablePropertyDatasetContext, UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import { type Observable, map } from '@umbraco-cms/backoffice/external/rxjs';
import { UmbObjectState } from '@umbraco-cms/backoffice/observable-api';
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
import { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbContentTypeModel, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbWorkspaceUniqueType } from '@umbraco-cms/backoffice/workspace';
export class UmbContentPropertyDatasetContext<
ContentTypeModel extends UmbContentTypeModel = UmbContentTypeModel,
VariantModelType extends UmbVariantModel = UmbVariantModel,
>
extends UmbContextBase<UmbPropertyDatasetContext>
implements UmbPropertyDatasetContext, UmbNameablePropertyDatasetContext
{
#workspace: UmbContentWorkspaceContext<ContentTypeModel, VariantModelType>;
#variantId: UmbVariantId;
public getVariantId() {
return this.#variantId;
}
#currentVariant = new UmbObjectState<UmbVariantModel | undefined>(undefined);
currentVariant = this.#currentVariant.asObservable();
name = this.#currentVariant.asObservablePart((x) => x?.name);
culture = this.#currentVariant.asObservablePart((x) => x?.culture);
segment = this.#currentVariant.asObservablePart((x) => x?.segment);
getEntityType(): string {
return this.#workspace.getEntityType();
}
getUnique(): UmbWorkspaceUniqueType | undefined {
return this.#workspace.getUnique();
}
getName(): string | undefined {
return this.#workspace.getName(this.#variantId);
}
setName(name: string) {
this.#workspace.setName(name, this.#variantId);
}
getVariantInfo() {
return this.#workspace.getVariant(this.#variantId);
}
constructor(
host: UmbControllerHost,
workspace: UmbContentWorkspaceContext<ContentTypeModel, VariantModelType>,
variantId?: UmbVariantId,
) {
// The controller alias, is a very generic name cause we want only one of these for this controller host.
super(host, UMB_PROPERTY_DATASET_CONTEXT);
this.#workspace = workspace;
this.#variantId = variantId ?? UmbVariantId.CreateInvariant();
this.observe(
this.#workspace.variantById(this.#variantId),
async (variantInfo) => {
if (!variantInfo) return;
this.#currentVariant.setValue(variantInfo);
},
'_observeActiveVariant',
);
}
#createPropertyVariantId(property: UmbPropertyTypeModel) {
return UmbVariantId.Create({
culture: property.variesByCulture ? this.#variantId.culture : null,
segment: property.variesBySegment ? this.#variantId.segment : null,
});
}
/**
* @method propertyVariantId
* @param {string} propertyAlias
* @returns {Promise<Observable<UmbVariantId | undefined> | undefined>}
* @description Get an Observable for the variant id of this property.
*/
async propertyVariantId(propertyAlias: string) {
return (await this.#workspace.structure.propertyStructureByAlias(propertyAlias)).pipe(
map((property) => (property ? this.#createPropertyVariantId(property) : undefined)),
);
}
/**
* @method propertyValueByAlias
* @param {string} propertyAlias
* @returns {Promise<Observable<ReturnType | undefined> | undefined>}
* @description Get an Observable for the value of this property.
*/
async propertyValueByAlias<ReturnType = unknown>(
propertyAlias: string,
): Promise<Observable<ReturnType | undefined> | undefined> {
await this.#workspace.isLoaded();
const structure = await this.#workspace.structure.getPropertyStructureByAlias(propertyAlias);
if (structure) {
return this.#workspace.propertyValueByAlias<ReturnType>(propertyAlias, this.#createPropertyVariantId(structure));
}
return;
}
// TODO: Refactor: Not used currently, but should investigate if we can implement this, to spare some energy.
async propertyValueByAliasAndVariantId<ReturnType = unknown>(
propertyAlias: string,
propertyVariantId: UmbVariantId,
): Promise<Observable<ReturnType | undefined> | undefined> {
return this.#workspace.propertyValueByAlias<ReturnType>(propertyAlias, propertyVariantId);
}
/**
* @method setPropertyValueByVariant
* @param {string} propertyAlias
* @param {PromiseLike<unknown>} value - value can be a promise resolving into the actual value or the raw value it self.
* @param {UmbVariantId} propertyVariantId - The variant id for the value to be set for.
* @returns {Promise<unknown>}
* @description Get the value of this property.
*/
setPropertyValueByVariant(
propertyAlias: string,
value: PromiseLike<unknown>,
propertyVariantId: UmbVariantId,
): Promise<void> {
return this.#workspace.setPropertyValue(propertyAlias, value, propertyVariantId);
}
/**
* @method setPropertyValue
* @param {string} propertyAlias
* @param {PromiseLike<unknown>} value - value can be a promise resolving into the actual value or the raw value it self.
* @returns {Promise<void>}
* @description Set the value of this property.
*/
async setPropertyValue(propertyAlias: string, value: PromiseLike<unknown>) {
this.#workspace.initiatePropertyValueChange();
// This is not reacting to if the property variant settings changes while running.
const property = await this.#workspace.structure.getPropertyStructureByAlias(propertyAlias);
if (property) {
const variantId = this.#createPropertyVariantId(property);
// This is not reacting to if the property variant settings changes while running.
this.#workspace.setPropertyValue(propertyAlias, await value, variantId);
}
this.#workspace.finishPropertyValueChange();
}
}

View File

@@ -1,5 +1,6 @@
import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbVariantModel } from '@umbraco-cms/backoffice/variant';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { UmbVariantId, UmbVariantModel } from '@umbraco-cms/backoffice/variant';
import type {
UmbPropertyStructureWorkspaceContext,
UmbRoutableWorkspaceContext,
@@ -13,4 +14,10 @@ export interface UmbContentWorkspaceContext<
UmbVariantDatasetWorkspaceContext<VariantModelType>,
UmbPropertyStructureWorkspaceContext<ContentTypeModel> {
readonly IS_CONTENT_WORKSPACE_CONTEXT: true;
isLoaded(): Promise<unknown> | undefined;
variantById(variantId: UmbVariantId): Observable<VariantModelType | undefined>;
initiatePropertyValueChange(): void;
finishPropertyValueChange(): void;
}

View File

@@ -0,0 +1 @@
export { UmbDeleteEntityAction } from './delete.action.js';

View File

@@ -1,4 +1,4 @@
import type { UmbRepositoryErrorResponse } from '../../../../repository/types.js';
import type { UmbRepositoryErrorResponse } from '../../../repository/types.js';
import type { UmbDuplicateRequestArgs } from './types.js';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';

View File

@@ -1,4 +1,4 @@
import { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from '../../../default/default.action.kind.js';
import { UMB_ENTITY_ACTION_DEFAULT_KIND_MANIFEST } from '../../default/default.action.kind.js';
import type { UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifest: UmbBackofficeManifestKind = {

View File

@@ -1,5 +1,5 @@
import { UmbEntityActionBase } from '../../../entity-action-base.js';
import { UmbRequestReloadStructureForEntityEvent } from '../../../request-reload-structure-for-entity.event.js';
import { UmbEntityActionBase } from '../../entity-action-base.js';
import { UmbRequestReloadStructureForEntityEvent } from '../../request-reload-structure-for-entity.event.js';
import type { UmbDuplicateRepository } from './duplicate-repository.interface.js';
import type { MetaEntityActionDuplicateToKind } from '@umbraco-cms/backoffice/extension-registry';
import { createExtensionApiByAlias } from '@umbraco-cms/backoffice/extension-registry';

View File

@@ -1 +0,0 @@
export { UmbDuplicateEntityAction } from './duplicate.action.js';

View File

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

View File

@@ -1,5 +1,2 @@
export { UmbDuplicateEntityAction } from './duplicate/index.js';
export { UmbDuplicateToEntityAction } from './duplicate-to/index.js';
export * from './duplicate/types.js';
export * from './duplicate-to/types.js';
export { UmbDuplicateEntityAction } from './duplicate.action.js';
export * from './types.js';

View File

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

View File

@@ -1,5 +1,2 @@
export * from './duplicate/index.js';
export * from './move/index.js';
export * from './delete/delete.action.js';
export * from './move/move-to.action.js';
export * from './sort-children-of/sort-children-of.action.js';
export * from './delete/index.js';

View File

@@ -1,14 +1,10 @@
import { manifests as defaultEntityActionManifests } from './default/manifests.js';
import { manifests as deleteEntityActionManifests } from './common/delete/manifests.js';
import { manifests as duplicateEntityActionManifests } from './common/duplicate/manifests.js';
import { manifests as moveEntityActionManifests } from './common/move/manifests.js';
import { manifests as sortChildrenOfEntityActionManifests } from './common/sort-children-of/manifests.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
...defaultEntityActionManifests,
...deleteEntityActionManifests,
...duplicateEntityActionManifests,
...moveEntityActionManifests,
...sortChildrenOfEntityActionManifests,
];

View File

@@ -6,7 +6,6 @@ import { UmbNotificationContext } from '@umbraco-cms/backoffice/notification';
import { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { UmbExtensionsApiInitializer, type UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api';
// TODO temp relative import until modules ship a component sub module
import './menu/components/index.js';
import './extension-registry/components/index.js';

View File

@@ -1,4 +1,4 @@
import { UMB_MENU_CONTEXT } from '../../menu/menu.context.js';
import { UMB_MENU_CONTEXT } from '../../menu/components/menu/menu.context.js';
import { UmbConditionBase } from './condition-base.controller.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type {

View File

@@ -2,5 +2,5 @@ import type { UmbPropertyEditorUiElement } from '../interfaces/index.js';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestBlockEditorCustomView extends ManifestElement<UmbPropertyEditorUiElement> {
type: 'bockEditorCustomView';
type: 'blockEditorCustomView';
}

View File

@@ -303,7 +303,7 @@
},
{
"name": "icon-brush-alt-2",
"file": "paintbrush-2.svg"
"file": "paintbrush-vertical.svg"
},
{
"name": "icon-brush-alt",

View File

@@ -8,6 +8,7 @@ export default {
} as Meta;
const Template: Story = () => {
const approvedIcons = icons.filter((x) => x.legacy !== true);
return html`
<div
style="display: grid;
@@ -17,7 +18,7 @@ const Template: Story = () => {
place-items: start;
justify-content: space-between;">
${repeat(
icons,
approvedIcons,
(icon) => icon.name,
(icon) =>
html` <div

View File

@@ -1,4 +1,4 @@
export default `<!-- @license lucide-static v0.367.0 - ISC -->
export default `<!-- @license lucide-static v0.379.0 - ISC -->
<svg
class="lucide lucide-activity"
xmlns="http://www.w3.org/2000/svg"
@@ -9,6 +9,6 @@ export default `<!-- @license lucide-static v0.367.0 - ISC -->
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2" />
</svg>
`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,4 @@
export default `<!-- @license lucide-static v0.367.0 - ISC -->
export default `<!-- @license lucide-static v0.379.0 - ISC -->
<svg
class="lucide lucide-award"
xmlns="http://www.w3.org/2000/svg"
@@ -9,7 +9,7 @@ export default `<!-- @license lucide-static v0.367.0 - ISC -->
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m15.477 12.89 1.515 8.526a.5.5 0 0 1-.81.47l-3.58-2.687a1 1 0 0 0-1.197 0l-3.586 2.686a.5.5 0 0 1-.81-.469l1.514-8.526" />
<circle cx="12" cy="8" r="6" />
<path d="M15.477 12.89 17 22l-5-3-5 3 1.523-9.11" />
</svg>
`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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