Merge branch 'v15/dev' into v15/feature/member-sidebar-menu

This commit is contained in:
Mads Rasmussen
2024-09-13 12:09:56 +02:00
73 changed files with 786 additions and 122 deletions

View File

@@ -1,7 +1,8 @@
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { html, customElement, LitElement, property, css } from '@umbraco-cms/backoffice/external/lit';
import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
import type { UmbBlockDataType, UmbBlockEditorCustomViewElement } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block';
import type { UmbBlockEditorCustomViewElement } from '@umbraco-cms/backoffice/block-custom-view';
// eslint-disable-next-line local-rules/enforce-umb-prefix-on-element-name
@customElement('example-block-custom-view')

View File

@@ -1,6 +1,4 @@
import type { ManifestBlockEditorCustomView } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestBlockEditorCustomView> = [
export const manifests: Array<UmbExtensionManifest> = [
{
type: 'blockEditorCustomView',
alias: 'Umb.blockEditorCustomView.TestView',

View File

@@ -7811,6 +7811,10 @@
"resolved": "src/packages/health-check",
"link": true
},
"node_modules/@umbraco-backoffice/help": {
"resolved": "src/packages/help",
"link": true
},
"node_modules/@umbraco-backoffice/language": {
"resolved": "src/packages/language",
"link": true
@@ -23078,6 +23082,7 @@
"src/packages/health-check": {
"name": "@umbraco-backoffice/health-check"
},
"src/packages/help": {},
"src/packages/language": {
"name": "@umbraco-backoffice/language"
},

View File

@@ -17,6 +17,7 @@
"./action": "./dist-cms/packages/core/action/index.js",
"./audit-log": "./dist-cms/packages/core/audit-log/index.js",
"./auth": "./dist-cms/packages/core/auth/index.js",
"./block-custom-view": "./dist-cms/packages/block/block-custom-view/index.js",
"./block-grid": "./dist-cms/packages/block/block-grid/index.js",
"./block-list": "./dist-cms/packages/block/block-list/index.js",
"./block-rte": "./dist-cms/packages/block/block-rte/index.js",
@@ -41,6 +42,7 @@
"./entity": "./dist-cms/packages/core/entity/index.js",
"./event": "./dist-cms/packages/core/event/index.js",
"./extension-registry": "./dist-cms/packages/core/extension-registry/index.js",
"./help": "./dist-cms/packages/help/index.js",
"./icon": "./dist-cms/packages/core/icon-registry/index.js",
"./id": "./dist-cms/packages/core/id/index.js",
"./imaging": "./dist-cms/packages/media/imaging/index.js",

View File

@@ -18,6 +18,7 @@ const CORE_PACKAGES = [
import('../../packages/dictionary/umbraco-package.js'),
import('../../packages/documents/umbraco-package.js'),
import('../../packages/health-check/umbraco-package.js'),
import('../../packages/help/umbraco-package.js'),
import('../../packages/language/umbraco-package.js'),
import('../../packages/log-viewer/umbraco-package.js'),
import('../../packages/markdown-editor/umbraco-package.js'),

View File

@@ -0,0 +1,9 @@
import type { ManifestBase } from '../types/index.js';
import type { UmbApi } from './api.interface.js';
/**
* Interface for APIs of a Extension.
*/
export interface UmbExtensionApi<ManifestType extends ManifestBase = ManifestBase> extends UmbApi {
manifest?: ManifestType;
}

View File

@@ -1,2 +1,3 @@
export * from './entry-point.interface.js';
export * from './api.interface.js';
export * from './extension-api.interface.js';

View File

@@ -1,4 +1,11 @@
import type { ManifestElementWithElementName, ManifestKind, ManifestBase } from '../types/index.js';
import type { WorkspaceAliasConditionConfig } from '@umbraco-cms/backoffice/workspace';
import type {
ManifestElementWithElementName,
ManifestKind,
ManifestBase,
ManifestWithDynamicConditions,
UmbConditionConfigBase,
} from '../types/index.js';
import { UmbExtensionRegistry } from './extension.registry.js';
import { expect } from '@open-wc/testing';
@@ -453,3 +460,224 @@ describe('UmbExtensionRegistry with exclusions', () => {
expect(extensionRegistry.isRegistered('Umb.Test.Section.Late')).to.be.false;
});
});
describe('Add Conditions', () => {
let extensionRegistry: UmbExtensionRegistry<any>;
let manifests: Array<ManifestWithDynamicConditions>;
beforeEach(() => {
extensionRegistry = new UmbExtensionRegistry<ManifestWithDynamicConditions>();
manifests = [
{
type: 'section',
name: 'test-section-1',
alias: 'Umb.Test.Section.1',
weight: 1,
conditions: [
{
alias: 'Umb.Test.Condition.Invalid',
},
],
},
{
type: 'section',
name: 'test-section-2',
alias: 'Umb.Test.Section.2',
weight: 200,
},
];
manifests.forEach((manifest) => extensionRegistry.register(manifest));
extensionRegistry.register({
type: 'condition',
name: 'test-condition-invalid',
alias: 'Umb.Test.Condition.Invalid',
});
});
it('should have the extensions registered', () => {
expect(extensionRegistry.isRegistered('Umb.Test.Section.1')).to.be.true;
expect(extensionRegistry.isRegistered('Umb.Test.Section.2')).to.be.true;
expect(extensionRegistry.isRegistered('Umb.Test.Condition.Invalid')).to.be.true;
expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.false;
});
it('allows an extension condition to be updated', async () => {
const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions;
expect(ext.conditions?.length).to.equal(1);
// Register new condition as if I was in my own entrypoint
extensionRegistry.register({
type: 'condition',
name: 'test-condition-valid',
alias: 'Umb.Test.Condition.Valid',
});
// Add the new condition to the extension
const conditionToAdd: UmbConditionConfigBase = {
alias: 'Umb.Test.Condition.Valid',
};
await extensionRegistry.appendCondition('Umb.Test.Section.1', conditionToAdd);
// Check new condition is registered
expect(extensionRegistry.isRegistered('Umb.Test.Condition.Valid')).to.be.true;
// Verify the extension now has two conditions and in correct order with aliases
const updatedExt = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions;
expect(updatedExt.conditions?.length).to.equal(2);
expect(updatedExt.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Invalid');
expect(updatedExt.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid');
// Verify the other extension was not updated:
const otherExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions;
expect(otherExt.conditions).to.be.undefined;
// Add a condition with a specific config to Section2
const workspaceCondition: WorkspaceAliasConditionConfig = {
alias: 'Umb.Condition.WorkspaceAlias',
match: 'Umb.Workspace.Document',
};
await extensionRegistry.appendCondition('Umb.Test.Section.2', workspaceCondition);
const updatedWorkspaceExt = extensionRegistry.getByAlias('Umb.Test.Section.2') as ManifestWithDynamicConditions;
expect(updatedWorkspaceExt.conditions?.length).to.equal(1);
expect(updatedWorkspaceExt.conditions?.[0]?.alias).to.equal('Umb.Condition.WorkspaceAlias');
});
it('allows an extension to update with multiple conditions', async () => {
const ext = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions;
expect(ext.conditions?.length).to.equal(1);
const conditions: Array<UmbConditionConfigBase> = [
{
alias: 'Umb.Test.Condition.Valid',
},
{
alias: 'Umb.Condition.WorkspaceAlias',
match: 'Umb.Workspace.Document',
} as WorkspaceAliasConditionConfig,
];
await extensionRegistry.appendConditions('Umb.Test.Section.1', conditions);
const extUpdated = extensionRegistry.getByAlias('Umb.Test.Section.1') as ManifestWithDynamicConditions;
expect(extUpdated.conditions?.length).to.equal(3);
expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Invalid');
expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Valid');
expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Condition.WorkspaceAlias');
});
it('allows conditions to be prepended when an extension is loaded later on', async () => {
const conditions: Array<UmbConditionConfigBase> = [
{
alias: 'Umb.Test.Condition.Invalid',
},
{
alias: 'Umb.Condition.WorkspaceAlias',
match: 'Umb.Workspace.Document',
} as WorkspaceAliasConditionConfig,
];
// Prepend the conditions, but do not await this.
extensionRegistry.appendConditions('Late.Extension.To.Be.Loaded', conditions);
// Make sure the extension is not registered YET
expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false;
// Register the extension LATE/after the conditions have been added
extensionRegistry.register({
type: 'section',
name: 'Late Section Extension with one condition',
alias: 'Late.Extension.To.Be.Loaded',
weight: 200,
conditions: [
{
alias: 'Umb.Test.Condition.Valid',
},
],
});
expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true;
const extUpdated = extensionRegistry.getByAlias('Late.Extension.To.Be.Loaded') as ManifestWithDynamicConditions;
expect(extUpdated.conditions?.length).to.equal(3);
expect(extUpdated.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid');
expect(extUpdated.conditions?.[1]?.alias).to.equal('Umb.Test.Condition.Invalid');
expect(extUpdated.conditions?.[2]?.alias).to.equal('Umb.Condition.WorkspaceAlias');
});
/**
* As of current state, it is by design without further reasons to why, but it is made so additional conditions are only added to a current or next time registered manifest.
* Meaning if it happens to be unregistered and re-registered it does not happen again.
* Unless the exact same appending of conditions happens again. [NL]
*
* This makes sense if extensions gets offloaded and re-registered, but the extension that registered additional conditions didn't get loaded/registered second time. Therefor they need to be re-registered for such to work. [NL]
*/
it('only append conditions to the next time the extension is registered', async () => {
const conditions: Array<UmbConditionConfigBase> = [
{
alias: 'Umb.Test.Condition.Invalid',
},
{
alias: 'Umb.Condition.WorkspaceAlias',
match: 'Umb.Workspace.Document',
} as WorkspaceAliasConditionConfig,
];
// Prepend the conditions, but do not await this.
extensionRegistry.appendConditions('Late.Extension.To.Be.Loaded', conditions);
// Make sure the extension is not registered YET
expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false;
// Register the extension LATE/after the conditions have been added
extensionRegistry.register({
type: 'section',
name: 'Late Section Extension with one condition',
alias: 'Late.Extension.To.Be.Loaded',
weight: 200,
conditions: [
{
alias: 'Umb.Test.Condition.Valid',
},
],
});
expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true;
const extUpdateFirstTime = extensionRegistry.getByAlias(
'Late.Extension.To.Be.Loaded',
) as ManifestWithDynamicConditions;
expect(extUpdateFirstTime.conditions?.length).to.equal(3);
extensionRegistry.unregister('Late.Extension.To.Be.Loaded');
// Make sure the extension is not registered YET
expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.false;
// Register the extension LATE/after the conditions have been added
extensionRegistry.register({
type: 'section',
name: 'Late Section Extension with one condition',
alias: 'Late.Extension.To.Be.Loaded',
weight: 200,
conditions: [
{
alias: 'Umb.Test.Condition.Valid',
},
],
});
expect(extensionRegistry.isRegistered('Late.Extension.To.Be.Loaded')).to.be.true;
const extUpdateSecondTime = extensionRegistry.getByAlias(
'Late.Extension.To.Be.Loaded',
) as ManifestWithDynamicConditions;
expect(extUpdateSecondTime.conditions?.length).to.equal(1);
expect(extUpdateSecondTime.conditions?.[0]?.alias).to.equal('Umb.Test.Condition.Valid');
});
});

View File

@@ -1,4 +1,9 @@
import type { ManifestBase, ManifestKind } from '../types/index.js';
import type {
ManifestBase,
ManifestKind,
ManifestWithDynamicConditions,
UmbConditionConfigBase,
} from '../types/index.js';
import type { SpecificManifestTypeOrManifestBase } from '../types/map.types.js';
import { UmbBasicState } from '@umbraco-cms/backoffice/observable-api';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
@@ -6,8 +11,9 @@ import { map, distinctUntilChanged, combineLatest, of, switchMap } from '@umbrac
/**
*
* @param previousValue
* @param currentValue
* @param previousValue {Array<ManifestBase>} - previous value
* @param currentValue {Array<ManifestBase>} - current value
* @returns {boolean} - true if value is assumed to be the same as previous value.
*/
function extensionArrayMemoization<T extends Pick<ManifestBase, 'alias'>>(
previousValue: Array<T>,
@@ -26,8 +32,9 @@ function extensionArrayMemoization<T extends Pick<ManifestBase, 'alias'>>(
/**
*
* @param previousValue
* @param currentValue
* @param previousValue {Array<ManifestBase>} - previous value
* @param currentValue {Array<ManifestBase>} - current value
* @returns {boolean} - true if value is assumed to be the same as previous value.
*/
function extensionAndKindMatchArrayMemoization<
T extends Pick<ManifestBase, 'alias'> & { __isMatchedWithKind?: boolean },
@@ -57,8 +64,9 @@ function extensionAndKindMatchArrayMemoization<
/**
*
* @param previousValue
* @param currentValue
* @param previousValue {Array<ManifestBase>} - previous value
* @param currentValue {Array<ManifestBase>} - current value
* @returns {boolean} - true if value is assumed to be the same as previous value.
*/
function extensionSingleMemoization<T extends Pick<ManifestBase, 'alias'>>(
previousValue: T | undefined,
@@ -72,8 +80,9 @@ function extensionSingleMemoization<T extends Pick<ManifestBase, 'alias'>>(
/**
*
* @param previousValue
* @param currentValue
* @param previousValue {Array<ManifestBase>} - previous value
* @param currentValue {Array<ManifestBase>} - current value
* @returns {boolean} - true if value is assumed to be the same as previous value.
*/
function extensionAndKindMatchSingleMemoization<
T extends Pick<ManifestBase, 'alias'> & { __isMatchedWithKind?: boolean },
@@ -100,8 +109,26 @@ export class UmbExtensionRegistry<
private _kinds = new UmbBasicState<Array<ManifestKind<ManifestTypes>>>([]);
public readonly kinds = this._kinds.asObservable();
#exclusions: Array<string> = [];
#additionalConditions: Map<string, Array<UmbConditionConfigBase>> = new Map();
#appendAdditionalConditions(manifest: ManifestTypes) {
const newConditions = this.#additionalConditions.get(manifest.alias);
if (newConditions) {
// Append the condition to the extensions conditions array
if ((manifest as ManifestWithDynamicConditions).conditions) {
for (const condition of newConditions) {
(manifest as ManifestWithDynamicConditions).conditions!.push(condition);
}
} else {
(manifest as ManifestWithDynamicConditions).conditions = newConditions;
}
this.#additionalConditions.delete(manifest.alias);
}
return manifest;
}
defineKind(kind: ManifestKind<ManifestTypes>): void {
const extensionsValues = this._extensions.getValue();
const extension = extensionsValues.find(
@@ -136,12 +163,25 @@ export class UmbExtensionRegistry<
};
register(manifest: ManifestTypes | ManifestKind<ManifestTypes>): void {
const isValid = this.#checkExtension(manifest);
const isValid = this.#validateExtension(manifest);
if (!isValid) {
return;
}
this._extensions.setValue([...this._extensions.getValue(), manifest as ManifestTypes]);
if (manifest.type === 'kind') {
this.defineKind(manifest as ManifestKind<ManifestTypes>);
return;
}
const isApproved = this.#isExtensionApproved(manifest);
if (!isApproved) {
return;
}
this._extensions.setValue([
...this._extensions.getValue(),
this.#appendAdditionalConditions(manifest as ManifestTypes),
]);
}
getAllExtensions(): Array<ManifestTypes> {
@@ -177,7 +217,7 @@ export class UmbExtensionRegistry<
return false;
}
#checkExtension(manifest: ManifestTypes | ManifestKind<ManifestTypes>): boolean {
#validateExtension(manifest: ManifestTypes | ManifestKind<ManifestTypes>): boolean {
if (!manifest.type) {
console.error(`Extension is missing type`, manifest);
return false;
@@ -188,11 +228,9 @@ export class UmbExtensionRegistry<
return false;
}
if (manifest.type === 'kind') {
this.defineKind(manifest as ManifestKind<ManifestTypes>);
return false;
}
return true;
}
#isExtensionApproved(manifest: ManifestTypes | ManifestKind<ManifestTypes>): boolean {
if (!this.#acceptExtension(manifest as ManifestTypes)) {
return false;
}
@@ -430,4 +468,41 @@ export class UmbExtensionRegistry<
distinctUntilChanged(extensionAndKindMatchArrayMemoization),
) as Observable<Array<ExtensionTypes>>;
}
/**
* Append a new condition to an existing extension
* Useful to add a condition for example the Save And Publish workspace action shipped by core.
* @param {string} alias - The alias of the extension to append the condition to.
* @param {UmbConditionConfigBase} newCondition - The condition to append to the extension.
*/
appendCondition(alias: string, newCondition: UmbConditionConfigBase) {
this.appendConditions(alias, [newCondition]);
}
/**
* Appends an array of conditions to an existing extension
* @param {string} alias - The alias of the extension to append the condition to
* @param {Array<UmbConditionConfigBase>} newConditions - An array of conditions to be appended to an extension manifest.
*/
appendConditions(alias: string, newConditions: Array<UmbConditionConfigBase>) {
const existingConditionsToBeAdded = this.#additionalConditions.get(alias);
this.#additionalConditions.set(
alias,
existingConditionsToBeAdded ? [...existingConditionsToBeAdded, ...newConditions] : newConditions,
);
const allExtensions = this._extensions.getValue();
for (const extension of allExtensions) {
if (extension.alias === alias) {
// Replace the existing extension with the updated one
allExtensions[allExtensions.indexOf(extension)] = this.#appendAdditionalConditions(extension as ManifestTypes);
// Update the main extensions collection/observable
this._extensions.setValue(allExtensions);
//Stop the search:
break;
}
}
}
}

View File

@@ -4,7 +4,7 @@ import type { ManifestBase } from './manifest-base.interface.js';
/**
* This type of extension takes a JS module and registers all exported manifests from the pointed JS file.
*/
export interface ManifestBundle<UmbManifestTypes extends ManifestBase = ManifestBase>
extends ManifestPlainJs<{ [key: string]: Array<UmbManifestTypes> }> {
export interface ManifestBundle<ManifestTypes extends ManifestBase = ManifestBase>
extends ManifestPlainJs<{ [key: string]: Array<ManifestTypes> }> {
type: 'bundle';
}

View File

@@ -1,20 +1,26 @@
import type { UmbBlockEditorCustomViewElement } from '../interfaces/index.js';
import type { UmbBlockEditorCustomViewElement } from './types.js';
import type { ManifestElement } from '@umbraco-cms/backoffice/extension-api';
export interface ManifestBlockEditorCustomView extends ManifestElement<UmbBlockEditorCustomViewElement> {
type: 'blockEditorCustomView';
/**
* @property {string | Array<string> } - Declare if this Custom View only must appear at specific Content Types by Alias.
* @property {string | Array<string> } forContentTypeAlias - Declare if this Custom View only must appear at specific Content Types by Alias.
* @description Optional condition if you like this custom view to only appear at for one or more specific Content Types.
* @example 'my-element-type-alias'
* @example ['my-element-type-alias-A', 'my-element-type-alias-B']
*/
forContentTypeAlias?: string | Array<string>;
/**
* @property {string | Array<string> } - Declare if this Custom View only must appear at specific Block Editors.
* @property {string | Array<string> } forBlockEditor - Declare if this Custom View only must appear at specific Block Editors.
* @description Optional condition if you like this custom view to only appear at a specific type of Block Editor.
* @example 'block-list'
* @example ['block-list', 'block-grid']
*/
forBlockEditor?: string | Array<string>;
}
declare global {
interface UmbExtensionManifestMap {
blockEditorCustomView: ManifestBlockEditorCustomView;
}
}

View File

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

View File

@@ -1,29 +1,8 @@
import type { ManifestBlockEditorCustomView } from '../index.js';
import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
// Shared with the Property Editor
export interface UmbBlockTypeBaseModel {
contentElementTypeKey: string;
settingsElementTypeKey?: string;
label?: string;
thumbnail?: string;
iconColor?: string;
backgroundColor?: string;
editorSize?: UUIModalSidebarSize;
forceHideContentEditorInOverlay: boolean;
}
import type { ManifestBlockEditorCustomView } from './block-editor-custom-view.extension.js';
import type { UmbBlockLayoutBaseModel, UmbBlockDataType } from '@umbraco-cms/backoffice/block';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
// Shared with the Property Editor
export interface UmbBlockLayoutBaseModel {
contentUdi: string;
settingsUdi?: string | null;
}
// Shared with the Property Editor
export interface UmbBlockDataType {
udi: string;
contentTypeKey: string;
[key: string]: unknown;
}
export type * from './block-editor-custom-view.extension.js';
export interface UmbBlockEditorCustomViewConfiguration {
editContentPath?: string;

View File

@@ -3,9 +3,9 @@ import { UmbBlockGridInlinePropertyDatasetContext } from './block-grid-inline-pr
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbBlockEditorCustomViewConfiguration } from '@umbraco-cms/backoffice/extension-registry';
import '../block-grid-areas-container/index.js';
import '../ref-grid-block/index.js';
import type { UmbBlockEditorCustomViewConfiguration } from '@umbraco-cms/backoffice/block-custom-view';
/**
* @element umb-block-grid-block-inline

View File

@@ -1,8 +1,8 @@
import { UMB_BLOCK_GRID_ENTRY_CONTEXT } from '../../context/block-grid-entry.context-token.js';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import type { UmbBlockEditorCustomViewConfiguration } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block';
import type { UmbBlockEditorCustomViewConfiguration } from '@umbraco-cms/backoffice/block-custom-view';
import '@umbraco-cms/backoffice/ufm';
import '../block-grid-areas-container/index.js';

View File

@@ -2,11 +2,7 @@ import { UmbBlockGridEntryContext } from '../../context/block-grid-entry.context
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 {
ManifestBlockEditorCustomView,
UmbBlockEditorCustomViewProperties,
UmbPropertyEditorUiElement,
} from '@umbraco-cms/backoffice/extension-registry';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils';
import { UMB_BLOCK_GRID, type UmbBlockGridLayoutModel } from '@umbraco-cms/backoffice/block-grid';
@@ -15,6 +11,10 @@ import '../block-grid-block/index.js';
import '../block-scale-handler/index.js';
import { UmbObserveValidationStateController } from '@umbraco-cms/backoffice/validation';
import { UmbDataPathBlockElementDataQuery } from '@umbraco-cms/backoffice/block';
import type {
ManifestBlockEditorCustomView,
UmbBlockEditorCustomViewProperties,
} from '@umbraco-cms/backoffice/block-custom-view';
import { UUIBlinkAnimationValue, UUIBlinkKeyframes } from '@umbraco-cms/backoffice/external/uui';
import type { UmbExtensionElementInitializer } from '@umbraco-cms/backoffice/extension-api';
/**

View File

@@ -2,16 +2,16 @@ import { UmbBlockListEntryContext } from '../../context/block-list-entry.context
import { UMB_BLOCK_LIST, type UmbBlockListLayoutModel } from '../../types.js';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { html, css, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import type {
ManifestBlockEditorCustomView,
UmbBlockEditorCustomViewProperties,
UmbPropertyEditorUiElement,
} from '@umbraco-cms/backoffice/extension-registry';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import '../ref-list-block/index.js';
import '../inline-list-block/index.js';
import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils';
import { UmbObserveValidationStateController } from '@umbraco-cms/backoffice/validation';
import { UmbDataPathBlockElementDataQuery } from '@umbraco-cms/backoffice/block';
import type {
ManifestBlockEditorCustomView,
UmbBlockEditorCustomViewProperties,
} from '@umbraco-cms/backoffice/block-custom-view';
/**
* @element umb-block-list-entry

View File

@@ -6,7 +6,7 @@ import { UMB_BLOCK_LIST_PROPERTY_EDITOR_ALIAS } from './manifests.js';
import { UmbLitElement, umbDestroyOnDisconnect } from '@umbraco-cms/backoffice/lit-element';
import { html, customElement, property, state, repeat, css, nothing } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbPropertyEditorUiElement, UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import {
UmbPropertyValueChangeEvent,
type UmbPropertyEditorConfigCollection,
@@ -19,6 +19,7 @@ import {
UmbBlockElementDataValidationPathTranslator,
type UmbBlockLayoutBaseModel,
} from '@umbraco-cms/backoffice/block';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
import '../../components/block-list-entry/index.js';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';

View File

@@ -1,7 +1,7 @@
import '../../../block-type/components/input-block-type/index.js';
import type { UmbInputBlockTypeElement } from '../../../block-type/index.js';
import { UMB_BLOCK_LIST_TYPE } from '../../types.js';
import type { UmbBlockTypeBaseModel, UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { html, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import {
UmbPropertyValueChangeEvent,

View File

@@ -1,5 +1,5 @@
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockLayoutBaseModel, UmbBlockValueType } from '@umbraco-cms/backoffice/block';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
export const UMB_BLOCK_LIST_TYPE = 'block-list-type';
export const UMB_BLOCK_LIST = 'block-list';

View File

@@ -2,11 +2,9 @@ import type { UmbBlockRteLayoutModel } from '../../types.js';
import { UmbBlockRteEntryContext } from '../../context/block-rte-entry.context.js';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { html, css, property, state, customElement } from '@umbraco-cms/backoffice/external/lit';
import type {
UmbBlockEditorCustomViewProperties,
UmbPropertyEditorUiElement,
} from '@umbraco-cms/backoffice/extension-registry';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbBlockEditorCustomViewProperties } from '@umbraco-cms/backoffice/block-custom-view';
import '../ref-rte-block/index.js';

View File

@@ -2,7 +2,7 @@ import { UMB_BLOCK_RTE_MANAGER_CONTEXT } from '../context/block-rte-manager.cont
import { UMB_BLOCK_RTE_ENTRIES_CONTEXT } from '../context/block-rte-entries.context-token.js';
import { type TinyMcePluginArguments, UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/tiny-mce';
import { UmbLocalizationController } from '@umbraco-cms/backoffice/localization-api';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
export default class UmbTinyMceMultiUrlPickerPlugin extends UmbTinyMcePluginBase {
#localize = new UmbLocalizationController(this._host);

View File

@@ -1,4 +1,4 @@
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
import type { UmbBlockLayoutBaseModel, UmbBlockValueType } from '@umbraco-cms/backoffice/block';
export const UMB_BLOCK_RTE_TYPE = 'block-rte-type';

View File

@@ -2,11 +2,12 @@ import { UMB_MANIFEST_VIEWER_MODAL } from '../../../modals/manifest-viewer/index
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { html, customElement, state, property, css } from '@umbraco-cms/backoffice/external/lit';
import { UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
import { umbExtensionsRegistry, type ManifestBlockEditorCustomView } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { stringOrStringArrayContains } from '@umbraco-cms/backoffice/utils';
import { UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api';
import { UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type';
import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { ManifestBlockEditorCustomView } from '@umbraco-cms/backoffice/block-custom-view';
@customElement('umb-block-type-custom-view-guide')
export class UmbBlockTypeCustomViewGuideElement extends UmbLitElement {
@@ -88,7 +89,7 @@ export class UmbBlockTypeCustomViewGuideElement extends UmbLitElement {
async #generateManifest() {
const modalManager = await this.getContext(UMB_MODAL_MANAGER_CONTEXT);
const manifest = {
const manifest: UmbExtensionManifest = {
type: 'blockEditorCustomView',
alias: 'Local.blockEditorCustomView.' + this.#contentTypeAlias,
name: 'Block Editor Custom View for ' + this.#contentTypeName,

View File

@@ -12,7 +12,7 @@ import {
UMB_DOCUMENT_TYPE_PICKER_MODAL,
} from '@umbraco-cms/backoffice/document-type';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
import '../block-type-card/index.js';

View File

@@ -1,6 +1,16 @@
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
export type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UUIModalSidebarSize } from '@umbraco-cms/backoffice/external/uui';
// Shared with the Property Editor
export interface UmbBlockTypeBaseModel {
contentElementTypeKey: string;
settingsElementTypeKey?: string;
label?: string;
thumbnail?: string;
iconColor?: string;
backgroundColor?: string;
editorSize?: UUIModalSidebarSize;
forceHideContentEditorInOverlay: boolean;
}
export interface UmbBlockTypeGroup {
name?: string;
key: string;

View File

@@ -1,4 +1,4 @@
import type { UmbBlockTypeWithGroupKey } from '../types.js';
import type { UmbBlockTypeBaseModel, UmbBlockTypeWithGroupKey } from '../types.js';
import { UmbBlockTypeWorkspaceEditorElement } from './block-type-workspace-editor.element.js';
import type { UmbPropertyDatasetContext } from '@umbraco-cms/backoffice/property';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
@@ -13,7 +13,7 @@ import {
} from '@umbraco-cms/backoffice/workspace';
import { UmbObjectState, appendToFrozenArray } from '@umbraco-cms/backoffice/observable-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { ManifestWorkspace, UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry';
export class UmbBlockTypeWorkspaceContext<BlockTypeData extends UmbBlockTypeWithGroupKey = UmbBlockTypeWithGroupKey>
extends UmbSubmittableWorkspaceContextBase<BlockTypeData>

View File

@@ -3,7 +3,7 @@ import type { UmbBlockWorkspaceOriginData } from '../workspace/block-workspace.m
import type { UmbBlockEntriesContext } from './block-entries.context.js';
import type { UMB_BLOCK_MANAGER_CONTEXT } from './block-manager.context-token.js';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
export const UMB_BLOCK_ENTRIES_CONTEXT = new UmbContextToken<
UmbBlockEntriesContext<

View File

@@ -4,10 +4,10 @@ import type { UmbBlockDataObjectModel, UmbBlockManagerContext } from './block-ma
import { UMB_BLOCK_ENTRIES_CONTEXT } from './block-entries.context-token.js';
import { type Observable, UmbArrayState, UmbBasicState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type { UmbModalRouteBuilder } from '@umbraco-cms/backoffice/router';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
export abstract class UmbBlockEntriesContext<
BlockManagerContextTokenType extends UmbContextToken<BlockManagerContextType, BlockManagerContextType>,

View File

@@ -4,7 +4,7 @@ import type { UMB_BLOCK_ENTRIES_CONTEXT } from './block-entries.context-token.js
import type { UmbBlockEntriesContext } from './block-entries.context.js';
import type { UmbBlockEntryContext } from './block-entry.context.js';
import type { UMB_BLOCK_MANAGER_CONTEXT } from './block-manager.context-token.js';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export const UMB_BLOCK_ENTRY_CONTEXT = new UmbContextToken<

View File

@@ -15,7 +15,7 @@ import { encodeFilePath } from '@umbraco-cms/backoffice/utils';
import { umbConfirmModal } from '@umbraco-cms/backoffice/modal';
import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { Observable } from '@umbraco-cms/backoffice/external/rxjs';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
export abstract class UmbBlockEntryContext<
BlockManagerContextTokenType extends UmbContextToken<BlockManagerContextType>,

View File

@@ -5,12 +5,12 @@ import { UmbContextBase } from '@umbraco-cms/backoffice/class-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState, UmbBooleanState, UmbClassState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import { UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type';
import { UmbId } from '@umbraco-cms/backoffice/id';
import type { UmbPropertyEditorConfigCollection } from '@umbraco-cms/backoffice/property-editor';
import { UMB_PROPERTY_CONTEXT } from '@umbraco-cms/backoffice/property';
import type { UmbVariantId } from '@umbraco-cms/backoffice/variant';
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
/**
*

View File

@@ -1,5 +1,5 @@
export * from './context/index.js';
export * from './modals/index.js';
export * from './types.js';
export type * from './types.js';
export * from './validation/index.js';
export * from './workspace/index.js';

View File

@@ -1,7 +1,6 @@
import type { UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/extension-registry';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
import type { UmbBlockWorkspaceData } from '@umbraco-cms/backoffice/block';
import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type';
import type { UmbBlockTypeGroup, UmbBlockTypeBaseModel } from '@umbraco-cms/backoffice/block-type';
export interface UmbBlockCatalogueModalData {
blocks: Array<UmbBlockTypeBaseModel>;

View File

@@ -1,5 +1,15 @@
import type { UmbBlockDataType, UmbBlockLayoutBaseModel } from '@umbraco-cms/backoffice/extension-registry';
export type { UmbBlockDataType, UmbBlockLayoutBaseModel } from '@umbraco-cms/backoffice/extension-registry';
// Shared with the Property Editor
export interface UmbBlockLayoutBaseModel {
contentUdi: string;
settingsUdi?: string | null;
}
// Shared with the Property Editor
export interface UmbBlockDataType {
udi: string;
contentTypeKey: string;
[key: string]: unknown;
}
export interface UmbBlockValueType<BlockLayoutType extends UmbBlockLayoutBaseModel> {
layout: { [key: string]: Array<BlockLayoutType> | undefined };

View File

@@ -1,4 +1,5 @@
import type { UmbBlockDataType, UmbBlockEditorCustomViewElement } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbBlockDataType } from '@umbraco-cms/backoffice/block';
import type { UmbBlockEditorCustomViewElement } from '@umbraco-cms/backoffice/block-custom-view';
import { css, customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';

View File

@@ -1,6 +1,4 @@
import type { ManifestBlockEditorCustomView } from '@umbraco-cms/backoffice/extension-registry';
export const manifest: ManifestBlockEditorCustomView = {
export const manifest: UmbExtensionManifest = {
type: 'blockEditorCustomView',
alias: 'Umb.blockEditorCustomView.TestView',
name: 'Block Editor Custom View Test',

View File

@@ -12,6 +12,7 @@ export default defineConfig({
dist,
entry: {
'block/index': 'block/index.ts',
'block-custom-view/index': 'block-custom-view/index.ts',
'block-grid/index': 'block-grid/index.ts',
'block-list/index': 'block-list/index.ts',
'block-rte/index': 'block-rte/index.ts',

View File

@@ -1,4 +1,3 @@
export * from './block-editor-custom-view-element.interface.js';
export * from './dashboard-element.interface.js';
export * from './external-login-provider-element.interface.js';
export * from './menu-item-element.interface.js';

View File

@@ -1,5 +1,4 @@
import type { ManifestAuthProvider } from './auth-provider.model.js';
import type { ManifestBlockEditorCustomView } from './block-editor-custom-view.model.js';
import type { ManifestCollection } from './collection.models.js';
import type { ManifestCollectionView } from './collection-view.model.js';
import type { ManifestCurrentUserAction, ManifestCurrentUserActionDefaultKind } from './current-user-action.model.js';
@@ -30,7 +29,7 @@ import type { ManifestHealthCheck } from './health-check.model.js';
import type { ManifestIcons } from './icons.model.js';
import type { ManifestLocalization } from './localization.model.js';
import type { ManifestMenu } from './menu.model.js';
import type { ManifestMenuItem, ManifestMenuItemTreeKind } from './menu-item.model.js';
import type { ManifestMenuItem, ManifestMenuItemLinkKind, ManifestMenuItemTreeKind } from './menu-item.model.js';
import type { ManifestModal } from './modal.model.js';
import type { ManifestPackageView } from './package-view.model.js';
import type { ManifestPreviewAppProvider } from './preview-app.model.js';
@@ -78,7 +77,6 @@ import type { ManifestBase, ManifestBundle, ManifestCondition } from '@umbraco-c
export type * from './app-entry-point.model.js';
export type * from './auth-provider.model.js';
export type * from './backoffice-entry-point.model.js';
export type * from './block-editor-custom-view.model.js';
export type * from './collection-action.model.js';
export type * from './collection-view.model.js';
export type * from './collection.models.js';
@@ -160,7 +158,6 @@ export type ManifestTypes =
| ManifestAppEntryPoint
| ManifestAuthProvider
| ManifestBackofficeEntryPoint
| ManifestBlockEditorCustomView
| ManifestBundle<ManifestTypes>
| ManifestCollection
| ManifestCollectionAction
@@ -188,6 +185,7 @@ export type ManifestTypes =
| ManifestMenu
| ManifestMenuItem
| ManifestMenuItemTreeKind
| ManifestMenuItemLinkKind
| ManifestMfaLoginProvider
| ManifestModal
| ManifestMonacoMarkdownEditorAction
@@ -221,3 +219,36 @@ export type ManifestTypes =
| ManifestWorkspaces
| ManifestWorkspaceViews
| ManifestBase;
type UnionOfProperties<T> = T extends object ? T[keyof T] : never;
declare global {
/**
* This global type allows to declare manifests types from its own module.
* @example
```js
declare global {
interface UmbExtensionManifestMap {
My_UNIQUE_MANIFEST_NAME: MyExtensionManifestType;
}
}
```
If you have multiple types, you can declare them in this way:
```js
declare global {
interface UmbExtensionManifestMap {
My_UNIQUE_MANIFEST_NAME: MyExtensionManifestTypeA | MyExtensionManifestTypeB;
}
}
```
*/
interface UmbExtensionManifestMap {
UMB_CORE: ManifestTypes;
}
/**
* This global type provides a union of all declared manifest types.
* If this is a local package that declares additional Manifest Types, then these will also be included in this union.
*/
type UmbExtensionManifest = UnionOfProperties<UmbExtensionManifestMap>;
}

View File

@@ -26,3 +26,13 @@ export interface MetaMenuItemTreeKind extends MetaMenuItem {
treeAlias: string;
hideTreeRoot?: boolean;
}
export interface ManifestMenuItemLinkKind extends ManifestMenuItem {
type: 'menuItem';
kind: 'link';
meta: MetaMenuItemLinkKind;
}
export interface MetaMenuItemLinkKind extends MetaMenuItem {
href: string;
}

View File

@@ -1,8 +1,7 @@
import type { ManifestTypes } from './models/index.js';
import type { ManifestKind } from '@umbraco-cms/backoffice/extension-api';
import { UmbExtensionRegistry } from '@umbraco-cms/backoffice/extension-api';
export type UmbBackofficeManifestKind = ManifestKind<ManifestTypes>;
export type UmbBackofficeExtensionRegistry = UmbExtensionRegistry<ManifestTypes>;
export type UmbBackofficeManifestKind = ManifestKind<UmbExtensionManifest>;
export type UmbBackofficeExtensionRegistry = UmbExtensionRegistry<UmbExtensionManifest>;
export const umbExtensionsRegistry = new UmbExtensionRegistry<ManifestTypes>() as UmbBackofficeExtensionRegistry;
export const umbExtensionsRegistry = new UmbExtensionRegistry<UmbExtensionManifest>() as UmbBackofficeExtensionRegistry;

View File

@@ -1,4 +1,4 @@
import type { ManifestTypes } from './models/index.js';
import './models/index.js';
/**
* Umbraco package manifest JSON
@@ -37,7 +37,7 @@ export interface UmbracoPackage {
* @title An array of Umbraco package manifest types that will be installed
* @required
*/
extensions: ManifestTypes[];
extensions: UmbExtensionManifest[];
/**
* @title The importmap for the package

View File

@@ -0,0 +1 @@
export * from '@umbraco-cms/backoffice/extension-registry';

View File

@@ -9,6 +9,7 @@ import { manifests as entityBulkActionManifests } from './entity-bulk-action/man
import { manifests as extensionManifests } from './extension-registry/manifests.js';
import { manifests as iconRegistryManifests } from './icon-registry/manifests.js';
import { manifests as localizationManifests } from './localization/manifests.js';
import { manifests as menuManifests } from './menu/manifests.js';
import { manifests as modalManifests } from './modal/common/manifests.js';
import { manifests as pickerManifests } from './picker/manifests.js';
import { manifests as propertyActionManifests } from './property-action/manifests.js';
@@ -35,6 +36,7 @@ export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
...extensionManifests,
...iconRegistryManifests,
...localizationManifests,
...menuManifests,
...modalManifests,
...pickerManifests,
...propertyActionManifests,

View File

@@ -37,6 +37,15 @@ export class UmbMenuItemLayoutElement extends UmbLitElement {
@property({ type: String })
public href?: string;
/**
* Set an anchor tag target, only used when using href.
* @type {string}
* @attr
* @default undefined
*/
@property({ type: String })
public target?: '_blank' | '_parent' | '_self' | '_top';
@state()
private _isActive = false;
@@ -63,7 +72,8 @@ export class UmbMenuItemLayoutElement extends UmbLitElement {
label=${this.label}
.caretLabel=${this.localize.term('visuallyHiddenTexts_expandChildItems') + ' ' + this.label}
?active=${this._isActive}
?has-children=${this.hasChildren}>
?has-children=${this.hasChildren}
target=${ifDefined(this.href && this.target ? this.target : undefined)}>
<umb-icon slot="icon" name=${this.iconName}></umb-icon>
${this.entityType
? html`<umb-entity-actions-bundle

View File

@@ -0,0 +1,39 @@
import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import type { ManifestMenuItemLinkKind, UmbMenuItemElement } from '@umbraco-cms/backoffice/extension-registry';
const elementName = 'umb-link-menu-item';
@customElement(elementName)
export class UmbLinkMenuItemElement extends UmbLitElement implements UmbMenuItemElement {
@property({ type: Object, attribute: false })
manifest?: ManifestMenuItemLinkKind;
#getTarget() {
const href = this.manifest?.meta.href;
if (href && href.startsWith('http')) {
return '_blank';
}
return '_self';
}
override render() {
return html`
<umb-menu-item-layout
.href=${this.manifest?.meta.href}
target=${this.#getTarget()}
.iconName=${this.manifest?.meta.icon ?? ''}
.label=${this.localize.string(this.manifest?.meta.label ?? this.manifest?.name ?? '')}>
</umb-menu-item-layout>
`;
}
}
export { UmbLinkMenuItemElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbLinkMenuItemElement;
}
}

View File

@@ -0,0 +1,14 @@
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
{
type: 'kind',
alias: 'Umb.Kind.MenuItem.Link',
matchKind: 'link',
matchType: 'menuItem',
manifest: {
type: 'menuItem',
element: () => import('./link-menu-item.element.js'),
},
},
];

View File

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

View File

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

View File

@@ -0,0 +1,77 @@
import { UMB_HELP_MENU_ALIAS } from '../menu/index.js';
import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import { UmbHeaderAppButtonElement } from '@umbraco-cms/backoffice/components';
import { umbExtensionsRegistry, type ManifestMenu } from '@umbraco-cms/backoffice/extension-registry';
import { UmbExtensionsManifestInitializer } from '@umbraco-cms/backoffice/extension-api';
const elementName = 'umb-help-header-app';
@customElement(elementName)
export class UmbHelpHeaderAppElement extends UmbHeaderAppButtonElement {
@state()
private _popoverOpen = false;
@state()
private _helpMenuHasMenuItems = false;
constructor() {
super();
new UmbExtensionsManifestInitializer(
this,
umbExtensionsRegistry,
'menuItem',
(manifest) => manifest.meta.menus.includes(UMB_HELP_MENU_ALIAS),
(menuItems) => {
const manifests = menuItems.map((menuItem) => menuItem.manifest);
this._helpMenuHasMenuItems = manifests.length > 0;
},
);
}
#onPopoverToggle(event: ToggleEvent) {
// TODO: This ignorer is just neede for JSON SCHEMA TO WORK, As its not updated with latest TS jet.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this._popoverOpen = event.newState === 'open';
}
override render() {
return html` ${this.#renderButton()} ${this.#renderPopover()} `;
}
#renderButton() {
if (!this._helpMenuHasMenuItems) return nothing;
return html`
<uui-button popovertarget="help-menu-popover" look="primary" label="help" compact>
<uui-icon name="icon-help-alt"></uui-icon>
</uui-button>
`;
}
#renderPopover() {
return html`
<uui-popover-container id="help-menu-popover" @toggle=${this.#onPopoverToggle}>
<umb-popover-layout>
<uui-scroll-container>
<umb-extension-slot
type="menu"
.filter="${(menu: ManifestMenu) => menu.alias === UMB_HELP_MENU_ALIAS}"
default-element="umb-menu"></umb-extension-slot>
</uui-scroll-container>
</umb-popover-layout>
</uui-popover-container>
`;
}
static override styles: CSSResultGroup = [UmbHeaderAppButtonElement.styles, css``];
}
export { UmbHelpHeaderAppElement as element };
declare global {
interface HTMLElementTagNameMap {
[elementName]: UmbHelpHeaderAppElement;
}
}

View File

@@ -0,0 +1,11 @@
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
{
type: 'headerApp',
alias: 'Umb.HeaderApp.Help',
name: 'Help Header App',
element: () => import('./help-header-app.element.js'),
weight: 500,
},
];

View File

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

View File

@@ -0,0 +1,5 @@
import { manifests as headerAppManifests } from './header-app/manifests.js';
import { manifests as menuManifests } from './menu/manifests.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [...menuManifests, ...headerAppManifests];

View File

@@ -0,0 +1 @@
export const UMB_HELP_MENU_ALIAS = 'Umb.Menu.Help';

View File

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

View File

@@ -0,0 +1,47 @@
import { UMB_HELP_MENU_ALIAS } from './constants.js';
import { UMB_CURRENT_USER_IS_ADMIN_CONDITION_ALIAS } from '@umbraco-cms/backoffice/current-user';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [
{
type: 'menu',
alias: UMB_HELP_MENU_ALIAS,
name: 'Help Menu',
},
{
type: 'menuItem',
kind: 'link',
alias: 'Umb.MenuItem.Help.LearningBase',
name: 'Learning Base Help Menu Item',
weight: 200,
meta: {
menus: [UMB_HELP_MENU_ALIAS],
label: 'Umbraco Learning Base',
icon: 'icon-movie-alt',
href: 'https://umbra.co/ulb',
},
conditions: [
{
alias: UMB_CURRENT_USER_IS_ADMIN_CONDITION_ALIAS,
},
],
},
{
type: 'menuItem',
kind: 'link',
alias: 'Umb.MenuItem.Help.CommunityWebsite',
name: 'Community Website Help Menu Item',
weight: 100,
meta: {
menus: [UMB_HELP_MENU_ALIAS],
label: 'Community Website',
icon: 'icon-hearts',
href: 'https://our.umbraco.com?utm_source=core&amp;utm_medium=help&amp;utm_content=link&amp;utm_campaign=our',
},
conditions: [
{
alias: UMB_CURRENT_USER_IS_ADMIN_CONDITION_ALIAS,
},
],
},
];

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
import { UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type';
import { type UmbBlockTypeBaseModel, UmbInputBlockTypeElement } from '@umbraco-cms/backoffice/block-type';
import { UMB_BLOCK_RTE_TYPE } from '@umbraco-cms/backoffice/block-rte';
import type { UmbBlockTypeBaseModel, UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import type { UmbPropertyEditorUiElement } from '@umbraco-cms/backoffice/extension-registry';
import { html, customElement, property, state, nothing } from '@umbraco-cms/backoffice/external/lit';
import {
UmbPropertyValueChangeEvent,

View File

@@ -0,0 +1 @@
export * from './is-admin/index.js';

View File

@@ -0,0 +1 @@
export const UMB_CURRENT_USER_IS_ADMIN_CONDITION_ALIAS = 'Umb.Condition.CurrentUser.IsAdmin';

View File

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

View File

@@ -0,0 +1,9 @@
import { UMB_CURRENT_USER_IS_ADMIN_CONDITION_ALIAS } from './constants.js';
import type { ManifestCondition } from '@umbraco-cms/backoffice/extension-api';
export const manifest: ManifestCondition = {
type: 'condition',
name: 'Current user is admin Condition',
alias: UMB_CURRENT_USER_IS_ADMIN_CONDITION_ALIAS,
api: () => import('./is-admin.condition.js'),
};

View File

@@ -0,0 +1,20 @@
import { isCurrentUserAnAdmin } from '../../utils/is-current-user.function.js';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import type {
UmbConditionConfigBase,
UmbConditionControllerArguments,
UmbExtensionCondition,
} from '@umbraco-cms/backoffice/extension-api';
import { UmbConditionBase } from '@umbraco-cms/backoffice/extension-registry';
export class UmbContentHasPropertiesWorkspaceCondition
extends UmbConditionBase<UmbConditionConfigBase>
implements UmbExtensionCondition
{
constructor(host: UmbControllerHost, args: UmbConditionControllerArguments<UmbConditionConfigBase>) {
super(host, args);
isCurrentUserAnAdmin(host).then((isAdmin) => (this.permitted = isAdmin));
}
}
export { UmbContentHasPropertiesWorkspaceCondition as api };

View File

@@ -0,0 +1,3 @@
import { manifest as isAdminManifests } from './is-admin/is-admin.condition.manifest.js';
export const manifests = [isAdminManifests];

View File

@@ -1,9 +1,10 @@
export * from './action/index.js';
export * from './components/index.js';
export * from './history/current-user-history.store.js';
export * from './utils/index.js';
export * from './repository/index.js';
export * from './conditions/index.js';
export * from './current-user.context.js';
export * from './current-user.context.token.js';
export * from './history/current-user-history.store.js';
export * from './repository/index.js';
export * from './utils/index.js';
export type * from './types.js';

View File

@@ -1,11 +1,13 @@
import { manifest as actionDefaultKindManifest } from './action/default.kind.js';
import { manifests as modalManifests } from './modals/manifests.js';
import { manifests as historyManifests } from './history/manifests.js';
import { manifests as conditionManifests } from './conditions/manifests.js';
import { manifests as externalLoginProviderManifests } from './external-login/manifests.js';
import { manifests as historyManifests } from './history/manifests.js';
import { manifests as mfaLoginProviderManifests } from './mfa-login/manifests.js';
import { manifests as modalManifests } from './modals/manifests.js';
import { manifests as profileManifests } from './profile/manifests.js';
import { manifests as themeManifests } from './theme/manifests.js';
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as themeManifests } from './theme/manifests.js';
import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry';
export const headerApps: Array<ManifestTypes> = [
@@ -21,19 +23,15 @@ export const headerApps: Array<ManifestTypes> = [
name: 'Current User',
element: () => import('./current-user-header-app.element.js'),
weight: 0,
meta: {
label: 'TODO: how should we enable this to not be set.',
icon: 'TODO: how should we enable this to not be set.',
pathname: 'user',
},
},
];
export const manifests = [
actionDefaultKindManifest,
...conditionManifests,
...externalLoginProviderManifests,
...headerApps,
...historyManifests,
...externalLoginProviderManifests,
...mfaLoginProviderManifests,
...modalManifests,
...profileManifests,

View File

@@ -7,6 +7,7 @@ import type {
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, query } from '@umbraco-cms/backoffice/external/lit';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
const elementName = 'umb-create-user-client-credential-modal';
@customElement(elementName)
@@ -17,10 +18,27 @@ export class UmbCreateUserModalElement extends UmbModalBaseElement<
@query('#CreateUserClientCredentialForm')
_form?: HTMLFormElement;
@query('#unique')
_inputUniqueElement?: UUIInputElement;
#userClientCredentialRepository = new UmbUserClientCredentialRepository(this);
#uniquePrefix = 'umbraco-back-office-';
protected override firstUpdated(): void {
// For some reason the pattern attribute is not working with this specific regex. It complains about the regex is invalid.
// TODO: investigate why this is happening.
this._inputUniqueElement?.addValidator(
'patternMismatch',
() => 'Only alphanumeric characters and hyphens are allowed',
() => {
const value = (this._inputUniqueElement?.value as string) || '';
// eslint-disable-next-line no-useless-escape
return !new RegExp(/^[a-zA-Z0-9\-]+$/).test(value);
},
);
}
async #onSubmit(e: SubmitEvent) {
e.preventDefault();

View File

@@ -9,7 +9,6 @@ import { manifests as propertyEditorManifests } from './property-editor/manifest
import { manifests as repositoryManifests } from './repository/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
import { manifests as menuItemManifests } from './menu-item/manifests.js';
import type { ManifestTypes, UmbBackofficeManifestKind } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestTypes | UmbBackofficeManifestKind> = [

View File

@@ -45,6 +45,7 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js
"@umbraco-cms/backoffice/action": ["./src/packages/core/action/index.ts"],
"@umbraco-cms/backoffice/audit-log": ["./src/packages/core/audit-log/index.ts"],
"@umbraco-cms/backoffice/auth": ["./src/packages/core/auth/index.ts"],
"@umbraco-cms/backoffice/block-custom-view": ["./src/packages/block/block-custom-view/index.ts"],
"@umbraco-cms/backoffice/block-grid": ["./src/packages/block/block-grid/index.ts"],
"@umbraco-cms/backoffice/block-list": ["./src/packages/block/block-list/index.ts"],
"@umbraco-cms/backoffice/block-rte": ["./src/packages/block/block-rte/index.ts"],
@@ -69,6 +70,7 @@ DON'T EDIT THIS FILE DIRECTLY. It is generated by /devops/tsconfig/index.js
"@umbraco-cms/backoffice/entity": ["./src/packages/core/entity/index.ts"],
"@umbraco-cms/backoffice/event": ["./src/packages/core/event/index.ts"],
"@umbraco-cms/backoffice/extension-registry": ["./src/packages/core/extension-registry/index.ts"],
"@umbraco-cms/backoffice/help": ["./src/packages/help/index.ts"],
"@umbraco-cms/backoffice/icon": ["./src/packages/core/icon-registry/index.ts"],
"@umbraco-cms/backoffice/id": ["./src/packages/core/id/index.ts"],
"@umbraco-cms/backoffice/imaging": ["./src/packages/media/imaging/index.ts"],

View File

@@ -8,6 +8,7 @@
"name": "My Dashboard",
"alias": "myDashboard",
"weight": -10,
"elementName": "my-dashboard",
"js": "js/my-dashboard.js",