Merge pull request #1234 from umbraco/feature/templating-page-field-modal

Feature: Page Field Modal + Section Picker Modal
This commit is contained in:
Jacob Overgaard
2024-02-20 08:40:35 +01:00
committed by GitHub
15 changed files with 390 additions and 340 deletions

View File

@@ -14,6 +14,7 @@ export const UMB_DICTIONARY_ITEM_PICKER_MODAL = new UmbModalToken<
size: 'small',
},
data: {
hideTreeRoot: true,
treeAlias: 'Umb.Tree.Dictionary',
},
});

View File

@@ -1,7 +1,5 @@
import {
UMB_PARTIAL_VIEW_PICKER_MODAL,
type UmbPartialViewPickerModalValue,
} from '../../modals/partial-view-picker/partial-view-picker-modal.token.js';
import { UMB_PARTIAL_VIEW_PICKER_MODAL } from '../../modals/partial-view-picker/partial-view-picker-modal.token.js';
import { UMB_TEMPLATING_PAGE_FIELD_BUILDER_MODAL } from '../../modals/templating-page-field-builder/templating-page-field-builder-modal.token.js';
import { CodeSnippetType } from '../../types.js';
import {
UMB_TEMPLATING_ITEM_PICKER_MODAL,
@@ -11,11 +9,7 @@ import { getInsertDictionarySnippet, getInsertPartialSnippet } from '../../utils
import { UmbDictionaryDetailRepository } from '@umbraco-cms/backoffice/dictionary';
import { customElement, property, css, html } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type {
UmbDictionaryItemPickerModalValue,
UmbModalManagerContext,
UmbModalContext,
} from '@umbraco-cms/backoffice/modal';
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import { UMB_DICTIONARY_ITEM_PICKER_MODAL, UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -26,8 +20,6 @@ export class UmbTemplatingInsertMenuElement extends UmbLitElement {
private _modalContext?: UmbModalManagerContext;
#openModal?: UmbModalContext;
#dictionaryDetailRepository = new UmbDictionaryDetailRepository(this);
constructor() {
@@ -42,78 +34,82 @@ export class UmbTemplatingInsertMenuElement extends UmbLitElement {
switch (type) {
case CodeSnippetType.partialView: {
this.#getPartialViewSnippet(value as UmbPartialViewPickerModalValue);
this.value = getInsertPartialSnippet(value);
this.#dispatchInsertEvent();
break;
}
case CodeSnippetType.dictionaryItem: {
await this.#getDictionaryItemSnippet(value as UmbDictionaryItemPickerModalValue);
await this.#getDictionaryItemSnippet(value);
this.#dispatchInsertEvent();
break;
}
case CodeSnippetType.pageField: {
this.value = value;
this.#dispatchInsertEvent();
break;
}
}
}
#getDictionaryItemSnippet = async (modalValue: UmbDictionaryItemPickerModalValue) => {
const unique = modalValue.selection[0];
async #getDictionaryItemSnippet(unique: string) {
if (unique === null) return;
const { data } = await this.#dictionaryDetailRepository.requestByUnique(unique);
this.value = getInsertDictionarySnippet(data?.name ?? '');
};
#getPartialViewSnippet = async (modalValue: UmbPartialViewPickerModalValue) => {
this.value = getInsertPartialSnippet(modalValue.selection?.[0] ?? '');
};
#openChooseTypeModal = () => {
this.#openModal = this._modalContext?.open(UMB_TEMPLATING_ITEM_PICKER_MODAL, {
data: {
hidePartialViews: this.hidePartialView,
},
});
this.#openModal?.onSubmit().then((closedModal: UmbTemplatingItemPickerModalValue) => {
this.determineInsertValue(closedModal);
});
};
#openInsertPageFieldSidebar() {
//this.#openModel = this._modalContext?.open();
}
#openInsertPartialViewSidebar() {
this.#openModal = this._modalContext?.open(UMB_PARTIAL_VIEW_PICKER_MODAL);
this.#openModal?.onSubmit().then((value) => {
this.#getPartialViewSnippet(value).then(() => {
this.#dispatchInsertEvent();
});
});
async #openTemplatingItemPickerModal() {
const itemPickerContext = this._modalContext?.open(UMB_TEMPLATING_ITEM_PICKER_MODAL);
await itemPickerContext?.onSubmit();
const value = itemPickerContext?.getValue();
if (!value) return;
this.determineInsertValue(value);
}
#openInsertDictionaryItemModal() {
this.#openModal = this._modalContext?.open(UMB_DICTIONARY_ITEM_PICKER_MODAL, {
data: {
pickableFilter: (item) => item.unique !== null,
hideTreeRoot: true,
},
});
this.#openModal?.onSubmit().then((value) => {
this.#getDictionaryItemSnippet(value).then(() => {
this.#dispatchInsertEvent();
});
});
async #openPartialViewPickerModal() {
const partialViewPickerContext = this._modalContext?.open(UMB_PARTIAL_VIEW_PICKER_MODAL);
await partialViewPickerContext?.onSubmit();
const path = partialViewPickerContext?.getValue().selection[0];
if (!path) return;
this.determineInsertValue({ type: CodeSnippetType.partialView, value: path });
}
async #openDictionaryItemPickerModal() {
const dictionaryItemPickerContext = this._modalContext?.open(UMB_DICTIONARY_ITEM_PICKER_MODAL);
await dictionaryItemPickerContext?.onSubmit();
const item = dictionaryItemPickerContext?.getValue().selection[0];
if (!item) return;
this.determineInsertValue({ type: CodeSnippetType.dictionaryItem, value: item });
}
async #openPageFieldBuilderModal() {
const pageFieldBuilderContext = this._modalContext?.open(UMB_TEMPLATING_PAGE_FIELD_BUILDER_MODAL);
await pageFieldBuilderContext?.onSubmit();
const output = pageFieldBuilderContext?.getValue().output;
if (!output) return;
// The output is already built due to the preview in the modal. Can insert it directly now.
this.value = output;
this.#dispatchInsertEvent();
}
#dispatchInsertEvent() {
this.dispatchEvent(new CustomEvent('insert', { bubbles: false, cancelable: true, composed: false }));
}
@property({ type: Boolean })
hidePartialView = false;
render() {
return html`
<uui-button-group>
<uui-button look="secondary" @click=${this.#openChooseTypeModal} label=${this.localize.term('template_insert')}>
<uui-button
look="secondary"
@click=${this.#openTemplatingItemPickerModal}
label=${this.localize.term('template_insert')}>
<uui-icon name="icon-add"></uui-icon>${this.localize.term('template_insert')}
</uui-button>
<umb-dropdown
@@ -126,18 +122,18 @@ export class UmbTemplatingInsertMenuElement extends UmbLitElement {
class="insert-menu-item"
label=${this.localize.term('template_insertPageField')}
title=${this.localize.term('template_insertPageField')}
@click=${this.#openInsertPageFieldSidebar}></uui-menu-item>
@click=${this.#openPageFieldBuilderModal}></uui-menu-item>
<uui-menu-item
class="insert-menu-item"
label=${this.localize.term('template_insertPartialView')}
title=${this.localize.term('template_insertPartialView')}
@click=${this.#openInsertPartialViewSidebar}>
@click=${this.#openPartialViewPickerModal}>
</uui-menu-item>
<uui-menu-item
class="insert-menu-item"
label=${this.localize.term('template_insertDictionaryItem')}
title=${this.localize.term('template_insertDictionaryItem')}
@click=${this.#openInsertDictionaryItemModal}>
@click=${this.#openDictionaryItemPickerModal}>
</uui-menu-item>
</umb-dropdown>
</uui-button-group>

View File

@@ -1,3 +1,4 @@
export * from './templating-section-picker/index.js';
export * from './templating-item-picker/index.js';
export * from './partial-view-picker/index.js';
export * from './templating-page-field-builder/index.js';

View File

@@ -13,6 +13,12 @@ const modals: Array<ManifestModal> = [
name: 'Templating Section Picker Modal',
js: () => import('./templating-section-picker/templating-section-picker-modal.element.js'),
},
{
type: 'modal',
alias: 'Umb.Modal.TemplatingPageFieldBuilder',
name: 'Templating Page Field Builder Modal',
js: () => import('./templating-page-field-builder/templating-page-field-builder-modal.element.js'),
},
];
export const manifests = [...modals];

View File

@@ -1,12 +1,13 @@
import { CodeSnippetType } from '../../types.js';
import { UMB_PARTIAL_VIEW_PICKER_MODAL } from '../partial-view-picker/partial-view-picker-modal.token.js';
import { UMB_TEMPLATING_PAGE_FIELD_BUILDER_MODAL } from '../templating-page-field-builder/templating-page-field-builder-modal.token.js';
import type {
UmbTemplatingItemPickerModalData,
UmbTemplatingItemPickerModalValue,
} from './templating-item-picker-modal.token.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement } from '@umbraco-cms/backoffice/external/lit';
import type { UmbModalManagerContext, UmbModalContext } from '@umbraco-cms/backoffice/modal';
import type { UmbModalManagerContext } from '@umbraco-cms/backoffice/modal';
import {
UMB_MODAL_MANAGER_CONTEXT,
UMB_DICTIONARY_ITEM_PICKER_MODAL,
@@ -22,43 +23,58 @@ export class UmbTemplatingItemPickerModalElement extends UmbModalBaseElement<
this.modalContext?.reject();
}
private _modalContext?: UmbModalManagerContext;
private _itemModalContext?: UmbModalManagerContext;
constructor() {
super();
this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => {
this._modalContext = instance;
this._itemModalContext = instance;
});
}
#openModal?: UmbModalContext;
async #openTemplatingPageFieldModal() {
const pageFieldBuilderContext = this._itemModalContext?.open(UMB_TEMPLATING_PAGE_FIELD_BUILDER_MODAL);
await pageFieldBuilderContext?.onSubmit();
#openInsertPartialViewSidebar() {
this.#openModal = this._modalContext?.open(UMB_PARTIAL_VIEW_PICKER_MODAL);
this.#openModal?.onSubmit().then((partialViewPickerModalValue) => {
if (partialViewPickerModalValue) {
this.value = {
type: CodeSnippetType.partialView,
value: partialViewPickerModalValue.selection[0],
};
this.modalContext?.submit();
}
});
const output = pageFieldBuilderContext?.getValue().output;
if (output) {
this.value = { value: output, type: CodeSnippetType.pageField };
this.modalContext?.submit();
}
}
#openInsertDictionaryItemModal() {
this.#openModal = this._modalContext?.open(UMB_DICTIONARY_ITEM_PICKER_MODAL, {
async #openPartialViewPickerModal() {
const partialViewPickerContext = this._itemModalContext?.open(UMB_PARTIAL_VIEW_PICKER_MODAL);
await partialViewPickerContext?.onSubmit();
const path = partialViewPickerContext?.getValue().selection[0];
if (path) {
const regex = /^%2F|%25dot%25cshtml$/g;
const prettyPath = path.replace(regex, '').replace(/%2F/g, '/');
this.value = {
value: prettyPath,
type: CodeSnippetType.partialView,
};
this.modalContext?.submit();
}
}
async #openDictionaryItemPickerModal() {
const dictionaryItemPickerModal = this._itemModalContext?.open(UMB_DICTIONARY_ITEM_PICKER_MODAL, {
data: {
hideTreeRoot: true,
pickableFilter: (item) => item.unique !== null,
},
});
this.#openModal?.onSubmit().then((dictionaryItemPickerModalValue) => {
if (dictionaryItemPickerModalValue) {
this.value = { value: dictionaryItemPickerModalValue, type: CodeSnippetType.dictionaryItem };
this.modalContext?.submit();
}
});
await dictionaryItemPickerModal?.onSubmit();
const dictionaryItem = dictionaryItemPickerModal?.getValue().selection[0];
if (dictionaryItem) {
this.value = { value: dictionaryItem, type: CodeSnippetType.dictionaryItem };
this.modalContext?.submit();
}
}
render() {
@@ -77,10 +93,10 @@ export class UmbTemplatingItemPickerModalElement extends UmbModalBaseElement<
#renderItems() {
return html`<div id="main">
<uui-button
@click=${() => console.log('to be continued')}
@click=${this.#openTemplatingPageFieldModal}
look="placeholder"
label=${this.localize.term('template_insert')}>
<h3><umb-localize key="template_insertPageField">Value</umb-localize> (Not implemented)</h3>
<h3><umb-localize key="template_insertPageField">Value</umb-localize></h3>
<p>
<umb-localize key="template_insertPageFieldDesc">
Displays the value of a named field from the current page, with options to modify the value or fallback to
@@ -89,7 +105,7 @@ export class UmbTemplatingItemPickerModalElement extends UmbModalBaseElement<
</p>
</uui-button>
<uui-button
@click=${this.#openInsertPartialViewSidebar}
@click=${this.#openPartialViewPickerModal}
look="placeholder"
label=${this.localize.term('template_insert')}>
<h3><umb-localize key="template_insertPartialView">Partial view</umb-localize></h3>
@@ -101,7 +117,7 @@ export class UmbTemplatingItemPickerModalElement extends UmbModalBaseElement<
</p>
</uui-button>
<uui-button
@click=${this.#openInsertDictionaryItemModal}
@click=${this.#openDictionaryItemPickerModal}
look="placeholder"
label=${this.localize.term('template_insertDictionaryItem')}>
<h3><umb-localize key="template_insertDictionaryItem">Dictionary Item</umb-localize></h3>

View File

@@ -1,4 +1,5 @@
import type { CodeSnippetType } from '../../types.js';
import type { UmbPartialViewPickerModalValue, UmbTemplatingPageFieldBuilderModalValue } from '../index.js';
import { type UmbDictionaryItemPickerModalValue, UmbModalToken } from '@umbraco-cms/backoffice/modal';
export interface UmbTemplatingItemPickerModalData {
@@ -6,7 +7,7 @@ export interface UmbTemplatingItemPickerModalData {
}
export type UmbTemplatingItemPickerModalValue = {
value: string | UmbDictionaryItemPickerModalValue;
value: string;
type: CodeSnippetType;
};

View File

@@ -0,0 +1,2 @@
export * from './templating-page-field-builder-modal.element.js';
export * from './templating-page-field-builder-modal.token.js';

View File

@@ -0,0 +1,109 @@
import { getUmbracoFieldSnippet } from '../../utils/index.js';
import type {
UmbTemplatingPageFieldBuilderModalData,
UmbTemplatingPageFieldBuilderModalValue,
} from './templating-page-field-builder-modal.token.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
@customElement('umb-templating-page-field-builder-modal')
export class UmbTemplatingPageFieldBuilderModalElement extends UmbModalBaseElement<
UmbTemplatingPageFieldBuilderModalData,
UmbTemplatingPageFieldBuilderModalValue
> {
private _close() {
this.modalContext?.reject();
}
private _submit() {
if (!this._field) return;
this.value = { output: getUmbracoFieldSnippet(this._field, this._default, this._recursive) };
this.modalContext?.submit();
}
@state()
private _field?: string;
@state()
private _haveDefault: boolean = false;
@state()
private _default?: string;
@state()
private _recursive: boolean = false;
/** TODO: Implement "Choose field" */
render() {
return html`
<umb-body-layout headline=${this.localize.term('template_insert')}>
<uui-box>
<div>
<uui-label for="page-field-value">
<umb-localize key="templateEditor_chooseField">Choose field</umb-localize>
</uui-label>
(Not implemented yet)
<uui-label for="page-field-default-value">
<umb-localize key="templateEditor_defaultValue">Default value</umb-localize>
</uui-label>
${!this._haveDefault
? html`<uui-button
label=${this.localize.term('templateEditor_addDefaultValue')}
look="placeholder"
@click=${() => (this._haveDefault = true)}></uui-button>`
: html`<uui-input
id="page-field-default-value"
label=${this.localize.term('templateEditor_defaultValue')}></uui-input>`}
<uui-label for="recursive"><umb-localize key="templateEditor_recursive">Recursive</umb-localize></uui-label>
<uui-checkbox
id="recursive"
label=${this.localize.term('templateEditor_recursiveDescr')}
?disabled=${this._field ? false : true}></uui-checkbox>
<uui-label><umb-localize key="templateEditor_outputSample">Output sample</umb-localize></uui-label>
<umb-code-block language="C#" copy
>${this._field ? getUmbracoFieldSnippet(this._field, this._default, this._recursive) : ''}</umb-code-block
>
</div>
</uui-box>
<uui-button
slot="actions"
@click=${this._close}
look="secondary"
label=${this.localize.term('general_close')}></uui-button>
<uui-button
slot="actions"
@click=${this._submit}
color="positive"
look="primary"
label=${this.localize.term('general_submit')}></uui-button>
</umb-body-layout>
`;
}
static styles = [
UmbTextStyles,
css`
uui-box > div {
display: grid;
gap: var(--uui-size-space-2);
}
uui-label:not(:first-child) {
margin-top: var(--uui-size-space-6);
}
`,
];
}
export default UmbTemplatingPageFieldBuilderModalElement;
declare global {
interface HTMLElementTagNameMap {
'umb-templating-page-field-builder-modal': UmbTemplatingPageFieldBuilderModalElement;
}
}

View File

@@ -0,0 +1,17 @@
import { UmbModalToken } from '@umbraco-cms/backoffice/modal';
export interface UmbTemplatingPageFieldBuilderModalData {}
export type UmbTemplatingPageFieldBuilderModalValue = {
output: string;
};
export const UMB_TEMPLATING_PAGE_FIELD_BUILDER_MODAL = new UmbModalToken<
UmbTemplatingPageFieldBuilderModalData,
UmbTemplatingPageFieldBuilderModalValue
>('Umb.Modal.TemplatingPageFieldBuilder', {
modal: {
type: 'sidebar',
size: 'small',
},
});

View File

@@ -1,2 +1,2 @@
export * from './templating-section-picker-input.element.js';
export * from './templating-section-picker-modal.element.js';
export * from './templating-section-picker-modal.token.js';

View File

@@ -1,150 +0,0 @@
import type { UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
import { UUIBooleanInputElement } from '@umbraco-cms/backoffice/external/uui';
import { css, html, customElement, property, query } from '@umbraco-cms/backoffice/external/lit';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
@customElement('umb-insert-section-checkbox')
export class UmbInsertSectionCheckboxElement extends UUIBooleanInputElement {
renderCheckbox() {
return html``;
}
@property({ type: Boolean, attribute: 'show-mandatory' })
showMandatory = false;
@property({ type: Boolean, attribute: 'show-input' })
showInput = false;
@query('uui-input')
input?: UUIInputElement;
@query('form')
form?: HTMLFormElement;
@query('uui-checkbox')
checkbox?: HTMLFormElement;
get snippet() {
if (!this.snippetMethod) return '';
const snippet = this.snippetMethod(this.inputValue as string, this.isMandatory) ?? '';
return snippet;
}
@property({ attribute: false })
snippetMethod?: (value: string, isMandatory: boolean) => string;
validate() {
if (!this.form) return true;
this.form.requestSubmit();
return this.form.checkValidity();
}
#preventDefault(event: Event) {
event.preventDefault();
}
get inputValue() {
return this.input?.value;
}
get isMandatory() {
return this.checkbox?.checked;
}
/* eslint-disable lit-a11y/click-events-have-key-events */
render() {
return html`
${super.render()}
<h3 class="uui-h3" @click=${this.click}>
${this.checked ? html`<uui-icon name="icon-check"></uui-icon>` : ''}${this.label}
</h3>
<div @click=${this.click}>
<slot name="description"><p>here goes some description</p></slot>
</div>
${this.checked && this.showInput
? html`<uui-form>
<form @submit=${this.#preventDefault}>
<uui-form-layout-item>
<uui-label slot="label" for="section-name-input" required>Section name</uui-label>
<uui-input
required
placeholder="Enter section name"
label="Section name"
id="section-name-input"></uui-input> </uui-form-layout-item
>${this.showMandatory
? html`<p slot="if-checked">
<uui-checkbox label="Section is mandatory">Section is mandatory </uui-checkbox><br />
<small
>If mandatory, the child template must contain a <code>@section</code> definition, otherwise an
error is shown.</small
>
</p>`
: ''}
</form>
</uui-form>`
: ''}
`;
}
/* eslint-enable lit-a11y/click-events-have-key-events */
static styles = [
...UUIBooleanInputElement.styles,
UmbTextStyles,
css`
:host {
display: block;
border-style: dashed;
background-color: transparent;
color: var(--uui-color-default-standalone, rgb(28, 35, 59));
border-color: var(--uui-color-border-standalone, #c2c2c2);
border-radius: var(--uui-border-radius, 3px);
border-width: 1px;
line-height: normal;
padding: 6px 18px;
}
:host(:hover),
:host(:focus),
:host(:focus-within) {
background-color: var(--uui-button-background-color-hover, transparent);
color: var(--uui-color-default-emphasis, #3544b1);
border-color: var(--uui-color-default-emphasis, #3544b1);
}
uui-icon {
background-color: var(--uui-color-positive-emphasis);
border-radius: 50%;
padding: 0.2em;
margin-right: 1ch;
color: var(--uui-color-positive-contrast);
font-size: 0.7em;
}
::slotted(*) {
line-height: normal;
}
.label {
display: none;
}
h3,
p {
text-align: left;
}
uui-input {
width: 100%;
}
`,
];
}
export default UmbInsertSectionCheckboxElement;
declare global {
interface HTMLElementTagNameMap {
'umb-insert-section-input': UmbInsertSectionCheckboxElement;
}
}

View File

@@ -1,143 +1,186 @@
import { getAddSectionSnippet, getRenderBodySnippet, getRenderSectionSnippet } from '../../utils/index.js';
import { TemplatingSectionType } from '../../types.js';
import type {
UmbTemplatingSectionPickerModalData,
UmbTemplatingSectionPickerModalValue,
} from './templating-section-picker-modal.token.js';
import type { UmbInsertSectionCheckboxElement } from './templating-section-picker-input.element.js';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import { css, html, customElement, queryAll, state } from '@umbraco-cms/backoffice/external/lit';
import { css, html, customElement, state, query } from '@umbraco-cms/backoffice/external/lit';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import './templating-section-picker-input.element.js';
import type { UUIBooleanInputElement, UUIInputElement } from '@umbraco-cms/backoffice/external/uui';
@customElement('umb-templating-section-picker-modal')
export class UmbTemplatingSectionPickerModalElement extends UmbModalBaseElement<
UmbTemplatingSectionPickerModalData,
UmbTemplatingSectionPickerModalValue
> {
@queryAll('umb-insert-section-checkbox')
checkboxes!: NodeListOf<UmbInsertSectionCheckboxElement>;
@query('#render-named-section-name')
private _renderNamedSectionNameInput?: UUIInputElement;
@query('#define-named-section-name')
private _defineNamedSectionNameInput?: UUIInputElement;
@query('#render-named-section-is-mandatory')
private _renderNamedSectionIsMandatoryCheckbox?: UUIBooleanInputElement;
@state()
selectedCheckbox?: UmbInsertSectionCheckboxElement | null = null;
@state()
snippet = '';
#chooseSection(event: Event) {
const target = event.target as UmbInsertSectionCheckboxElement;
const checkboxes = Array.from(this.checkboxes);
if (checkboxes.every((checkbox) => checkbox.checked === false)) {
this.selectedCheckbox = null;
return;
}
if (target.checked) {
this.selectedCheckbox = target;
this.snippet = this.selectedCheckbox.snippet ?? '';
checkboxes.forEach((checkbox) => {
if (checkbox !== target) {
checkbox.checked = false;
}
});
}
}
firstUpdated() {
this.selectedCheckbox = this.checkboxes[0];
}
snippetMethods = [getRenderBodySnippet, getRenderSectionSnippet, getAddSectionSnippet];
private _pickedSection: TemplatingSectionType = TemplatingSectionType.renderChildTemplate;
#close() {
this.modalContext?.reject();
}
#submit() {
const value = this.selectedCheckbox?.snippet;
if (this.selectedCheckbox?.validate()) {
this.value = { value: value ?? '' };
this.modalContext?.submit();
switch (this._pickedSection) {
case TemplatingSectionType.renderChildTemplate:
this.value = { value: getRenderBodySnippet() };
break;
case TemplatingSectionType.renderANamedSection:
this.value = {
value: getRenderSectionSnippet(
this._renderNamedSectionNameInput?.value as string,
this._renderNamedSectionIsMandatoryCheckbox?.checked ?? false,
),
};
break;
case TemplatingSectionType.defineANamedSection:
this.value = { value: getAddSectionSnippet(this._defineNamedSectionNameInput?.value as string) };
break;
}
this.modalContext?.submit();
}
render() {
return html`
<umb-body-layout headline=${this.localize.term('template_insert')}>
<div id="main">
<uui-box>
<umb-insert-section-checkbox
@change=${this.#chooseSection}
label=${this.localize.term('template_renderBody')}
checked
.snippetMethod=${getRenderBodySnippet}>
<p slot="description">
<umb-localize key="template_renderBodyDesc">
Renders the contents of a child template, by inserting a <code>@RenderBody()</code> placeholder.
</umb-localize>
</p>
</umb-insert-section-checkbox>
<uui-box>
<div id="main">
${this.#renderRenderChildTemplate()} ${this.#renderRenderANamedSection()}
${this.#renderDefineANamedSection()}
</div>
</uui-box>
<umb-insert-section-checkbox
@change=${this.#chooseSection}
label=${this.localize.term('template_renderSection')}
show-mandatory
show-input
.snippetMethod=${getRenderSectionSnippet}>
<p slot="description">
<umb-localize key="template_renderSectionDesc">
Renders a named area of a child template, by inserting a
<code>@RenderSection(name)</code> placeholder. This renders an area of a child template which is
wrapped in a corresponding <code>@section [name]{ ... }</code> definition.
</umb-localize>
</p>
</umb-insert-section-checkbox>
<umb-insert-section-checkbox
@change=${this.#chooseSection}
label=${this.localize.term('template_defineSection')}
show-input
.snippetMethod=${getAddSectionSnippet}>
<p slot="description">
<umb-localize key="template_defineSectionDesc">
Renders a named area of a child template, by inserting a
<code>@RenderSection(name)</code> placeholder. This renders an area of a child template which is
wrapped in a corresponding <code>@section [name]{ ... }</code> definition.
</umb-localize>
</p>
</umb-insert-section-checkbox>
</uui-box>
</div>
<div slot="actions">
<uui-button @click=${this.#close} look="secondary" label="Close">Close</uui-button>
<uui-button @click=${this.#submit} look="primary" color="positive" label="Submit">Submit</uui-button>
<uui-button @click=${this.#close} look="secondary" label=${this.localize.term('general_close')}></uui-button>
<uui-button
@click=${this.#submit}
look="primary"
color="positive"
label=${this.localize.term('general_submit')}></uui-button>
</div>
</umb-body-layout>
`;
}
#renderRenderChildTemplate() {
return html`<uui-button
label=${this.localize.term('template_renderBody')}
@click=${() => (this._pickedSection = TemplatingSectionType.renderChildTemplate)}
look="placeholder">
${this._pickedSection === TemplatingSectionType.renderChildTemplate
? html`<uui-badge color="positive"><uui-icon name="icon-check"></uui-icon></uui-badge>`
: ''}
<h3><umb-localize key="template_renderBody">Render Child Template</umb-localize></h3>
<p>
<umb-localize key="template_renderBodyDesc">
Renders the contents of a child template, by inserting a <code>@RenderBody()</code> placeholder.
</umb-localize>
</p>
</uui-button>`;
}
#renderRenderANamedSection() {
return html`<uui-button
label=${this.localize.term('template_renderSection')}
@click=${() => (this._pickedSection = TemplatingSectionType.renderANamedSection)}
look="placeholder">
${this._pickedSection === TemplatingSectionType.renderANamedSection
? html`<uui-badge color="positive"><uui-icon name="icon-check"></uui-icon></uui-badge>`
: ''}
<h3><umb-localize key="template_renderSection">Render a named section</umb-localize></h3>
<p>
<umb-localize key="template_renderSectionDesc">
Renders a named area of a child template, by inserting a
<code>@RenderSection(name)</code> placeholder. This renders an area of a child template which is wrapped in a
corresponding <code>@section [name]{ ... }</code> definition.
</umb-localize>
</p>
${this._pickedSection === TemplatingSectionType.renderANamedSection
? html`<div class="section">
<uui-label for="render-named-section-name" required>
<umb-localize key="template_sectionName">Section Name</umb-localize>
</uui-label>
<uui-input id="render-named-section-name" label=${this.localize.term('template_sectionName')}></uui-input>
<uui-checkbox
id="render-named-section-is-mandatory"
label=${this.localize.term('template_sectionMandatory')}></uui-checkbox>
<small>
<umb-localize key="template_sectionMandatoryDesc">
If mandatory, the child template must contain a <code>@section</code> definition, otherwise an error is
shown.
</umb-localize>
</small>
</div>`
: ''}
</uui-button>`;
}
#renderDefineANamedSection() {
return html`<uui-button
label=${this.localize.term('template_defineSection')}
@click=${() => (this._pickedSection = TemplatingSectionType.defineANamedSection)}
look="placeholder">
${this._pickedSection === TemplatingSectionType.defineANamedSection
? html`<uui-badge color="positive"><uui-icon name="icon-check"></uui-icon></uui-badge>`
: ''}
<h3><umb-localize key="template_defineSection">Define a named section</umb-localize></h3>
<p>
<umb-localize key="template_defineSectionDesc">
Defines a part of your template as a named section by wrapping it in <code>@section { ... }</code>. This can
be rendered in a specific area of the parent of this template, by using <code>@RenderSection</code>.
</umb-localize>
</p>
${this._pickedSection === TemplatingSectionType.defineANamedSection
? html`<div class="section">
<uui-label for="define-named-section-name" required>
<umb-localize key="template_sectionName">Section Name</umb-localize>
</uui-label>
<uui-input id="define-named-section-name" label=${this.localize.term('template_sectionName')}></uui-input>
</div>`
: ''}
</uui-button>`;
}
static styles = [
UmbTextStyles,
css`
:host {
display: block;
color: var(--uui-color-text);
--umb-header-layout-height: 70px;
}
#main {
box-sizing: border-box;
height: calc(
100dvh - var(--umb-header-layout-height) - var(--umb-footer-layout-height) - 2 * var(--uui-size-layout-1)
);
}
#main umb-insert-section-checkbox:not(:last-of-type) {
margin-bottom: var(--uui-size-space-5);
}
code {
background-color: var(--uui-color-surface-alt);
border: 1px solid var(--uui-color-border);
border-radius: var(--uui-border-radius);
}
#main {
display: grid;
grid-gap: var(--uui-size-space-5);
}
.section {
display: grid;
}
uui-button {
text-align: left;
}
uui-button p {
margin-top: 0;
}
uui-input,
small {
margin-block: var(--uui-size-space-2) var(--uui-size-space-6);
}
`,
];
}

View File

@@ -109,6 +109,7 @@ export class UmbTemplateWorkspaceEditorElement extends UmbLitElement {
#openInsertSectionModal() {
const sectionModal = this._modalContext?.open(UMB_TEMPLATING_SECTION_PICKER_MODAL);
sectionModal?.onSubmit().then((insertSectionModalValue) => {
if (insertSectionModalValue?.value) {
this._codeEditor?.insert(insertSectionModalValue.value);

View File

@@ -1,4 +1,11 @@
export enum CodeSnippetType {
partialView = 'partialView',
dictionaryItem = 'dictionaryItem',
pageField = 'pageField',
}
export enum TemplatingSectionType {
renderChildTemplate = 'RenderChildTemplate',
renderANamedSection = 'RenderANamedSection',
defineANamedSection = 'DefineANamedSection',
}

View File

@@ -45,7 +45,7 @@ export const getUmbracoFieldSnippet = (field: string, defaultValue: string | nul
const value = `${field !== null ? `@Model.Value("${field}"` : ''}${
fallback !== null ? `, fallback: ${fallback}` : ''
}${defaultValue !== null ? `, defaultValue: new HtmlString("${defaultValue}")` : ''}${field ? ')' : ''}`;
}${defaultValue !== null ? `, defaultValue: new HtmlString("${defaultValue}")` : ''}${field ? ')' : ')'}`;
return value;
};