Merge branch 'main' into improvement/model-remapping

This commit is contained in:
Mads Rasmussen
2024-02-01 14:58:44 +01:00
62 changed files with 486 additions and 684 deletions

View File

@@ -34,7 +34,7 @@ There are two ways to use this:
```json
"Umbraco": {
"CMS": {
"NewBackOffice":{
"Security":{
"BackOfficeHost": "http://localhost:5173",
"AuthorizeCallbackPathName": "/"
},

View File

@@ -31,7 +31,6 @@
"./extension-registry": "./dist-cms/packages/core/extension-registry/index.js",
"./id": "./dist-cms/packages/core/id/index.js",
"./localization": "./dist-cms/packages/core/localization/index.js",
"./macro": "./dist-cms/packages/core/macro/index.js",
"./menu": "./dist-cms/packages/core/menu/index.js",
"./modal": "./dist-cms/packages/core/modal/index.js",
"./notification": "./dist-cms/packages/core/notification/index.js",

View File

@@ -1,42 +0,0 @@
.umb-macro-holder {
border: 3px dotted var(--uui-palette-spanish-pink-light);
padding: 7px;
display: block;
margin: 3px;
}
.umb-macro-holder.loading {
background: url(assets/img/loader.gif) right no-repeat;
background-size: 18px;
background-position-x: 99%;
}
.umb-embed-holder {
position: relative;
}
.umb-embed-holder>* {
user-select: none;
pointer-events: none;
}
.umb-embed-holder[data-mce-selected] {
outline: 2px solid var(--uui-palette-spanish-pink-light);
}
.umb-embed-holder::before {
z-index: 1000;
width: 100%;
height: 100%;
position: absolute;
content: ' ';
}
.umb-embed-holder[data-mce-selected]::before {
background: rgba(0, 0, 0, 0.025);
}
*[data-mce-selected='inline-boundary'] {
background: rgba(0, 0, 0, 0.025);
outline: 2px solid var(--uui-palette-spanish-pink-light);
}

View File

@@ -3217,7 +3217,7 @@ export default {
minimalLevelDescription: 'We will only send an anonymized site ID to let us know that the site exists.',
basicLevelDescription: 'We will send an anonymized site ID, Umbraco version, and packages installed',
detailedLevelDescription:
'\n We will send:\n <ul>\n <li>Anonymized site ID, Umbraco version, and packages installed.</li>\n <li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use.</li>\n <li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>\n <li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode.</li>\n </ul>\n <em>We might change what we send on the Detailed level in the future. If so, it will be listed above.\n <br>By choosing "Detailed" you agree to current and future anonymized information being collected.</em>\n ',
'\n We will send:\n <ul>\n <li>Anonymized site ID, Umbraco version, and packages installed.</li>\n <li>Number of: Root nodes, Content nodes, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use.</li>\n <li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>\n <li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode.</li>\n </ul>\n <em>We might change what we send on the Detailed level in the future. If so, it will be listed above.\n <br>By choosing "Detailed" you agree to current and future anonymized information being collected.</em>\n ',
},
umbId: {
editProfile: 'Edit your Umbraco ID profile',

View File

@@ -1522,7 +1522,7 @@ export default {
addGroup: 'Add group',
inheritedFrom: 'Inherited from',
addProperty: 'Add property',
editProperty : 'Edit property',
editProperty: 'Edit property',
requiredLabel: 'Required label',
enableListViewHeading: 'Enable list view',
enableListViewDescription:
@@ -2470,7 +2470,7 @@ export default {
minimalLevelDescription: 'We will only send an anonymized site ID to let us know that the site exists.',
basicLevelDescription: 'We will send an anonymized site ID, Umbraco version, and packages installed',
detailedLevelDescription:
'\n We will send:\n <ul>\n <li>Anonymized site ID, Umbraco version, and packages installed.</li>\n <li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use.</li>\n <li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>\n <li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode.</li>\n </ul>\n <em>We might change what we send on the Detailed level in the future. If so, it will be listed above.\n <br>By choosing "Detailed" you agree to current and future anonymized information being collected.</em>\n ',
'\n We will send:\n <ul>\n <li>Anonymized site ID, Umbraco version, and packages installed.</li>\n <li>Number of: Root nodes, Content nodes, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, Backoffice external login providers, and Property Editors in use.</li>\n <li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li>\n <li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, whether the delivery API is enabled, and allows public access, and if you are in debug mode.</li>\n </ul>\n <em>We might change what we send on the Detailed level in the future. If so, it will be listed above.\n <br>By choosing "Detailed" you agree to current and future anonymized information being collected.</em>\n ',
},
umbId: {
editProfile: 'Edit your Umbraco ID profile',

View File

@@ -0,0 +1,49 @@
.umb-macro-holder {
border: 3px dotted red;
padding: 7px;
margin: 3px;
display: block;
position: relative;
}
.umb-macro-holder::after {
content: 'Macros are no longer supported. Please use the block picker instead.';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
background-color: rgba(0, 0, 0, 0.7);
padding: 10px;
border-radius: 5px;
}
.umb-embed-holder {
position: relative;
}
.umb-embed-holder > * {
user-select: none;
pointer-events: none;
}
.umb-embed-holder[data-mce-selected] {
outline: 2px solid var(--uui-palette-spanish-pink-light);
}
.umb-embed-holder::before {
z-index: 1000;
width: 100%;
height: 100%;
position: absolute;
content: ' ';
}
.umb-embed-holder[data-mce-selected]::before {
background: rgba(0, 0, 0, 0.025);
}
*[data-mce-selected='inline-boundary'] {
background: rgba(0, 0, 0, 0.025);
outline: 2px solid var(--uui-palette-spanish-pink-light);
}

View File

@@ -27,7 +27,7 @@ export class UmbExtensionsElementInitializer<
> {
//
#extensionRegistry;
private _defaultElement?: string;
#defaultElement?: string;
#props?: Record<string, unknown>;
public get properties() {
@@ -50,7 +50,7 @@ export class UmbExtensionsElementInitializer<
) {
super(host, extensionRegistry, type, filter, onChange);
this.#extensionRegistry = extensionRegistry;
this._defaultElement = defaultElement;
this.#defaultElement = defaultElement;
this._init();
}
@@ -60,7 +60,7 @@ export class UmbExtensionsElementInitializer<
this.#extensionRegistry,
manifest.alias,
this._extensionChanged,
this._defaultElement,
this.#defaultElement,
) as ControllerType;
extController.properties = this.#props;

View File

@@ -510,6 +510,7 @@ export const data: Array<UmbMockDataTypeModel> = [
{
label: 'Mocked Block Type for Block List',
contentElementTypeKey: '4f68ba66-6fb2-4778-83b8-6ab4ca3a7c5c',
settingsElementTypeKey: 'all-property-editors-document-type-id',
icon: 'icon-server-alt',
},
{
@@ -549,7 +550,7 @@ export const data: Array<UmbMockDataTypeModel> = [
},
{
alias: 'useInlineEditingAsDefault',
value: true,
value: false,
},
{
alias: 'useLiveEditing',
@@ -729,7 +730,7 @@ export const data: Array<UmbMockDataTypeModel> = [
'+a[id|style|rel|data-id|data-udi|rev|charset|hreflang|dir|lang|tabindex|accesskey|type|name|href|target|title|class|onfocus|onblur|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup],-strong/-b[class|style],-em/-i[class|style],-strike[class|style],-s[class|style],-u[class|style],#p[id|style|dir|class|align],-ol[class|reversed|start|style|type],-ul[class|style],-li[class|style],br[class],img[id|dir|lang|longdesc|usemap|style|class|src|onmouseover|onmouseout|border|alt=|title|hspace|vspace|width|height|align|umbracoorgwidth|umbracoorgheight|onresize|onresizestart|onresizeend|rel|data-id],-sub[style|class],-sup[style|class],-blockquote[dir|style|class],-table[border=0|cellspacing|cellpadding|width|height|class|align|summary|style|dir|id|lang|bgcolor|background|bordercolor],-tr[id|lang|dir|class|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor],tbody[id|class],thead[id|class],tfoot[id|class],#td[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|bgcolor|background|bordercolor|scope],-th[id|lang|dir|class|colspan|rowspan|width|height|align|valign|style|scope],caption[id|lang|dir|class|style],-div[id|dir|class|align|style],-span[class|align|style],-pre[class|align|style],address[class|align|style],-h1[id|dir|class|align|style],-h2[id|dir|class|align|style],-h3[id|dir|class|align|style],-h4[id|dir|class|align|style],-h5[id|dir|class|align|style],-h6[id|style|dir|class|align|style],hr[class|style],small[class|style],dd[id|class|title|style|dir|lang],dl[id|class|title|style|dir|lang],dt[id|class|title|style|dir|lang],object[class|id|width|height|codebase|*],param[name|value|_value|class],embed[type|width|height|src|class|*],map[name|class],area[shape|coords|href|alt|target|class],bdo[class],button[class],iframe[*],figure,figcaption,video[*],audio[*],picture[*],source[*],canvas[*]',
},
{ alias: 'invalidElements', value: 'font' },
// { alias: 'stylesheets', value: ['/css/rte-content.css'] },
{ alias: 'stylesheets', value: [] },
{
alias: 'toolbar',
value: [
@@ -751,7 +752,6 @@ export const data: Array<UmbMockDataTypeModel> = [
'anchor',
'table',
'umbmediapicker',
'umbmacro',
'umbembeddialog',
],
},

View File

@@ -34,8 +34,18 @@ export const data: Array<UmbMockDocumentModel> = [
alias: 'richTextEditor',
culture: null,
segment: null,
value:
'Some value for the RTE with an <a href="http://foo.com">external link</a> and an <a href="/{localLink:umb://document/c05da24d7740447b9cdcbd8ce2172e38}">internal link</a> foo foo <div class="umb-macro-holder TestMacro umb-macro-mce_1 mceNonEditable"><!-- <?UMBRACO_MACRO macroAlias="TestMacro" /> --><ins>Macro alias: <strong>TestMacro</strong></ins></div>',
value: {
blocks: {},
markup: `
<p>
Some value for the RTE with an <a href="https://google.com">external link</a> and an <a href="/{localLink:umb://document/c05da24d7740447b9cdcbd8ce2172e38}">internal link</a> foo foo
</p>
<div class="umb-macro-holder TestMacro umb-macro-mce_1 mceNonEditable"><!-- <?UMBRACO_MACRO macroAlias="TestMacro" /> --><ins>Macro alias: <strong>TestMacro</strong></ins></div>
<p>The following tests the embed plugin:</p>
<div class="mceNonEditable umb-embed-holder" data-embed-height="240" data-embed-width="360" data-embed-constrain="false"><iframe width="360" height="240" src="https://www.youtube.com/embed/QRIWz9SotY4?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen="allowfullscreen" title="Sleep Token - The Summoning"></iframe></div>
<p>End of test content</p>
`,
},
},
{
alias: 'email',
@@ -176,6 +186,7 @@ export const data: Array<UmbMockDocumentModel> = [
'Umbraco.BlockList': [
{
contentUdi: '1234',
settingsUdi: '5678',
},
],
},
@@ -186,7 +197,13 @@ export const data: Array<UmbMockDocumentModel> = [
elementProperty: 'Hello world',
},
],
settingsData: [],
settingsData: [
{
udi: '5678',
contentTypeKey: 'all-property-editors-document-type-id',
elementProperty: 'Hello world',
},
],
},
},
{

View File

@@ -17,26 +17,6 @@ export const healthGroups: Array<HealthCheckGroupWithResultResponseModel & { nam
{
name: 'Configuration',
checks: [
{
id: '1c6c0a39-d673-4794-b734-1bec45d6a363',
results: [
{
message: `MacroErrors are set to 'Throw' which will prevent some or all pages in your site from loading
completely if there are any errors in macros. Rectifying this will set the value to 'Inline'. `,
resultType: StatusResultTypeModel.ERROR,
readMoreLink: 'https://umbra.co/healthchecks-macro-errors',
actions: [
{
healthCheckId: 'id123',
name: 'Action name',
alias: 'Action alias',
description: 'Action description',
valueRequired: true,
},
],
},
],
},
{
id: '3e2f7b14-4b41-452b-9a30-e67fbc8e1206',
results: [
@@ -217,12 +197,6 @@ export const healthGroupsWithoutResult: HealthCheckGroupPresentationModel[] = [
{
name: 'Configuration',
checks: [
{
id: 'd0f7599e-9b2a-4d9e-9883-81c7edc5616f',
name: 'Macro errors',
description:
'Checks to make sure macro errors are not set to throw a YSOD (yellow screen of death), which would prevent certain or all pages from loading completely.',
},
{
id: '3e2f7b14-4b41-452b-9a30-e67fbc8e1206',
name: 'Notification Email Settings',

View File

@@ -30,7 +30,7 @@ export const handlers = [
{
level: TelemetryLevelModel.DETAILED,
description:
'We will send:<ul><li>Anonymized site ID, umbraco version, and packages installed.</li><li>Number of: Root nodes, Content nodes, Macros, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use.</li><li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li><li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, and if you are in debug mode.</li></ul><i>We might change what we send on the Detailed level in the future. If so, it will be listed above.<br>By choosing "Detailed" you agree to current and future anonymized information being collected.</i>',
'We will send:<ul><li>Anonymized site ID, umbraco version, and packages installed.</li><li>Number of: Root nodes, Content nodes, Media, Document Types, Templates, Languages, Domains, User Group, Users, Members, and Property Editors in use.</li><li>System information: Webserver, server OS, server framework, server OS language, and database provider.</li><li>Configuration settings: Modelsbuilder mode, if custom Umbraco path exists, ASP environment, and if you are in debug mode.</li></ul><i>We might change what we send on the Detailed level in the future. If so, it will be listed above.<br>By choosing "Detailed" you agree to current and future anonymized information being collected.</i>',
},
],
},

View File

@@ -28,6 +28,9 @@ export class UmbPropertyEditorUIBlockListBlockElement extends UmbLitElement impl
@state()
_contentUdi?: string;
@state()
_hasSettings = false;
@state()
_label = '';
@@ -37,6 +40,12 @@ export class UmbPropertyEditorUIBlockListBlockElement extends UmbLitElement impl
@state()
_inlineEditingMode?: boolean;
// TODO: Move type for the Block Properties, and use it on the Element Interface for the Manifest.
@state()
_blockViewProps: {
label?: string;
} = {};
constructor() {
super();
@@ -46,7 +55,11 @@ export class UmbPropertyEditorUIBlockListBlockElement extends UmbLitElement impl
this.observe(this.#context.contentUdi, (contentUdi) => {
this._contentUdi = contentUdi;
});
this.observe(this.#context.blockTypeSettingsElementTypeKey, (blockTypeSettingsElementTypeKey) => {
this._hasSettings = !!blockTypeSettingsElementTypeKey;
});
this.observe(this.#context.label, (label) => {
this._blockViewProps.label = label;
this._label = label;
});
this.observe(this.#context.inlineEditingMode, (inlineEditingMode) => {
@@ -70,22 +83,32 @@ export class UmbPropertyEditorUIBlockListBlockElement extends UmbLitElement impl
}
#renderRefBlock() {
return html`<umb-ref-list-block .name=${this._label}></umb-ref-list-block>`;
return html`<umb-ref-list-block .label=${this._label}></umb-ref-list-block>`;
}
#renderInlineBlock() {
return html`<umb-inline-list-block .name=${this._label}></umb-inline-list-block>`;
return html`<umb-inline-list-block .label=${this._label}></umb-inline-list-block>`;
}
#renderBlock() {
return html`
${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()}
<umb-extension-slot
type="blockEditorCustomView"
default-element=${this._inlineEditingMode ? 'umb-inline-list-block' : 'umb-ref-list-block'}
.props=${this._blockViewProps}
>${this._inlineEditingMode ? this.#renderInlineBlock() : this.#renderRefBlock()}</umb-extension-slot
>
<uui-action-bar>
${this._workspaceEditPath
? html`<uui-button label="edit" compact href=${this._workspaceEditPath}>
<uui-icon name="icon-edit"></uui-icon>
</uui-button>`
: ''}
${this._workspaceEditPath && this._hasSettings
? html`<uui-button label="Edit settings" compact href=${this._workspaceEditPath + '/view/settings'}>
<uui-icon name="icon-settings"></uui-icon>
</uui-button>`
: ''}
<uui-button label="delete" compact @click=${this.#requestDelete}>
<uui-icon name="icon-remove"></uui-icon>
</uui-button>

View File

@@ -3,7 +3,7 @@ import type { UMB_BLOCK_WORKSPACE_CONTEXT } from '../../../block/index.js';
import { UMB_BLOCK_WORKSPACE_ALIAS } from '../../../block/index.js';
import { UmbExtensionsApiInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { css, customElement, html, state } from '@umbraco-cms/backoffice/external/lit';
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import '../../../block/workspace/views/edit/block-workspace-view-edit-no-router.element.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
@@ -17,8 +17,8 @@ export class UmbInlineListBlockElement extends UmbLitElement {
#workspaceContext?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE;
#contentUdi?: string;
@state()
_label = '';
@property({ type: String })
label?: string;
@state()
_isOpen = false;
@@ -36,9 +36,6 @@ export class UmbInlineListBlockElement extends UmbLitElement {
},
'observeContentUdi',
);
this.observe(blockContext.label, (label) => {
this._label = label;
});
});
this.observe(umbExtensionsRegistry.getByTypeAndAlias('workspace', UMB_BLOCK_WORKSPACE_ALIAS), (manifest) => {
if (manifest) {
@@ -73,7 +70,7 @@ export class UmbInlineListBlockElement extends UmbLitElement {
}}>
<uui-icon name="icon-document"></uui-icon>
<uui-symbol-expand .open=${this._isOpen}></uui-symbol-expand>
<span>${this._label}</span>
<span>${this.label}</span>
</button>
${this._isOpen === true
? html`<umb-block-workspace-view-edit-no-router></umb-block-workspace-view-edit-no-router>`
@@ -82,6 +79,7 @@ export class UmbInlineListBlockElement extends UmbLitElement {
}
static styles = [
UmbTextStyles,
css`
#accordion-button {
display: flex;

View File

@@ -1,6 +1,5 @@
import { UMB_BLOCK_LIST_CONTEXT } from '../../context/block-list.context-token.js';
import { css, customElement, html, property, state } from '@umbraco-cms/backoffice/external/lit';
import { UUIRefNodeElement } from '@umbraco-cms/backoffice/external/uui';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
/**
@@ -10,7 +9,7 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
export class UmbRefListBlockElement extends UmbLitElement {
//
@property({ type: String })
name?: string;
label?: string;
@state()
_workspaceEditPath?: string;
@@ -31,13 +30,12 @@ export class UmbRefListBlockElement extends UmbLitElement {
render() {
// href=${this._workspaceEditPath ?? '#'}
return html`<uui-ref-node border .name=${this.name ?? ''}></uui-ref-node>`;
return html`<uui-ref-node border .name=${this.label ?? ''}></uui-ref-node>`;
}
static styles = [
...UUIRefNodeElement.styles,
css`
:host {
uui-ref-node {
min-height: var(--uui-size-16);
}
`,

View File

@@ -0,0 +1,41 @@
import { UMB_BLOCK_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/block';
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
import type {
ManifestCondition,
UmbConditionConfigBase,
UmbConditionControllerArguments,
UmbExtensionCondition,
} from '@umbraco-cms/backoffice/extension-api';
export class UmbBlockWorkspaceHasSettingsCondition extends UmbBaseController implements UmbExtensionCondition {
config: BlockWorkspaceHasSettingsConditionConfig;
permitted = false;
#onChange: () => void;
constructor(args: UmbConditionControllerArguments<BlockWorkspaceHasSettingsConditionConfig>) {
super(args.host);
this.config = args.config;
this.#onChange = args.onChange;
this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (context) => {
this.observe(
context.settings.contentTypeId,
(settingsContentTypeId) => {
this.permitted = !!settingsContentTypeId;
this.#onChange();
},
'observeSettingsElementTypeId',
);
});
}
}
export type BlockWorkspaceHasSettingsConditionConfig =
UmbConditionConfigBase<'Umb.Condition.BlockWorkspaceHasSettings'>;
export const manifest: ManifestCondition = {
type: 'condition',
name: 'Block Has Settings Condition',
alias: 'Umb.Condition.BlockWorkspaceHasSettings',
api: UmbBlockWorkspaceHasSettingsCondition,
};

View File

@@ -0,0 +1 @@
export * from './block-workspace-has-settings.condition.js';

View File

@@ -60,7 +60,7 @@ export abstract class UmbBlockContext<
}
constructor(host: UmbControllerHost, blockManagerContextToken: BlockManagerContextTokenType) {
super(host, UMB_BLOCK_CONTEXT.toString());
super(host, UMB_BLOCK_ENTITY_CONTEXT.toString());
// Consume block manager:
this.consumeContext(blockManagerContextToken, (manager) => {
@@ -198,6 +198,6 @@ export abstract class UmbBlockContext<
}
}
export const UMB_BLOCK_CONTEXT = new UmbContextToken<
export const UMB_BLOCK_ENTITY_CONTEXT = new UmbContextToken<
UmbBlockContext<typeof UMB_BLOCK_MANAGER_CONTEXT, typeof UMB_BLOCK_MANAGER_CONTEXT.TYPE>
>('UmbBlockContext');

View File

@@ -1 +1 @@
export * from './block.context.js';
export * from './block-entity.context.js';

View File

@@ -1,5 +1,6 @@
export * from './conditions/index.js';
export * from './context/index.js';
export * from './manager/index.js';
export * from './modals/index.js';
export * from './workspace/index.js';
export * from './types.js';
export * from './workspace/index.js';

View File

@@ -4,13 +4,8 @@ import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState, UmbClassState, UmbStringState } from '@umbraco-cms/backoffice/observable-api';
import { UmbDocumentTypeDetailRepository } from '@umbraco-cms/backoffice/document-type';
import { buildUdi, getKeyFromUdi } from '@umbraco-cms/backoffice/utils';
import type {
UmbBlockTypeBaseModel,
UmbBlockWorkspaceData} from '@umbraco-cms/backoffice/block';
import {
UMB_BLOCK_MANAGER_CONTEXT,
UMB_BLOCK_WORKSPACE_MODAL
} from '@umbraco-cms/backoffice/block';
import type { UmbBlockTypeBaseModel, UmbBlockWorkspaceData } from '@umbraco-cms/backoffice/block';
import { UMB_BLOCK_MANAGER_CONTEXT, UMB_BLOCK_WORKSPACE_MODAL } from '@umbraco-cms/backoffice/block';
import { UmbModalRouteRegistrationController } from '@umbraco-cms/backoffice/modal';
import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type';
import { UmbId } from '@umbraco-cms/backoffice/id';
@@ -72,6 +67,7 @@ export abstract class UmbBlockManagerContext<
constructor(host: UmbControllerHost) {
super(host, UMB_BLOCK_MANAGER_CONTEXT);
// TODO: This might will need the property alias as part of the URL, to avoid collision if multiple of these Editor on same Node.
// IDEA: Make a Workspace registration controller that can be used to register a workspace, which does both edit and create?.
new UmbModalRouteRegistrationController(this, UMB_BLOCK_WORKSPACE_MODAL)
.addAdditionalPath('block')

View File

@@ -1,4 +1,5 @@
import { manifest as blockWorkspaceHasSettingsConditionManifest } from './conditions/block-workspace-has-settings.condition.js';
import { manifests as modalManifests } from './modals/manifests.js';
import { manifests as workspaceManifests } from './workspace/manifests.js';
export const manifests = [...modalManifests, ...workspaceManifests];
export const manifests = [...modalManifests, ...workspaceManifests, blockWorkspaceHasSettingsConditionManifest];

View File

@@ -10,6 +10,7 @@ import { UMB_BLOCK_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/block';
import { buildUdi } from '@umbraco-cms/backoffice/utils';
import { UMB_MODAL_CONTEXT } from '@umbraco-cms/backoffice/modal';
export type UmbBlockWorkspaceElementManagerNames = 'content' | 'settings';
export class UmbBlockWorkspaceContext<
LayoutDataType extends UmbBlockLayoutBaseModel = UmbBlockLayoutBaseModel,
> extends UmbEditableWorkspaceContextBase<LayoutDataType> {

View File

@@ -6,9 +6,9 @@ import type { UmbRoute } from '@umbraco-cms/backoffice/router';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { UmbWorkspaceIsNewRedirectController } from '@umbraco-cms/backoffice/workspace';
import type { UmbApi} from '@umbraco-cms/backoffice/extension-api';
import type { UmbApi } from '@umbraco-cms/backoffice/extension-api';
import { UmbExtensionsApiInitializer, createExtensionApi } from '@umbraco-cms/backoffice/extension-api';
import type { ManifestWorkspace} from '@umbraco-cms/backoffice/extension-registry';
import type { ManifestWorkspace } from '@umbraco-cms/backoffice/extension-registry';
import { umbExtensionsRegistry } from '@umbraco-cms/backoffice/extension-registry';
import { decodeFilePath } from '@umbraco-cms/backoffice/utils';
@@ -31,7 +31,7 @@ export class UmbBlockWorkspaceElement extends UmbLitElement {
createExtensionApi(manifest, [this, { manifest: manifest }]).then((context) => {
if (context) {
this.#gotWorkspaceContext(context);
// TODO: We need to recreate when ID changed?
// TODO: Do we need to recreate when ID changed? Or is that a responsibility of the context it self?
new UmbExtensionsApiInitializer(this, umbExtensionsRegistry, 'workspaceContext', [
this,
this.#workspaceContext,

View File

@@ -41,6 +41,7 @@ export const manifests: Array<ManifestTypes> = [
label: 'Content',
pathname: 'content',
icon: 'icon-document',
blockElementManagerName: 'content',
},
conditions: [
{
@@ -48,5 +49,27 @@ export const manifests: Array<ManifestTypes> = [
match: UMB_BLOCK_WORKSPACE_ALIAS,
},
],
},
} as any,
{
type: 'workspaceView',
alias: 'Umb.WorkspaceView.Block.Settings',
name: 'Block Workspace Settings View',
js: () => import('./views/edit/block-workspace-view-edit.element.js'),
weight: 1000,
meta: {
label: 'Settings',
pathname: 'settings',
icon: 'icon-settings',
blockElementManagerName: 'settings',
},
conditions: [
{
alias: 'Umb.Condition.WorkspaceAlias',
match: UMB_BLOCK_WORKSPACE_ALIAS,
},
{
alias: 'Umb.Condition.BlockWorkspaceHasSettings',
},
],
} as any,
];

View File

@@ -39,7 +39,6 @@ export class UmbBlockWorkspaceViewEditNoRouterElement extends UmbLitElement impl
this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (workspaceContext) => {
this._workspaceContext = workspaceContext;
console.log('workspaceContext.content.structure', workspaceContext.content.structure);
this._tabsStructureHelper.setStructureManager(workspaceContext.content.structure);
this._observeRootGroups();
});

View File

@@ -1,8 +1,9 @@
import { UMB_BLOCK_WORKSPACE_CONTEXT } from '../../block-workspace.context-token.js';
import type { UmbBlockWorkspaceElementManagerNames } from '../../block-workspace.context.js';
import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type {
PropertyContainerTypes,
UmbPropertyContainerTypes,
UmbContentTypeModel,
UmbPropertyTypeModel,
} from '@umbraco-cms/backoffice/content-type';
@@ -11,24 +12,34 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
@customElement('umb-block-workspace-view-edit-properties')
export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement {
@property({ attribute: false })
public get managerName(): UmbBlockWorkspaceElementManagerNames | undefined {
return this.#managerName;
}
public set managerName(value: UmbBlockWorkspaceElementManagerNames | undefined) {
this.#managerName = value;
this.#setStructureManager();
}
#managerName?: UmbBlockWorkspaceElementManagerNames;
#blockWorkspace?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE;
#propertyStructureHelper = new UmbContentTypePropertyStructureHelper<UmbContentTypeModel>(this);
@property({ type: String, attribute: 'container-name', reflect: false })
public get containerName(): string | undefined {
return this._propertyStructureHelper.getContainerName();
return this.#propertyStructureHelper.getContainerName();
}
public set containerName(value: string | undefined) {
this._propertyStructureHelper.setContainerName(value);
this.#propertyStructureHelper.setContainerName(value);
}
@property({ type: String, attribute: 'container-type', reflect: false })
public get containerType(): PropertyContainerTypes | undefined {
return this._propertyStructureHelper.getContainerType();
public get containerType(): UmbPropertyContainerTypes | undefined {
return this.#propertyStructureHelper.getContainerType();
}
public set containerType(value: PropertyContainerTypes | undefined) {
this._propertyStructureHelper.setContainerType(value);
public set containerType(value: UmbPropertyContainerTypes | undefined) {
this.#propertyStructureHelper.setContainerType(value);
}
_propertyStructureHelper = new UmbContentTypePropertyStructureHelper<UmbContentTypeModel>(this);
@state()
_propertyStructure: Array<UmbPropertyTypeModel> = [];
@@ -36,13 +47,23 @@ export class UmbBlockWorkspaceViewEditPropertiesElement extends UmbLitElement {
super();
this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (workspaceContext) => {
this._propertyStructureHelper.setStructureManager(workspaceContext.content.structure);
});
this.observe(this._propertyStructureHelper.propertyStructure, (propertyStructure) => {
this._propertyStructure = propertyStructure;
this.#blockWorkspace = workspaceContext;
this.#setStructureManager();
});
}
#setStructureManager() {
if (!this.#blockWorkspace || !this.#managerName) return;
this.#propertyStructureHelper.setStructureManager(this.#blockWorkspace[this.#managerName].structure);
this.observe(
this.#propertyStructureHelper.propertyStructure,
(propertyStructure) => {
this._propertyStructure = propertyStructure;
},
'observePropertyStructure',
);
}
render() {
return repeat(
this._propertyStructure,

View File

@@ -7,29 +7,42 @@ import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { PropertyTypeContainerModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
import './block-workspace-view-edit-properties.element.js';
// eslint-disable-next-line import/order
import type { UmbBlockWorkspaceElementManagerNames } from '../../block-workspace.context.js';
@customElement('umb-block-workspace-view-edit-tab')
export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement {
private _tabName?: string | undefined;
@property({ attribute: false })
public get managerName(): UmbBlockWorkspaceElementManagerNames | undefined {
return this.#managerName;
}
public set managerName(value: UmbBlockWorkspaceElementManagerNames | undefined) {
this.#managerName = value;
this.#setStructureManager();
}
#managerName?: UmbBlockWorkspaceElementManagerNames;
#blockWorkspace?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE;
#groupStructureHelper = new UmbContentTypeContainerStructureHelper<UmbContentTypeModel>(this);
@property({ type: String })
public get tabName(): string | undefined {
return this._groupStructureHelper.getName();
return this.#groupStructureHelper.getName();
}
public set tabName(value: string | undefined) {
if (value === this._tabName) return;
const oldValue = this._tabName;
this._tabName = value;
this._groupStructureHelper.setName(value);
this.#groupStructureHelper.setName(value);
this.requestUpdate('tabName', oldValue);
}
private _tabName?: string | undefined;
@property({ type: Boolean })
public get noTabName(): boolean {
return this._groupStructureHelper.getIsRoot();
return this.#groupStructureHelper.getIsRoot();
}
public set noTabName(value: boolean) {
this._groupStructureHelper.setIsRoot(value);
this.#groupStructureHelper.setIsRoot(value);
}
private _ownerTabId?: string | null;
@@ -40,7 +53,7 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement {
public set ownerTabId(value: string | null | undefined) {
if (value === this._ownerTabId) return;
this._ownerTabId = value;
this._groupStructureHelper.setOwnerId(value);
this.#groupStructureHelper.setOwnerId(value);
}
/**
@@ -50,8 +63,6 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement {
@property({ type: Boolean, reflect: false })
hideSingleGroup = false;
_groupStructureHelper = new UmbContentTypeContainerStructureHelper<UmbContentTypeModel>(this);
@state()
_groups: Array<PropertyTypeContainerModelBaseModel> = [];
@@ -62,16 +73,30 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement {
super();
this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (workspaceContext) => {
this._groupStructureHelper.setStructureManager(workspaceContext.content.structure);
});
this.observe(this._groupStructureHelper.containers, (groups) => {
this._groups = groups;
});
this.observe(this._groupStructureHelper.hasProperties, (hasProperties) => {
this._hasProperties = hasProperties;
this.#blockWorkspace = workspaceContext;
this.#setStructureManager();
});
}
#setStructureManager() {
if (!this.#blockWorkspace || !this.#managerName) return;
this.#groupStructureHelper.setStructureManager(this.#blockWorkspace[this.#managerName].structure);
this.observe(
this.#groupStructureHelper.containers,
(groups) => {
this._groups = groups;
},
'observeGroups',
);
this.observe(
this.#groupStructureHelper.hasProperties,
(hasProperties) => {
this._hasProperties = hasProperties;
},
'observeHasProperties',
);
}
render() {
return html`
${this._hasProperties ? this.#renderPart(this._tabName) : ''}
@@ -86,11 +111,13 @@ export class UmbBlockWorkspaceViewEditTabElement extends UmbLitElement {
#renderPart(groupName: string | null | undefined, boxName?: string | null | undefined) {
return this.hideSingleGroup && this._groups.length === 1
? html` <umb-block-workspace-view-edit-properties
.managerName=${this.#managerName}
class="properties"
container-type="Group"
container-name=${groupName || ''}></umb-block-workspace-view-edit-properties>`
: html` <uui-box .headline=${boxName || ''}
><umb-block-workspace-view-edit-properties
.managerName=${this.#managerName}
class="properties"
container-type="Group"
container-name=${groupName || ''}></umb-block-workspace-view-edit-properties

View File

@@ -1,23 +1,34 @@
import { UMB_BLOCK_WORKSPACE_CONTEXT } from '../../block-workspace.context-token.js';
import type { UmbBlockWorkspaceElementManagerNames } from '../../block-workspace.context.js';
import type { UmbBlockWorkspaceViewEditTabElement } from './block-workspace-view-edit-tab.element.js';
import { css, html, customElement, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, state, repeat, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbContentTypeModel } from '@umbraco-cms/backoffice/content-type';
import { UmbContentTypeContainerStructureHelper } from '@umbraco-cms/backoffice/content-type';
import type {
UmbRoute,
UmbRouterSlotChangeEvent,
UmbRouterSlotInitEvent} from '@umbraco-cms/backoffice/router';
import {
encodeFolderName
} from '@umbraco-cms/backoffice/router';
import type { UmbRoute, UmbRouterSlotChangeEvent, UmbRouterSlotInitEvent } from '@umbraco-cms/backoffice/router';
import { encodeFolderName } from '@umbraco-cms/backoffice/router';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import type { PropertyTypeContainerModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry';
import type { ManifestWorkspaceView, UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-registry';
@customElement('umb-block-workspace-view-edit')
export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements UmbWorkspaceViewElement {
@property({ attribute: false })
public get manifest(): ManifestWorkspaceView | undefined {
return;
}
public set manifest(value: ManifestWorkspaceView | undefined) {
this.#managerName = (value?.meta as any).blockElementManagerName ?? 'content';
this.#setStructureManager();
}
#managerName?: UmbBlockWorkspaceElementManagerNames;
#blockWorkspace?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE;
#tabsStructureHelper = new UmbContentTypeContainerStructureHelper<UmbContentTypeModel>(this);
//@state()
//private _hasRootProperties = false;
@state()
private _hasRootGroups = false;
@state()
@@ -32,44 +43,44 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U
@state()
private _activePath = '';
private _workspaceContext?: typeof UMB_BLOCK_WORKSPACE_CONTEXT.TYPE;
private _tabsStructureHelper = new UmbContentTypeContainerStructureHelper<UmbContentTypeModel>(this);
constructor() {
super();
this._tabsStructureHelper.setIsRoot(true);
this._tabsStructureHelper.setContainerChildType('Tab');
this.observe(this._tabsStructureHelper.containers, (tabs) => {
this._tabs = tabs;
this._createRoutes();
});
this.#tabsStructureHelper.setIsRoot(true);
this.#tabsStructureHelper.setContainerChildType('Tab');
// _hasRootProperties can be gotten via _tabsStructureHelper.hasProperties. But we do not support root properties currently.
this.consumeContext(UMB_BLOCK_WORKSPACE_CONTEXT, (workspaceContext) => {
this._workspaceContext = workspaceContext;
this._tabsStructureHelper.setStructureManager(workspaceContext.content.structure);
this._observeRootGroups();
this.#blockWorkspace = workspaceContext;
this.#setStructureManager();
});
}
private _observeRootGroups() {
if (!this._workspaceContext) return;
#setStructureManager() {
if (!this.#blockWorkspace || !this.#managerName) return;
this.#tabsStructureHelper.setStructureManager(this.#blockWorkspace[this.#managerName].structure);
this.observe(
this._workspaceContext.content.structure.hasRootContainers('Group'),
this.#blockWorkspace![this.#managerName!].structure.hasRootContainers('Group'),
(hasRootGroups) => {
this._hasRootGroups = hasRootGroups;
this._createRoutes();
},
'_observeGroups',
'observeGroups',
);
this.observe(
this.#tabsStructureHelper.containers,
(tabs) => {
this._tabs = tabs;
this._createRoutes();
},
'observeTabs',
);
}
private _createRoutes() {
if (!this._tabs || !this._workspaceContext) return;
if (!this._tabs || !this.#blockWorkspace) return;
const routes: UmbRoute[] = [];
if (this._tabs.length > 0) {
@@ -79,10 +90,11 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U
path: `tab/${encodeFolderName(tabName).toString()}`,
component: () => import('./block-workspace-view-edit-tab.element.js'),
setup: (component) => {
(component as UmbBlockWorkspaceViewEditTabElement).managerName = this.#managerName;
(component as UmbBlockWorkspaceViewEditTabElement).tabName = tabName;
// TODO: Consider if we can link these more simple, and not parse this on.
// Instead have the structure manager looking at wether one of the OwnerALikecontainers is in the owner document.
(component as UmbBlockWorkspaceViewEditTabElement).ownerTabId = this._tabsStructureHelper.isOwnerContainer(
(component as UmbBlockWorkspaceViewEditTabElement).ownerTabId = this.#tabsStructureHelper.isOwnerContainer(
tab.id!,
)
? tab.id
@@ -97,6 +109,7 @@ export class UmbBlockWorkspaceViewEditElement extends UmbLitElement implements U
path: '',
component: () => import('./block-workspace-view-edit-tab.element.js'),
setup: (component) => {
(component as UmbBlockWorkspaceViewEditTabElement).managerName = this.#managerName;
(component as UmbBlockWorkspaceViewEditTabElement).noTabName = true;
(component as UmbBlockWorkspaceViewEditTabElement).ownerTabId = null;
},

View File

@@ -1,6 +1,6 @@
import { type ManifestTypes, umbExtensionsRegistry } from '../../extension-registry/index.js';
import type { TemplateResult } from '@umbraco-cms/backoffice/external/lit';
import { css, repeat, customElement, property, state } from '@umbraco-cms/backoffice/external/lit';
import { css, repeat, customElement, property, state, html } from '@umbraco-cms/backoffice/external/lit';
import {
type UmbExtensionElementInitializer,
UmbExtensionsElementInitializer,
@@ -122,11 +122,13 @@ export class UmbExtensionSlotElement extends UmbLitElement {
}
render() {
return repeat(
this._permittedExts,
(ext) => ext.alias,
(ext) => (this.renderMethod ? this.renderMethod(ext) : ext.component),
);
return this._permittedExts.length > 0
? repeat(
this._permittedExts,
(ext) => ext.alias,
(ext) => (this.renderMethod ? this.renderMethod(ext) : ext.component),
)
: html`<slot></slot>`;
}
static styles = css`

View File

@@ -1,6 +1,5 @@
import type { RawEditorOptions } from '@umbraco-cms/backoffice/external/tinymce';
//These are absolutely required in order for the macros to render inline
//we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce
//so we don't have to specify all the normal elements again
export const defaultFallbackConfig: RawEditorOptions = {
@@ -23,7 +22,6 @@ export const defaultFallbackConfig: RawEditorOptions = {
'indent',
'link',
'umbmediapicker',
'umbmacro',
'umbembeddialog',
],
style_formats: [

View File

@@ -42,11 +42,11 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
super.value = newValue;
const newContent = newValue?.toString() ?? '';
if(this.#editorRef && this.#editorRef.getContent() != newContent) {
if (this.#editorRef && this.#editorRef.getContent() != newContent) {
this.#editorRef.setContent(newContent);
}
}
get value(): FormDataEntryValue | FormData {
return super.value;
}
@@ -179,6 +179,8 @@ export class UmbInputTinyMceElement extends FormControlMixin(UmbLitElement) {
(stylesheetPath: string) => `${this.#serverUrl}/css/${stylesheetPath.replace(/\\/g, '/')}`,
) ?? [];
stylesheets.push('/umbraco/backoffice/css/rte-content.css');
// create an object by merging the configuration onto the fallback config
const configurationOptions: RawEditorOptions = {
...defaultFallbackConfig,

View File

@@ -1,9 +1,5 @@
import type {
PropertyContainerTypes,
UmbContentTypePropertyStructureManager,
} from './content-type-structure-manager.class.js';
import type { UmbContentTypeModel } from './types.js';
import type { PropertyTypeContainerModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
import type { UmbContentTypePropertyStructureManager } from './content-type-structure-manager.class.js';
import type { UmbContentTypeModel, UmbPropertyContainerTypes, UmbPropertyTypeContainerModel } from './types.js';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState, UmbBooleanState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
@@ -14,8 +10,8 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
#structure?: UmbContentTypePropertyStructureManager<T>;
private _ownerType?: PropertyContainerTypes = 'Tab';
private _childType?: PropertyContainerTypes = 'Group';
private _ownerType?: UmbPropertyContainerTypes = 'Tab';
private _childType?: UmbPropertyContainerTypes = 'Group';
private _isRoot = false;
/**
* The owner id is the owning container (The container that is begin presented, the container is the parent of the child containers)
@@ -26,12 +22,12 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
// Containers defined in data might be more than actual containers to display as we merge them by name.
// Direct containers are the containers defining the total of this container(Multiple containers with the same name and type)
private _ownerAlikeContainers: PropertyTypeContainerModelBaseModel[] = [];
private _ownerAlikeContainers: UmbPropertyTypeContainerModel[] = [];
// Owner containers are containers owned by the owner Content Type (The specific one up for editing)
private _ownerContainers: PropertyTypeContainerModelBaseModel[] = [];
private _ownerContainers: UmbPropertyTypeContainerModel[] = [];
// State containing the merged containers (only one pr. name):
#containers = new UmbArrayState<PropertyTypeContainerModelBaseModel>([], (x) => x.id);
#containers = new UmbArrayState<UmbPropertyTypeContainerModel>([], (x) => x.id);
readonly containers = this.#containers.asObservable();
#hasProperties = new UmbBooleanState(false);
@@ -53,7 +49,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
this._observeOwnerAlikeContainers();
}
public setType(value?: PropertyContainerTypes) {
public setType(value?: UmbPropertyContainerTypes) {
if (this._ownerType === value) return;
this._ownerType = value;
this._observeOwnerAlikeContainers();
@@ -62,7 +58,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
return this._ownerType;
}
public setContainerChildType(value?: PropertyContainerTypes) {
public setContainerChildType(value?: UmbPropertyContainerTypes) {
if (this._childType === value) return;
this._childType = value;
this._observeOwnerAlikeContainers();
@@ -173,7 +169,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
);
}
private _insertGroupContainers = (groupContainers: PropertyTypeContainerModelBaseModel[]) => {
private _insertGroupContainers = (groupContainers: UmbPropertyTypeContainerModel[]) => {
groupContainers.forEach((group) => {
if (group.name !== null && group.name !== undefined) {
if (!this.#containers.getValue().find((x) => x.name === group.name)) {
@@ -208,7 +204,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
/** Manipulate methods: */
async insertContainer(container: PropertyTypeContainerModelBaseModel, sortOrder = 0) {
async insertContainer(container: UmbPropertyTypeContainerModel, sortOrder = 0) {
await this.#init;
if (!this.#structure) return false;
@@ -232,7 +228,7 @@ export class UmbContentTypeContainerStructureHelper<T extends UmbContentTypeMode
return true;
}
async partialUpdateContainer(containerId: string, partialUpdate: Partial<PropertyTypeContainerModelBaseModel>) {
async partialUpdateContainer(containerId: string, partialUpdate: Partial<UmbPropertyTypeContainerModel>) {
await this.#init;
if (!this.#structure || !containerId || !partialUpdate) return;

View File

@@ -1,8 +1,5 @@
import type {
PropertyContainerTypes,
UmbContentTypePropertyStructureManager,
} from './content-type-structure-manager.class.js';
import type { UmbContentTypeModel, UmbPropertyTypeModel } from './types.js';
import type { UmbContentTypePropertyStructureManager } from './content-type-structure-manager.class.js';
import type { UmbContentTypeModel, UmbPropertyContainerTypes, UmbPropertyTypeModel } from './types.js';
import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api';
import { UmbArrayState, UmbObserverController } from '@umbraco-cms/backoffice/observable-api';
@@ -13,7 +10,7 @@ export class UmbContentTypePropertyStructureHelper<T extends UmbContentTypeModel
#structure?: UmbContentTypePropertyStructureManager<T>;
private _containerType?: PropertyContainerTypes;
private _containerType?: UmbPropertyContainerTypes;
private _isRoot?: boolean;
private _containerName?: string;
@@ -29,8 +26,10 @@ export class UmbContentTypePropertyStructureHelper<T extends UmbContentTypeModel
this.#propertyStructure.sortBy((a, b) => ((a as any).sortOrder ?? 0) - ((b as any).sortOrder ?? 0));
}
get ownerDocumentTypes() {
return this.#structure?.contentTypes;
async ownerDocumentTypes() {
await this.#init;
if (!this.#structure) return;
return this.#structure.contentTypes;
}
public setStructureManager(structure: UmbContentTypePropertyStructureManager<T>) {
@@ -40,7 +39,7 @@ export class UmbContentTypePropertyStructureHelper<T extends UmbContentTypeModel
this._observeGroupContainers();
}
public setContainerType(value?: PropertyContainerTypes) {
public setContainerType(value?: UmbPropertyContainerTypes) {
if (this._containerType === value) return;
this._containerType = value;
this._observeGroupContainers();

View File

@@ -1,7 +1,12 @@
import type { UmbContentTypeModel, UmbPropertyTypeModel } from './types.js';
import type {
UmbContentTypeModel,
UmbPropertyContainerTypes,
UmbPropertyTypeContainerModel,
UmbPropertyTypeModel,
UmbPropertyTypeScaffoldModel,
} from './types.js';
import type { UmbDetailRepository } from '@umbraco-cms/backoffice/repository';
import { UmbId } from '@umbraco-cms/backoffice/id';
import type { PropertyTypeContainerModelBaseModel } from '@umbraco-cms/backoffice/backend-api';
import type { UmbControllerHost, UmbController } from '@umbraco-cms/backoffice/controller-api';
import type { MappingFunction } from '@umbraco-cms/backoffice/observable-api';
import {
@@ -13,10 +18,6 @@ import {
import { incrementString } from '@umbraco-cms/backoffice/utils';
import { UmbBaseController } from '@umbraco-cms/backoffice/class-api';
export type PropertyContainerTypes = 'Group' | 'Tab';
// TODO: get this type from the repository, or use some generic type.
// TODO: Make this a controller on its own:
export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeModel> extends UmbBaseController {
#init!: Promise<unknown>;
@@ -30,8 +31,10 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
x.flatMap((x) => x.containers ?? []),
);
#containers: UmbArrayState<PropertyTypeContainerModelBaseModel> =
new UmbArrayState<PropertyTypeContainerModelBaseModel>([], (x) => x.id);
#containers: UmbArrayState<UmbPropertyTypeContainerModel> = new UmbArrayState<UmbPropertyTypeContainerModel>(
[],
(x) => x.id,
);
constructor(host: UmbControllerHost, typeRepository: UmbDetailRepository<T>) {
super(host);
@@ -178,13 +181,13 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
async createContainer(
contentTypeUnique: string | null,
parentId: string | null = null,
type: PropertyContainerTypes = 'Group',
type: UmbPropertyContainerTypes = 'Group',
sortOrder?: number,
) {
await this.#init;
contentTypeUnique = contentTypeUnique ?? this.#ownerContentTypeUnique!;
const container: PropertyTypeContainerModelBaseModel = {
const container: UmbPropertyTypeContainerModel = {
id: UmbId.new(),
parent: parentId ? { id: parentId } : null,
name: '',
@@ -205,7 +208,7 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
return container;
}
async insertContainer(contentTypeUnique: string | null, container: PropertyTypeContainerModelBaseModel) {
async insertContainer(contentTypeUnique: string | null, container: UmbPropertyTypeContainerModel) {
await this.#init;
contentTypeUnique = contentTypeUnique ?? this.#ownerContentTypeUnique!;
@@ -223,7 +226,7 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
makeContainerNameUniqueForOwnerContentType(
newName: string,
containerType: PropertyContainerTypes = 'Tab',
containerType: UmbPropertyContainerTypes = 'Tab',
parentId: string | null = null,
) {
const ownerRootContainers = this.getOwnerContainers(containerType); //getRootContainers() can't differentiates between compositions and locals
@@ -242,7 +245,7 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
async updateContainer(
contentTypeUnique: string | null,
containerId: string,
partialUpdate: Partial<PropertyTypeContainerModelBaseModel>,
partialUpdate: Partial<UmbPropertyTypeContainerModel>,
) {
await this.#init;
contentTypeUnique = contentTypeUnique ?? this.#ownerContentTypeUnique!;
@@ -273,13 +276,12 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
}
createPropertyScaffold(containerId: string | null = null, sortOrder?: number) {
const property: UmbPropertyTypeModel = {
const property: UmbPropertyTypeScaffoldModel = {
id: UmbId.new(),
containerId: containerId,
container: containerId ? { id: containerId } : null,
alias: '',
name: '',
description: '',
dataTypeId: '',
variesByCulture: false,
variesBySegment: false,
validation: {
@@ -292,7 +294,7 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
labelOnTop: false,
},
sortOrder: sortOrder ?? 0,
} as any; // Sort order was not allowed when this was written.
};
return property;
}
@@ -301,11 +303,12 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
await this.#init;
contentTypeUnique = contentTypeUnique ?? this.#ownerContentTypeUnique!;
const property: UmbPropertyTypeModel = this.createPropertyScaffold(containerId, sortOrder);
const property = this.createPropertyScaffold(containerId, sortOrder);
const properties = [
const properties: Array<UmbPropertyTypeScaffoldModel | UmbPropertyTypeModel> = [
...(this.#contentTypes.getValue().find((x) => x.unique === contentTypeUnique)?.properties ?? []),
];
properties.push(property);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
@@ -448,27 +451,27 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
});
}
rootContainers(containerType: PropertyContainerTypes) {
rootContainers(containerType: UmbPropertyContainerTypes) {
return this.#containers.asObservablePart((data) => {
return data.filter((x) => x.parent === null && x.type === containerType);
});
}
getRootContainers(containerType: PropertyContainerTypes) {
getRootContainers(containerType: UmbPropertyContainerTypes) {
return this.#containers.getValue().filter((x) => x.parent === null && x.type === containerType);
}
hasRootContainers(containerType: PropertyContainerTypes) {
hasRootContainers(containerType: UmbPropertyContainerTypes) {
return this.#containers.asObservablePart((data) => {
return data.filter((x) => x.parent === null && x.type === containerType).length > 0;
});
}
ownerContainersOf(containerType: PropertyContainerTypes) {
ownerContainersOf(containerType: UmbPropertyContainerTypes) {
return this.ownerContentTypeObservablePart((x) => x.containers?.filter((x) => x.type === containerType) ?? []);
}
getOwnerContainers(containerType: PropertyContainerTypes, parentId: string | null = null) {
getOwnerContainers(containerType: UmbPropertyContainerTypes, parentId: string | null = null) {
return this.getOwnerContentType()?.containers?.filter((x) =>
parentId ? x.parent?.id === parentId : x.parent === null && x.type === containerType,
);
@@ -478,14 +481,14 @@ export class UmbContentTypePropertyStructureManager<T extends UmbContentTypeMode
return this.getOwnerContentType()?.containers?.filter((x) => x.id === containerId);
}
containersOfParentKey(parentId: string, containerType: PropertyContainerTypes) {
containersOfParentKey(parentId: string, containerType: UmbPropertyContainerTypes) {
return this.#containers.asObservablePart((data) => {
return data.filter((x) => x.parent?.id === parentId && x.type === containerType);
});
}
// In future this might need to take parentName(parentId lookup) into account as well? otherwise containers that share same name and type will always be merged, but their position might be different and they should not be merged.
containersByNameAndType(name: string, containerType: PropertyContainerTypes) {
containersByNameAndType(name: string, containerType: UmbPropertyContainerTypes) {
return this.#containers.asObservablePart((data) => {
return data.filter((x) => x.name === name && x.type === containerType);
});

View File

@@ -1,9 +1,18 @@
import type {
PropertyTypeContainerModelBaseModel,
CompositionTypeModel,
PropertyTypeModelBaseModel,
ReferenceByIdModel,
} from '@umbraco-cms/backoffice/backend-api';
export type UmbPropertyContainerTypes = 'Group' | 'Tab';
export interface UmbPropertyTypeContainerModel {
id: string;
parent?: ReferenceByIdModel | null;
name?: string | null;
type: UmbPropertyContainerTypes;
sortOrder: number;
}
export interface UmbContentTypeModel {
unique: string;
parentUnique: string | null;
@@ -17,11 +26,15 @@ export interface UmbContentTypeModel {
isElement: boolean;
// TODO: investigate if we need our own model for these
properties: Array<UmbPropertyTypeModel>;
containers: Array<PropertyTypeContainerModelBaseModel>;
containers: Array<UmbPropertyTypeContainerModel>;
allowedContentTypes: Array<UmbContentTypeSortModel>;
compositions: Array<UmbContentTypeCompositionModel>;
}
export interface UmbPropertyTypeScaffoldModel extends Omit<UmbPropertyTypeModel, 'dataType'> {
dataType?: UmbPropertyTypeModel['dataType'];
}
export interface UmbPropertyTypeModel extends Omit<PropertyTypeModelBaseModel, 'dataType'> {
dataType: { unique: string };
}

View File

@@ -5,10 +5,9 @@ import { css, html, customElement, state, repeat, when } from '@umbraco-cms/back
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type {
UmbDataTypePickerFlowDataTypePickerModalData,
UmbDataTypePickerFlowDataTypePickerModalValue} from '@umbraco-cms/backoffice/modal';
import {
UmbModalBaseElement,
UmbDataTypePickerFlowDataTypePickerModalValue,
} from '@umbraco-cms/backoffice/modal';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
@customElement('umb-data-type-picker-flow-data-type-picker-modal')
export class UmbDataTypePickerFlowDataTypePickerModalElement extends UmbModalBaseElement<

View File

@@ -1,4 +1,5 @@
import { manifest as switchConditionManifest } from './switch.condition.js';
import { manifest as menuAliasConditionManifest } from './menu-alias.condition.js';
import { manifest as sectionAliasConditionManifest } from './section-alias.condition.js';
import { manifest as switchConditionManifest } from './switch.condition.js';
export const manifests = [switchConditionManifest, sectionAliasConditionManifest];
export const manifests = [menuAliasConditionManifest, sectionAliasConditionManifest, switchConditionManifest];

View File

@@ -2,6 +2,7 @@ import type { CollectionAliasConditionConfig } from '../../collection/collection
import type { SectionAliasConditionConfig } from './section-alias.condition.js';
import type { SwitchConditionConfig } from './switch.condition.js';
import type { UserPermissionConditionConfig } from '@umbraco-cms/backoffice/user-permission';
import type { BlockWorkspaceHasSettingsConditionConfig } from '@umbraco-cms/backoffice/block';
import type {
WorkspaceAliasConditionConfig,
WorkspaceEntityTypeConditionConfig,
@@ -16,6 +17,7 @@ export type ConditionTypes =
| CollectionAliasConditionConfig
| SectionAliasConditionConfig
| WorkspaceAliasConditionConfig
| BlockWorkspaceHasSettingsConditionConfig
| WorkspaceEntityTypeConditionConfig
| SwitchConditionConfig
| UserPermissionConditionConfig

View File

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

View File

@@ -1,3 +1,4 @@
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 { ManifestDashboard } from './dashboard.model.js';
@@ -40,6 +41,7 @@ import type {
ManifestEntryPoint,
} from '@umbraco-cms/backoffice/extension-api';
export type * from './block-editor-custom-view.model.js';
export type * from './collection.models.js';
export type * from './collection-action.model.js';
export type * from './collection-view.model.js';
@@ -79,6 +81,7 @@ export type * from './workspace.model.js';
export type ManifestTypes =
| ManifestBundle<ManifestTypes>
| ManifestCondition
| ManifestBlockEditorCustomView
| ManifestCollection
| ManifestCollectionView
| ManifestCollectionAction

View File

@@ -15,7 +15,6 @@ export * from './entity-action/index.js';
export * from './entity-bulk-action/index.js';
export * from './extension-registry/index.js';
export * from './id/index.js';
export * from './macro/index.js';
export * from './menu/index.js';
export * from './modal/index.js';
export * from './notification/index.js';

View File

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

View File

@@ -1,148 +0,0 @@
import { UmbContextToken } from '@umbraco-cms/backoffice/context-api';
export interface MacroSyntaxData {
macroAlias: string;
macroParamsDictionary: { [key: string]: string };
syntax?: string;
}
export class UmbMacroService {
/** parses the special macro syntax like <?UMBRACO_MACRO macroAlias="Map" />
* and returns an object with the macro alias and it's parameters
* */
parseMacroSyntax(syntax = '') {
//This regex will match an alias of anything except characters that are quotes or new lines (for legacy reasons, when new macros are created
// their aliases are cleaned an invalid chars are stripped)
const expression =
/(<\?UMBRACO_MACRO (?:.+?)?macroAlias=["']([^"'\n\r]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i;
const match = expression.exec(syntax);
if (!match || match.length < 3) {
return null;
}
const macroAlias = match[2];
//this will leave us with just the parameters
const paramsChunk = match[1]
.trim()
.replace(new RegExp(`UMBRACO_MACRO macroAlias=["']${macroAlias}["']`), '')
.trim();
const paramExpression = /(\w+?)=['"]([\s\S]*?)['"]/g;
const returnVal: MacroSyntaxData = {
macroAlias,
macroParamsDictionary: {},
};
let paramMatch;
while ((paramMatch = paramExpression.exec(paramsChunk))) {
returnVal.macroParamsDictionary[paramMatch[1]] = paramMatch[2];
}
return returnVal;
}
/**
* generates the syntax for inserting a macro into a rich text editor - this is the very old umbraco style syntax *
* @param {MacroSyntaxData} args an object containing the macro alias and it's parameter values
*/
generateMacroSyntax(args: MacroSyntaxData) {
let macroString = `<?UMBRACO_MACRO macroAlias="${args.macroAlias}" `;
for (const [key, val] of Object.entries(args.macroParamsDictionary)) {
//check for null
const valOrEmpty = val ?? '';
//need to detect if the val is a string or an object
let keyVal;
if (typeof valOrEmpty === 'string') {
keyVal = `${key}="${valOrEmpty}"`;
} else {
//if it's not a string we'll send it through the json serializer
const json = JSON.parse(valOrEmpty);
//then we need to url encode it so that it's safe
const encoded = encodeURIComponent(json);
keyVal = `${key}="${encoded}"`;
}
macroString += keyVal;
}
macroString += '/>';
return macroString;
}
/**
* generates the syntax for inserting a macro into an mvc template *
* @param {object} args an object containing the macro alias and it's parameter values
*/
generateMvcSyntax(args: MacroSyntaxData) {
let macroString = `@await Umbraco.RenderMacroAsync("${args.macroAlias}"`;
let hasParams = false;
let paramString = '';
if (args.macroParamsDictionary) {
paramString = ', new {';
for (const [key, val] of Object.entries(args.macroParamsDictionary)) {
hasParams = true;
const keyVal = `${key}="${val ? val : ''}", `;
paramString += keyVal;
}
//remove the last , and trailing whitespace
paramString = paramString.trimEnd().replace(/,*$/, '');
paramString += '}';
}
if (hasParams) {
macroString += paramString;
}
macroString += ')';
return macroString;
}
collectValueData(macro: any, macroParams: any, renderingEngine: any) {
const macroParamsDictionary: { [key: string]: string } = {};
const macroAlias = macro.alias;
if (!macroAlias) {
throw 'The macro object does not contain an alias';
}
macroParams.forEach((item: any) => {
let val = item.value;
if (item.value !== null && item.value !== undefined && typeof item.value !== 'string') {
try {
val = JSON.parse(val);
} catch (e) {
// not json
}
}
//each value needs to be xml escaped!! since the value get's stored as an xml attribute
macroParamsDictionary[item.alias] = encodeURIComponent(val);
});
let syntax;
//get the syntax based on the rendering engine
if (renderingEngine && renderingEngine.toLowerCase() === 'mvc') {
syntax = this.generateMvcSyntax({ macroAlias, macroParamsDictionary });
} else {
syntax = this.generateMacroSyntax({ macroAlias, macroParamsDictionary });
}
return {
macroParamsDictionary,
macroAlias,
syntax,
} as MacroSyntaxData;
}
}
export const UMB_MACRO_SERVICE_CONTEXT = new UmbContextToken<UmbMacroService>(UmbMacroService.name);

View File

@@ -259,7 +259,7 @@ export class UmbPropertySettingsModalElement extends UmbModalBaseElement<
.value=${this.value.description}></uui-textarea>
</div>
<umb-data-type-flow-input
.value=${this.value.dataType.unique}
.value=${this.value.dataType?.unique}
@change=${this.#onDataTypeIdChange}></umb-data-type-flow-input>
<hr />
<div class="container">

View File

@@ -1,10 +1,10 @@
import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbPropertyTypeModel, UmbPropertyTypeScaffoldModel } from '@umbraco-cms/backoffice/content-type';
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export type UmbPropertySettingsModalData = {
documentTypeId: string;
};
export type UmbPropertySettingsModalValue = UmbPropertyTypeModel;
export type UmbPropertySettingsModalValue = UmbPropertyTypeModel | UmbPropertyTypeScaffoldModel;
export const UMB_PROPERTY_SETTINGS_MODAL = new UmbModalToken<
UmbPropertySettingsModalData,

View File

@@ -66,21 +66,6 @@ const pluginManifests: Array<ManifestTinyMcePlugin> = [
],
},
},
{
type: 'tinyMcePlugin',
alias: 'Umb.TinyMcePlugin.MacroPicker',
name: 'Macro Picker TinyMCE Plugin',
js: () => import('./tiny-mce-macropicker.plugin.js'),
meta: {
toolbar: [
{
alias: 'umbmacro',
label: 'Macro',
icon: 'preferences',
},
],
},
},
];
export const manifests = [...pluginManifests];

View File

@@ -1,210 +0,0 @@
import type { MacroSyntaxData} from '@umbraco-cms/backoffice/macro';
import { UmbMacroService } from '@umbraco-cms/backoffice/macro';
import type { TinyMcePluginArguments} from '@umbraco-cms/backoffice/components';
import { UmbTinyMcePluginBase } from '@umbraco-cms/backoffice/components';
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { UMB_CONFIRM_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import type { AstNode } from '@umbraco-cms/backoffice/external/tinymce';
interface DialogData {
richTextEditor: boolean;
macroData?: MacroSyntaxData | null;
activeMacroElement?: HTMLElement;
}
// TODO => This is a quick transplant of the existing macro plugin - needs to be finished, and need to
// determine how to replicate the existing macro service
export default class UmbTinyMceMacroPickerPlugin extends UmbTinyMcePluginBase {
#macroService = new UmbMacroService();
#modalContext?: UmbModalManagerContext;
constructor(args: TinyMcePluginArguments) {
super(args);
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (modalContext) => {
this.#modalContext = modalContext;
});
/** Adds custom rules for the macro plugin and custom serialization */
this.editor.on('preInit', () => {
//this is requires so that we tell the serializer that a 'div' is actually allowed in the root,
// otherwise the cleanup will strip it out
this.editor.serializer.addRules('div');
/** This checks if the div is a macro container, if so, checks if its wrapped in a p tag and then unwraps it (removes p tag) */
this.editor.serializer.addNodeFilter('div', (nodes: Array<AstNode>) => {
for (let i = 0; i < nodes.length; i++) {
if (nodes[i].attr('class') === 'umb-macro-holder' && nodes[i].parent?.name.toLowerCase() === 'p') {
nodes[i].parent?.unwrap();
}
}
});
});
/** when the contents load we need to find any macros declared and load in their content */
this.editor.on('SetContent', () => {
//get all macro divs and load their content
this.editor.dom.select('.umb-macro-holder.mceNonEditable').forEach((macroElement: HTMLElement) => {
this.#loadMacroContent(macroElement as HTMLDivElement, null);
});
});
/** Adds the button instance */
this.editor.ui.registry.addButton('umbmacro', {
icon: 'preferences',
tooltip: 'Insert macro',
/** The insert macro button click event handler */
onAction: () => {
let dialogData: DialogData = {
//flag for use in rte so we only show macros flagged for the editor
richTextEditor: true,
};
//when we click we could have a macro already selected and in that case we'll want to edit the current parameters
//so we'll need to extract them and submit them to the dialog.
const activeMacroElement = this.#getRealMacroElem();
if (activeMacroElement) {
//we have a macro selected so we'll need to parse it's alias and parameters
const comment = Array.from(activeMacroElement.childNodes).find((x) => x.nodeType === 8);
if (!comment) {
throw 'Cannot parse the current macro, the syntax in the editor is invalid';
}
const syntax = comment.textContent?.trim();
const parsed = this.#macroService?.parseMacroSyntax(syntax);
dialogData = {
richTextEditor: false,
macroData: parsed,
activeMacroElement, //pass the active element along so we can retrieve it later
};
}
this.#showMacroPicker(dialogData);
},
});
}
/** loads in the macro content async from the server */
#loadMacroContent(macroDiv?: HTMLDivElement, macroData?: MacroSyntaxData | null) {
//if we don't have the macroData, then we'll need to parse it from the macro div
if (!macroData && macroDiv) {
const comment = Array.from(macroDiv.childNodes).find((x) => x.nodeType === 8);
if (!comment) {
throw 'Cannot parse the current macro, the syntax in the editor is invalid';
}
const syntax = comment.textContent?.trim();
const parsed = this.#macroService?.parseMacroSyntax(syntax);
macroData = parsed;
}
//show the throbber
macroDiv?.classList.add('loading');
// Add the contenteditable="false" attribute
// As just the CSS class of .mceNonEditable is not working by itself?!
macroDiv?.setAttribute('contenteditable', 'false');
// TODO => macro data service?
// const contentId = $routeParams.id;
// //need to wrap in safe apply since this might be occuring outside of angular
// angularHelper.safeApply($rootScope, function () {
// tryExecuteAndNotify(this, macroResource
// .getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary))
// .then(function (htmlResult) {
// $macroDiv.removeClass('loading');
// htmlResult = htmlResult.trim();
// if (htmlResult !== '') {
// const wasDirty = editor.isDirty();
// const $ins = macroDiv?.querySelector('ins');
// $ins.html(htmlResult);
// if (!wasDirty) {
// editor.undoManager.clear();
// }
// }
// });
// });
}
#insertInEditor(macroObject: MacroSyntaxData, activeMacroElement?: HTMLElement) {
//Important note: the TinyMce plugin "noneditable" is used here so that the macro cannot be edited,
// for this to work the mceNonEditable class needs to come last and we also need to use the attribute contenteditable = false
// (even though all the docs and examples say that is not necessary)
//put the macro syntax in comments, we will parse this out on the server side to be used
//for persisting.
const macroSyntaxComment = `<!-- ${macroObject.syntax} -->`;
//create an id class for this element so we can re-select it after inserting
const uniqueId = 'umb-macro-' + this.editor.dom.uniqueId();
let macroDiv = this.editor.dom.create(
'div',
{
class: `umb-macro-holder ${macroObject.macroAlias} ${uniqueId} mceNonEditable`,
contenteditable: 'false',
},
`${macroSyntaxComment}<ins>Macro alias: <strong>${macroObject.macroAlias}</strong></ins>`,
);
//if there's an activeMacroElement then replace it, otherwise set the contents of the selected node
if (activeMacroElement) {
activeMacroElement.replaceWith(macroDiv); //directly replaces the html node
} else {
this.editor.selection.setNode(macroDiv);
}
macroDiv = this.editor.dom.select('div.umb-macro-holder.' + uniqueId)[0] as HTMLDivElement;
this.editor.setDirty(true);
//async load the macro content
this.#loadMacroContent(macroDiv, macroObject);
}
/**
* Because the macro got wrapped in a P tag because of the way 'enter' works in older versions of Umbraco, this
* method will return the macro element if not wrapped in a p, or the p if the macro
* element is the only one inside of it even if we are deep inside an element inside the macro
*/
#getRealMacroElem() {
// Ask the editor for the currently selected element
const element = this.editor?.selection.getNode() as HTMLElement;
if (!element) {
return null;
}
const e = element.closest('.umb-macro-holder');
if (!e || e === null) return null;
if (e.parentNode?.nodeName === 'P') {
//now check if we're the only element
if (element.parentNode?.childNodes.length === 1) {
return e.parentNode as HTMLElement;
}
}
return e as HTMLElement;
}
// TODO => depends on macro picker, which doesn't exist, just showing a generic modal for now
async #showMacroPicker(dialogData: DialogData) {
const modalHandler = this.#modalContext?.open(UMB_CONFIRM_MODAL, {
data: {
headline: 'Macro picker',
content: 'Yet to be implemented',
},
});
if (!modalHandler) return;
const result = await modalHandler.onSubmit();
if (!result) return;
// TODO => object here should be the response from the modal
this.#insertInEditor({} as MacroSyntaxData, dialogData.activeMacroElement);
this.editor.dispatch('Change');
}
}

View File

@@ -39,7 +39,6 @@ const config = new UmbPropertyEditorConfigCollection([
'anchor',
'table',
'umbmediapicker',
'umbmacro',
'umbembeddialog',
],
},

View File

@@ -1,5 +1,5 @@
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { CSSResultGroup} from '@umbraco-cms/backoffice/external/lit';
import type { CSSResultGroup } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, property } from '@umbraco-cms/backoffice/external/lit';
import type { UmbWorkspaceData } from '@umbraco-cms/backoffice/modal';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';

View File

@@ -9,6 +9,7 @@ import type {
import { DocumentTypeResource } from '@umbraco-cms/backoffice/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
import type { UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
/**
* A data source for the Document Type that fetches data from the server
@@ -109,7 +110,7 @@ export class UmbDocumentTypeDetailServerDataSource implements UmbDetailDataSourc
appearance: property.appearance,
};
}),
containers: data.containers,
containers: data.containers as UmbPropertyTypeContainerModel[],
allowedContentTypes: data.allowedDocumentTypes.map((allowedDocumentType) => {
return {
contentType: { unique: allowedDocumentType.documentType.id },

View File

@@ -3,7 +3,7 @@ import './document-type-workspace-view-edit-property.element.js';
import type { UmbDocumentTypeDetailModel } from '../../../types.js';
import { css, html, customElement, property, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { PropertyContainerTypes, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbPropertyContainerTypes, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type';
import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
@@ -80,10 +80,10 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle
}
@property({ type: String, attribute: 'container-type', reflect: false })
public get containerType(): PropertyContainerTypes | undefined {
public get containerType(): UmbPropertyContainerTypes | undefined {
return this._propertyStructureHelper.getContainerType();
}
public set containerType(value: PropertyContainerTypes | undefined) {
public set containerType(value: UmbPropertyContainerTypes | undefined) {
this._propertyStructureHelper.setContainerType(value);
}
@@ -104,7 +104,7 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle
constructor() {
super();
this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => {
this.consumeContext(UMB_WORKSPACE_CONTEXT, async (workspaceContext) => {
this._propertyStructureHelper.setStructureManager(
(workspaceContext as UmbDocumentTypeWorkspaceContext).structure,
);
@@ -116,6 +116,15 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle
},
'_observeIsSorting',
);
const docTypesObservable = await this._propertyStructureHelper.ownerDocumentTypes();
if (!docTypesObservable) return;
this.observe(
docTypesObservable,
(documents) => {
this._ownerDocumentTypes = documents;
},
'observeOwnerDocumentTypes',
);
});
this.observe(this._propertyStructureHelper.propertyStructure, (propertyStructure) => {
this._propertyStructure = propertyStructure;
@@ -134,7 +143,10 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle
return { data: { documentTypeId }, value: propertyData };
})
.onSubmit((value) => {
this.#addProperty(value);
if (!value.dataType) {
throw new Error('No data type selected');
}
this.#addProperty(value as UmbPropertyTypeModel);
})
.observeRouteBuilder((routeBuilder) => {
this._modalRouteNewProperty = routeBuilder(null);
@@ -150,19 +162,6 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle
}
}
connectedCallback(): void {
super.connectedCallback();
const doctypes = this._propertyStructureHelper.ownerDocumentTypes;
if (!doctypes) return;
this.observe(
doctypes,
(documents) => {
this._ownerDocumentTypes = documents;
},
'observeOwnerDocumentTypes',
);
}
async #addProperty(propertyData: UmbPropertyTypeModel) {
const propertyPlaceholder = await this._propertyStructureHelper.addProperty(this._containerId);
if (!propertyPlaceholder) return;
@@ -182,7 +181,7 @@ export class UmbDocumentTypeWorkspaceViewEditPropertiesElement extends UmbLitEle
);
return html`<umb-document-type-workspace-view-edit-property
data-umb-property-id=${ifDefined(property.id)}
data-umb-property-id=${property.id}
owner-document-type-id=${ifDefined(inheritedFromDocument?.unique)}
owner-document-type-name=${ifDefined(inheritedFromDocument?.name)}
?inherited=${property.container?.id !== this.containerId}

View File

@@ -13,7 +13,7 @@ import {
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
import { generateAlias } from '@umbraco-cms/backoffice/utils';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbPropertyTypeModel, UmbPropertyTypeScaffoldModel } from '@umbraco-cms/backoffice/content-type';
/**
* @element umb-document-type-workspace-view-edit-property
@@ -22,22 +22,22 @@ import type { UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type'
*/
@customElement('umb-document-type-workspace-view-edit-property')
export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement {
private _property?: UmbPropertyTypeModel | undefined;
private _property?: UmbPropertyTypeModel | UmbPropertyTypeScaffoldModel | undefined;
/**
* Property, the data object for the property.
* @type {UmbPropertyTypeModel}
* @type {UmbPropertyTypeModel | UmbPropertyTypeScaffoldModel | undefined}
* @attr
* @default undefined
*/
@property({ type: Object })
public get property(): UmbPropertyTypeModel | undefined {
public get property(): UmbPropertyTypeModel | UmbPropertyTypeScaffoldModel | undefined {
return this._property;
}
public set property(value: UmbPropertyTypeModel | undefined) {
public set property(value: UmbPropertyTypeModel | UmbPropertyTypeScaffoldModel | undefined) {
const oldValue = this._property;
this._property = value;
this.#modalRegistration.setUniquePathValue('propertyId', value?.id?.toString());
this.setDataType(this._property?.dataType.unique);
this.setDataType(this._property?.dataType?.unique);
this.requestUpdate('property', oldValue);
}
@@ -96,7 +96,10 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement {
return { data: { documentTypeId }, value: propertyData };
})
.onSubmit((result) => {
this._partialUpdate(result);
if (!result.dataType) {
throw new Error('No dataType found on property');
}
this._partialUpdate(result as UmbPropertyTypeModel);
})
.observeRouteBuilder((routeBuilder) => {
this._modalRoute = routeBuilder(null);
@@ -295,7 +298,7 @@ export class UmbDocumentTypeWorkspacePropertyElement extends UmbLitElement {
renderPropertyTags() {
return this.property
? html`<div class="types">
${this.property.dataType.unique ? html`<uui-tag look="default">${this._dataTypeName}</uui-tag>` : nothing}
${this.property.dataType?.unique ? html`<uui-tag look="default">${this._dataTypeName}</uui-tag>` : nothing}
${this.property.variesByCulture
? html`<uui-tag look="default">
<uui-icon name="icon-shuffle"></uui-icon> ${this.localize.term('contentTypeEditor_cultureVariantLabel')}

View File

@@ -1,7 +1,7 @@
import { UMB_DOCUMENT_WORKSPACE_CONTEXT } from '../../document-workspace.context-token.js';
import { css, html, customElement, property, state, repeat } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { PropertyContainerTypes, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbPropertyContainerTypes, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type';
import { UmbLitElement } from '@umbraco-cms/internal/lit-element';
@@ -16,10 +16,10 @@ export class UmbDocumentWorkspaceViewEditPropertiesElement extends UmbLitElement
}
@property({ type: String, attribute: 'container-type', reflect: false })
public get containerType(): PropertyContainerTypes | undefined {
public get containerType(): UmbPropertyContainerTypes | undefined {
return this._propertyStructureHelper.getContainerType();
}
public set containerType(value: PropertyContainerTypes | undefined) {
public set containerType(value: UmbPropertyContainerTypes | undefined) {
this._propertyStructureHelper.setContainerType(value);
}

View File

@@ -11,14 +11,17 @@ import type { UmbWorkspaceViewElement } from '@umbraco-cms/backoffice/extension-
@customElement('umb-document-workspace-view-edit')
export class UmbDocumentWorkspaceViewEditElement extends UmbLitElement implements UmbWorkspaceViewElement {
//@state()
//private _hasRootProperties = false;
@state()
private _hasRootGroups = false;
@state()
private _routes: UmbRoute[] = [];
@state()
_tabs?: Array<PropertyTypeContainerModelBaseModel>;
private _tabs?: Array<PropertyTypeContainerModelBaseModel>;
@state()
private _routerPath?: string;

View File

@@ -6,6 +6,7 @@ import type { CreateMediaTypeRequestModel, UpdateMediaTypeRequestModel } from '@
import { MediaTypeResource } from '@umbraco-cms/backoffice/backend-api';
import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api';
import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources';
import type { UmbPropertyTypeContainerModel } from '@umbraco-cms/backoffice/content-type';
/**
* A data source for the Media Type that fetches data from the server
@@ -96,7 +97,7 @@ export class UmbMediaTypeServerDataSource implements UmbDetailDataSource<UmbMedi
appearance: property.appearance,
};
}),
containers: data.containers,
containers: data.containers as UmbPropertyTypeContainerModel[],
allowedContentTypes: data.allowedMediaTypes.map((allowedMediaType) => {
return {
contentType: { unique: allowedMediaType.mediaType.id },

View File

@@ -3,7 +3,7 @@ import './media-type-workspace-view-edit-property.element.js';
import type { UmbMediaTypeDetailModel } from '../../../types.js';
import { css, html, customElement, property, state, repeat, ifDefined } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { PropertyContainerTypes, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import type { UmbPropertyContainerTypes, UmbPropertyTypeModel } from '@umbraco-cms/backoffice/content-type';
import { UmbContentTypePropertyStructureHelper } from '@umbraco-cms/backoffice/content-type';
import type { UmbSorterConfig } from '@umbraco-cms/backoffice/sorter';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
@@ -80,10 +80,10 @@ export class UmbMediaTypeWorkspaceViewEditPropertiesElement extends UmbLitElemen
}
@property({ type: String, attribute: 'container-type', reflect: false })
public get containerType(): PropertyContainerTypes | undefined {
public get containerType(): UmbPropertyContainerTypes | undefined {
return this._propertyStructureHelper.getContainerType();
}
public set containerType(value: PropertyContainerTypes | undefined) {
public set containerType(value: UmbPropertyContainerTypes | undefined) {
this._propertyStructureHelper.setContainerType(value);
}
@@ -104,7 +104,7 @@ export class UmbMediaTypeWorkspaceViewEditPropertiesElement extends UmbLitElemen
constructor() {
super();
this.consumeContext(UMB_WORKSPACE_CONTEXT, (workspaceContext) => {
this.consumeContext(UMB_WORKSPACE_CONTEXT, async (workspaceContext) => {
this._propertyStructureHelper.setStructureManager((workspaceContext as UmbMediaTypeWorkspaceContext).structure);
this.observe(
(workspaceContext as UmbMediaTypeWorkspaceContext).isSorting,
@@ -114,6 +114,16 @@ export class UmbMediaTypeWorkspaceViewEditPropertiesElement extends UmbLitElemen
},
'_observeIsSorting',
);
const mediaTypesObservable = await this._propertyStructureHelper.ownerDocumentTypes();
if (!mediaTypesObservable) return;
this.observe(
mediaTypesObservable,
(medias) => {
this._ownerMediaTypes = medias;
},
'observeOwnerMediaTypes',
);
});
this.observe(this._propertyStructureHelper.propertyStructure, (propertyStructure) => {
this._propertyStructure = propertyStructure;
@@ -132,7 +142,10 @@ export class UmbMediaTypeWorkspaceViewEditPropertiesElement extends UmbLitElemen
return { data: { documentTypeId: mediaTypeId }, value: propertyData }; //TODO: Should we have a separate modal for mediaTypes?
})
.onSubmit((value) => {
this.#addProperty(value);
if (!value.dataType) {
throw new Error('No data type selected');
}
this.#addProperty(value as UmbPropertyTypeModel);
})
.observeRouteBuilder((routeBuilder) => {
this._modalRouteNewProperty = routeBuilder(null);
@@ -147,19 +160,6 @@ export class UmbMediaTypeWorkspaceViewEditPropertiesElement extends UmbLitElemen
}
}
connectedCallback(): void {
super.connectedCallback();
const mediaTypes = this._propertyStructureHelper.ownerDocumentTypes; //TODO: Should we have a separate propertyStructureHelper for mediaTypes?
if (!mediaTypes) return;
this.observe(
mediaTypes,
(medias) => {
this._ownerMediaTypes = medias;
},
'observeOwnerMediaTypes',
);
}
async #addProperty(propertyData: UmbPropertyTypeModel) {
const propertyPlaceholder = await this._propertyStructureHelper.addProperty(this._containerId);
if (!propertyPlaceholder) return;

View File

@@ -96,7 +96,10 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement {
return { data: { documentTypeId: mediaTypeId }, value: propertyData }; //TODO: Should we have a separate modal for mediaTypes?
})
.onSubmit((result) => {
this._partialUpdate(result);
if (!result.dataType) {
throw new Error('No dataType found on property');
}
this._partialUpdate(result as UmbPropertyTypeModel);
})
.observeRouteBuilder((routeBuilder) => {
this._modalRoute = routeBuilder(null);
@@ -295,7 +298,7 @@ export class UmbMediaTypeWorkspacePropertyElement extends UmbLitElement {
renderPropertyTags() {
return this.property
? html`<div class="types">
${this.property.dataType.unique ? html`<uui-tag look="default">${this._dataTypeName}</uui-tag>` : nothing}
${this.property.dataType?.unique ? html`<uui-tag look="default">${this._dataTypeName}</uui-tag>` : nothing}
${this.property.variesByCulture
? html`<uui-tag look="default">
<uui-icon name="icon-shuffle"></uui-icon> ${this.localize.term('contentTypeEditor_cultureVariantLabel')}

View File

@@ -61,9 +61,6 @@ export class UmbTemplatingInsertMenuElement extends UmbLitElement {
break;
}
case CodeSnippetType.macro: {
throw new Error('Not implemented');
}
}
}
@@ -131,10 +128,6 @@ export class UmbTemplatingInsertMenuElement extends UmbLitElement {
title="Dictionary item"
@click=${this.#openInsertDictionaryItemModal}>
</uui-menu-item>
<!-- <li>
<uui-menu-item class="insert-menu-item" label="Macro" title="Macro"> </uui-menu-item>
</li> -->
</umb-dropdown>
</uui-button-group>
`;

View File

@@ -3,7 +3,8 @@ import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit';
import type {
UmbModalManagerContext,
UmbModalContext,
UmbDictionaryItemPickerModalValue} from '@umbraco-cms/backoffice/modal';
UmbDictionaryItemPickerModalValue,
} from '@umbraco-cms/backoffice/modal';
import {
UMB_MODAL_MANAGER_CONTEXT,
UMB_PARTIAL_VIEW_PICKER_MODAL,
@@ -18,7 +19,6 @@ export interface ChooseInsertTypeModalData {
export enum CodeSnippetType {
partialView = 'partialView',
dictionaryItem = 'dictionaryItem',
macro = 'macro',
}
export interface UmbChooseInsertTypeModalValue {
value: string | UmbDictionaryItemPickerModalValue;

View File

@@ -256,12 +256,15 @@ export default class UmbTemplateQueryBuilderModalElement extends UmbModalBaseEle
</uui-button>`
: ''}
</div>
<div class="row">
<div class="row query-results">
<span id="results-count">
${this._templateQuery?.resultCount ?? 0}
<umb-localize key="template_itemsReturned">items returned, in</umb-localize>
${this._templateQuery?.executionTime ?? 0} ms
</span>
${this._templateQuery?.sampleResults.map(
(sample) => html`<span><uui-icon name=${sample.icon}></uui-icon>${sample.name}</span>`,
) ?? ''}
</div>
<umb-code-block language="C#" copy>${this._templateQuery?.queryExpression ?? ''}</umb-code-block>
</uui-box>
@@ -318,6 +321,16 @@ export default class UmbTemplateQueryBuilderModalElement extends UmbModalBaseEle
#results-count {
font-weight: bold;
}
.query-results {
flex-direction: column;
align-items: flex-start;
gap: 0;
}
.query-results span {
display: flex;
align-items: center;
gap: var(--uui-size-1);
}
`,
];
}

View File

@@ -72,7 +72,6 @@
"@umbraco-cms/backoffice/server-file-system": ["src/packages/core/server-file-system"],
"@umbraco-cms/backoffice/id": ["src/packages/core/id"],
"@umbraco-cms/backoffice/localization": ["src/packages/core/localization"],
"@umbraco-cms/backoffice/macro": ["src/packages/core/macro"],
"@umbraco-cms/backoffice/menu": ["src/packages/core/menu"],
"@umbraco-cms/backoffice/modal": ["src/packages/core/modal"],
"@umbraco-cms/backoffice/notification": ["src/packages/core/notification"],

View File

@@ -72,7 +72,6 @@ export default {
'@umbraco-cms/backoffice/server-file-system': './src/packages/core/server-file-system/index.ts',
'@umbraco-cms/backoffice/id': './src/packages/core/id/index.ts',
'@umbraco-cms/backoffice/localization': './src/packages/core/localization/index.ts',
'@umbraco-cms/backoffice/macro': './src/packages/core/macro/index.ts',
'@umbraco-cms/backoffice/menu': './src/packages/core/menu/index.ts',
'@umbraco-cms/backoffice/modal': './src/packages/core/modal/index.ts',
'@umbraco-cms/backoffice/notification': './src/packages/core/notification/index.ts',