Merge branch 'main' into v14/feature/readonly-member-group-picker-property-editor

This commit is contained in:
Mads Rasmussen
2024-08-28 14:25:53 +02:00
committed by GitHub
19 changed files with 254 additions and 38 deletions

View File

@@ -0,0 +1,31 @@
# Bellissima release instructions
## Build
> _See internal documentation on the build/release workflow._
## GitHub Release Notes
To generate release notes on GitHub.
- Go to the [**Releases** area](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases)
- Press the [**"Draft a new release"** button](https://github.com/umbraco/Umbraco.CMS.Backoffice/releases/new)
- In the combobox for "Choose a tag", expand then select or enter the next version number, e.g. `v14.2.0`
- If the tag does not already exist, an option labelled "Create new tag: v14.2.0 on publish" will appear, select that option
- In the combobox for "Target: main", expand then select the release branch for the next version, e.g. `release/14.2`
- In the combobox for "Previous tag: auto":
- If the next release is an RC, then you can leave as `auto`
- Otherwise, select the previous stable version, e.g. `v14.1.1`
- Press the **"Generate release notes"** button, this will populate the main textarea
- Check the details, view in the "Preview" tab
- What type of release is this?
- If it's an RC, then check "Set as a pre-release"
- If it's stable, then check "Set as the latest release"
- Once you're happy with the contents and ready to save...
- If you need more time to review, press the **"Save draft"** button and you can come back to it later
- If you are ready to make the release notes public, then press **"Publish release"** button! :tada:
> If you're curious about how the content is generated, take a look at the `release.yml` configuration:
> https://github.com/umbraco/Umbraco.CMS.Backoffice/blob/main/.github/release.yml

View File

@@ -0,0 +1,5 @@
# UFM Custom Component
This example demonstrates how to write a custom Umbraco-Flavored Markdown (UFM) component.
https://docs.umbraco.com/umbraco-cms/reference/umbraco-flavored-markdown#custom-ufm-components

View File

@@ -0,0 +1,26 @@
import { customElement, html, property } from '@umbraco-cms/backoffice/external/lit';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
import { UmbUfmComponentBase } from '@umbraco-cms/backoffice/ufm';
import type { UfmToken } from '@umbraco-cms/backoffice/ufm';
export class UmbCustomUfmComponent extends UmbUfmComponentBase {
render(token: UfmToken) {
// You could do further regular expression/text processing,
// then output your custom HTML markup.
return `<ufm-custom-component text="${token.text}"></ufm-custom-component>`;
}
}
// eslint-disable-next-line local-rules/enforce-umb-prefix-on-element-name
@customElement('ufm-custom-component')
export class UmbCustomUfmComponentElement extends UmbLitElement {
@property()
text?: string;
override render() {
return html`<marquee>${this.text}</marquee>`;
}
}
export { UmbCustomUfmComponent as api };
export { UmbCustomUfmComponentElement as element };

View File

@@ -0,0 +1,13 @@
import type { ManifestUfmComponent } from '@umbraco-cms/backoffice/extension-registry';
export const manifests: Array<ManifestUfmComponent> = [
{
type: 'ufmComponent',
alias: 'Umb.CustomUfmComponent',
name: 'Custom UFM Component',
api: () => import('./custom-ufm-component.js'),
meta: {
marker: '%',
},
},
];

View File

@@ -1,12 +1,12 @@
{
"name": "@umbraco-cms/backoffice",
"version": "14.2.0",
"version": "14.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@umbraco-cms/backoffice",
"version": "14.2.0",
"version": "14.3.0",
"license": "MIT",
"workspaces": [
"./src/packages/block",

View File

@@ -1,7 +1,7 @@
{
"name": "@umbraco-cms/backoffice",
"license": "MIT",
"version": "14.2.0",
"version": "14.3.0",
"type": "module",
"exports": {
".": null,

View File

@@ -37,3 +37,8 @@ export class UmbSubmitWorkspaceAction extends UmbWorkspaceActionBase<UmbSubmitta
return await workspaceContext.requestSubmit();
}
}
/*
* @deprecated Use UmbSubmitWorkspaceAction instead
*/
export { UmbSubmitWorkspaceAction as UmbSaveWorkspaceAction };

View File

@@ -95,6 +95,20 @@ export class UmbMediaWorkspaceViewInfoElement extends UmbLitElement {
this._updateDate = Array.isArray(variants) ? variants[0].updateDate || 'Unknown' : 'Unknown';
});
}
#openSvg(imagePath: string) {
const popup = window.open('', '_blank');
if (!popup) return;
const html = `<!doctype html>
<body style="background-image: linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(135deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(135deg, transparent 75%, #ccc 75%); background-size:30px 30px; background-position:0 0, 15px 0, 15px -15px, 0px 15px;">
<img src="${imagePath}"/>
<script>history.pushState(null, null, "${window.location.href}");</script>
</body>`;
popup.document.open();
popup.document.write(html);
popup.document.close();
}
override render() {
return html`
@@ -118,18 +132,12 @@ export class UmbMediaWorkspaceViewInfoElement extends UmbLitElement {
}
#renderLinksSection() {
/** TODO Make sure link section is completed */
if (this._urls && this._urls.length) {
return html`
${repeat(
this._urls,
(url) => url.url,
(url) => html`
<a href=${url.url} target="_blank" class="link-item with-href">
<span class="link-content">${url.url}</span>
<uui-icon name="icon-out"></uui-icon>
</a>
`,
(item) => item.url,
(item) => this.#renderLinkItem(item),
)}
`;
} else {
@@ -141,6 +149,25 @@ export class UmbMediaWorkspaceViewInfoElement extends UmbLitElement {
}
}
#renderLinkItem(item: MediaUrlInfoModel) {
const ext = item.url.split(/[#?]/)[0].split('.').pop()?.trim();
if (ext === 'svg') {
return html`
<a href="#" target="_blank" class="link-item with-href" @click=${() => this.#openSvg(item.url)}>
<span class="link-content">${item.url}</span>
<uui-icon name="icon-out"></uui-icon>
</a>
`;
} else {
return html`
<a href=${item.url} target="_blank" class="link-item with-href">
<span class="link-content">${item.url}</span>
<uui-icon name="icon-out"></uui-icon>
</a>
`;
}
}
#renderGeneralSection() {
return html`
<div class="general-item">

View File

@@ -102,6 +102,27 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
@property({ type: Object, attribute: false })
public filter: (member: UmbMemberItemModel) => boolean = () => true;
/**
* Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content.
* @type {boolean}
* @attr
* @default false
*/
@property({ type: Boolean, reflect: true })
public get readonly() {
return this.#readonly;
}
public set readonly(value) {
this.#readonly = value;
if (this.#readonly) {
this.#sorter.disable();
} else {
this.#sorter.enable();
}
}
#readonly = false;
@state()
private _editMemberPath = '';
@@ -180,28 +201,22 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
id="btn-add"
look="placeholder"
@click=${this.#openPicker}
label=${this.localize.term('general_choose')}></uui-button>
label=${this.localize.term('general_choose')}
?disabled=${this.readonly}></uui-button>
`;
}
#renderItem(item: UmbMemberItemModel) {
if (!item.unique) return nothing;
return html`
<uui-ref-node name=${item.name} id=${item.unique}>
${this.#renderIcon(item)}
<uui-ref-node-member name=${item.name} id=${item.unique} ?readonly=${this.readonly}>
<uui-action-bar slot="actions">
${this.#renderOpenButton(item)}
<uui-button @click=${() => this.#onRemove(item)} label=${this.localize.term('general_remove')}></uui-button>
${this.#renderOpenButton(item)} ${this.#renderRemoveButton(item)}
</uui-action-bar>
</uui-ref-node>
</uui-ref-node-member>
`;
}
#renderIcon(item: UmbMemberItemModel) {
if (!item.memberType.icon) return;
return html`<umb-icon slot="icon" name=${item.memberType.icon}></umb-icon>`;
}
#renderOpenButton(item: UmbMemberItemModel) {
if (!this.showOpenButton) return nothing;
return html`
@@ -213,6 +228,13 @@ export class UmbInputMemberElement extends UmbFormControlMixin<string | undefine
`;
}
#renderRemoveButton(item: UmbMemberItemModel) {
if (this.readonly) return nothing;
return html`
<uui-button @click=${() => this.#onRemove(item)} label=${this.localize.term('general_remove')}></uui-button>
`;
}
static override styles = [
css`
#btn-add {

View File

@@ -12,6 +12,7 @@ export const manifests: Array<ManifestTypes> = [
propertyEditorSchemaAlias: 'Umbraco.MemberPicker',
icon: 'icon-user',
group: 'people',
supportsReadOnly: true,
},
},
memberPickerSchemaManifest,

View File

@@ -16,13 +16,27 @@ export class UmbPropertyEditorUIMemberPickerElement extends UmbLitElement implem
@property({ attribute: false })
public config?: UmbPropertyEditorConfigCollection;
/**
* Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content.
* @type {boolean}
* @attr
* @default false
*/
@property({ type: Boolean, reflect: true })
readonly = false;
#onChange(event: CustomEvent & { target: UmbInputMemberElement }) {
this.value = event.target.value;
this.dispatchEvent(new UmbPropertyValueChangeEvent());
}
override render() {
return html`<umb-input-member min="0" max="1" .value=${this.value} @change=${this.#onChange}></umb-input-member>`;
return html`<umb-input-member
min="0"
max="1"
.value=${this.value}
@change=${this.#onChange}
?readonly=${this.readonly}></umb-input-member>`;
}
}

View File

@@ -1,6 +1,15 @@
import type { UmbLinkPickerLink } from '../../link-picker-modal/types.js';
import { UMB_LINK_PICKER_MODAL } from '../../link-picker-modal/link-picker-modal.token.js';
import { css, customElement, html, property, repeat, state } from '@umbraco-cms/backoffice/external/lit';
import {
css,
customElement,
html,
ifDefined,
nothing,
property,
repeat,
state,
} from '@umbraco-cms/backoffice/external/lit';
import { simpleHashCode } from '@umbraco-cms/backoffice/observable-api';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
import { UmbLitElement } from '@umbraco-cms/backoffice/lit-element';
@@ -124,6 +133,27 @@ export class UmbInputMultiUrlElement extends UUIFormControlMixin(UmbLitElement,
#urls: Array<UmbLinkPickerLink> = [];
/**
* Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content.
* @type {boolean}
* @attr
* @default false
*/
@property({ type: Boolean, reflect: true })
public get readonly() {
return this.#readonly;
}
public set readonly(value) {
this.#readonly = value;
if (this.#readonly) {
this.#sorter.disable();
} else {
this.#sorter.enable();
}
}
#readonly = false;
@state()
private _modalRoute?: UmbModalRouteBuilder;
@@ -248,7 +278,8 @@ export class UmbInputMultiUrlElement extends UUIFormControlMixin(UmbLitElement,
id="btn-add"
look="placeholder"
label=${this.localize.term('general_add')}
.href=${this._modalRoute?.({ index: -1 })}></uui-button>
.href=${this._modalRoute?.({ index: -1 })}
?disabled=${this.readonly}></uui-button>
`;
}
@@ -267,24 +298,34 @@ export class UmbInputMultiUrlElement extends UUIFormControlMixin(UmbLitElement,
#renderItem(link: UmbLinkPickerLink, index: number) {
const unique = this.#getUnique(link);
const href = this._modalRoute?.({ index }) ?? '#';
const href = this.readonly ? undefined : (this._modalRoute?.({ index }) ?? undefined);
return html`
<uui-ref-node
id=${unique}
href=${href}
href=${ifDefined(href)}
name=${link.name || ''}
detail=${(link.url || '') + (link.queryString || '')}>
detail=${(link.url || '') + (link.queryString || '')}
?readonly=${this.readonly}>
<umb-icon slot="icon" name=${link.icon || 'icon-link'}></umb-icon>
<uui-action-bar slot="actions">
<uui-button href=${href} label=${this.localize.term('general_edit')}></uui-button>
<uui-button
@click=${() => this.#requestRemoveItem(index)}
label=${this.localize.term('general_remove')}></uui-button>
${this.#renderEditAction(href)} ${this.#renderRemoveAction(index)}
</uui-action-bar>
</uui-ref-node>
`;
}
#renderEditAction(href?: string) {
if (this.readonly || !href) return nothing;
return html` <uui-button href=${href} label=${this.localize.term('general_edit')}></uui-button> `;
}
#renderRemoveAction(index: number) {
if (this.readonly) return nothing;
return html` <uui-button
@click=${() => this.#requestRemoveItem(index)}
label=${this.localize.term('general_remove')}></uui-button>`;
}
static override styles = [
css`
#btn-add {

View File

@@ -181,11 +181,6 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement<UmbLinkPicker
.open=${!this.documentExpand}></uui-symbol-expand>
<uui-label for="document-expand">${this.localize.term('defaultdialogs_linkToPage')}</uui-label>
<div style="${styleMap({ display: !this.documentExpand ? 'block' : 'none' })}">
<uui-input
disabled
id="search-input"
placeholder=${this.localize.term('placeholders_search')}
label=${this.localize.term('placeholders_search')}></uui-input>
<umb-tree
alias=${UMB_DOCUMENT_TREE_ALIAS}
.props=${{

View File

@@ -11,6 +11,7 @@ export const manifests = [
propertyEditorSchemaAlias: 'Umbraco.MultiUrlPicker',
icon: 'icon-link',
group: 'pickers',
supportsReadOnly: true,
settings: {
properties: [
{

View File

@@ -27,6 +27,15 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement impl
this._overlaySize = config.getValueByAlias<UUIModalSidebarSize>('overlaySize') ?? 'small';
}
/**
* Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content.
* @type {boolean}
* @attr
* @default false
*/
@property({ type: Boolean, reflect: true })
readonly = false;
#parseInt(value: unknown, fallback: number): number {
const num = Number(value);
return !isNaN(num) && num > 0 ? num : fallback;
@@ -74,6 +83,7 @@ export class UmbPropertyEditorUIMultiUrlPickerElement extends UmbLitElement impl
.urls=${this.value ?? []}
.variantId=${this._variantId}
?hide-anchor=${this._hideAnchor}
?readonly=${this.readonly}
@change=${this.#onChange}>
</umb-input-multi-url>
`;

View File

@@ -29,6 +29,15 @@ export class UmbInputCheckboxListElement extends UUIFormControlMixin(UmbLitEleme
return this.selection.join(',');
}
/**
* Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content.
* @type {boolean}
* @attr
* @default false
*/
@property({ type: Boolean, reflect: true })
readonly = false;
protected override getFormElement() {
return undefined;
}
@@ -64,7 +73,11 @@ export class UmbInputCheckboxListElement extends UUIFormControlMixin(UmbLitEleme
}
#renderCheckbox(item: (typeof this.list)[0]) {
return html`<uui-checkbox ?checked=${item.checked} label=${item.label} value=${item.value}></uui-checkbox>`;
return html`<uui-checkbox
?checked=${item.checked}
label=${item.label}
value=${item.value}
?readonly=${this.readonly}></uui-checkbox>`;
}
static override styles = [

View File

@@ -12,6 +12,7 @@ export const manifests: Array<ManifestTypes> = [
propertyEditorSchemaAlias: 'Umbraco.CheckBoxList',
icon: 'icon-bulleted-list',
group: 'lists',
supportsReadOnly: true,
settings: {
properties: [
{

View File

@@ -39,6 +39,15 @@ export class UmbPropertyEditorUICheckboxListElement extends UmbLitElement implem
}
}
/**
* Sets the input to readonly mode, meaning value cannot be changed but still able to read and select its content.
* @type {boolean}
* @attr
* @default false
*/
@property({ type: Boolean, reflect: true })
readonly = false;
@state()
private _list: UmbInputCheckboxListElement['list'] = [];
@@ -52,6 +61,7 @@ export class UmbPropertyEditorUICheckboxListElement extends UmbLitElement implem
<umb-input-checkbox-list
.list=${this._list}
.selection=${this.#selection}
?readonly=${this.readonly}
@change=${this.#onChange}></umb-input-checkbox-list>
`;
}

View File

@@ -1,2 +1,3 @@
export * from './components/ufm-render/index.js';
export * from './plugins/marked-ufm.plugin.js';
export * from './ufm-components/ufm-component-base.js';