diff --git a/src/Umbraco.Web.UI.Client/.github/RELEASE_INSTRUCTION.md b/src/Umbraco.Web.UI.Client/.github/RELEASE_INSTRUCTION.md new file mode 100644 index 0000000000..cfd5e491e7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/.github/RELEASE_INSTRUCTION.md @@ -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 diff --git a/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/README.md b/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/README.md new file mode 100644 index 0000000000..bc2ed47bca --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/README.md @@ -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 diff --git a/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/custom-ufm-component.ts b/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/custom-ufm-component.ts new file mode 100644 index 0000000000..9f4486eb7a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/custom-ufm-component.ts @@ -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 ``; + } +} + +// 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`${this.text}`; + } +} + +export { UmbCustomUfmComponent as api }; +export { UmbCustomUfmComponentElement as element }; diff --git a/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/index.ts b/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/index.ts new file mode 100644 index 0000000000..2da0c936cc --- /dev/null +++ b/src/Umbraco.Web.UI.Client/examples/ufm-custom-component/index.ts @@ -0,0 +1,13 @@ +import type { ManifestUfmComponent } from '@umbraco-cms/backoffice/extension-registry'; + +export const manifests: Array = [ + { + type: 'ufmComponent', + alias: 'Umb.CustomUfmComponent', + name: 'Custom UFM Component', + api: () => import('./custom-ufm-component.js'), + meta: { + marker: '%', + }, + }, +]; diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index bc1d573f63..04b68f446c 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -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", diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index ae97ee3097..6c89efd83a 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -1,7 +1,7 @@ { "name": "@umbraco-cms/backoffice", "license": "MIT", - "version": "14.2.0", + "version": "14.3.0", "type": "module", "exports": { ".": null, diff --git a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/submit.action.ts b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/submit.action.ts index 9a1fc41596..20bd6d3e05 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/submit.action.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/core/workspace/components/workspace-action/common/submit/submit.action.ts @@ -37,3 +37,8 @@ export class UmbSubmitWorkspaceAction extends UmbWorkspaceActionBase + + + +`; + + 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` - - ${url.url} - - - `, + (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` + this.#openSvg(item.url)}> + ${item.url} + + + `; + } else { + return html` + + ${item.url} + + + `; + } + } + #renderGeneralSection() { return html`
diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.element.ts index 90ce063c4d..3c1983d5c9 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/components/input-member/input-member.element.ts @@ -102,6 +102,27 @@ export class UmbInputMemberElement extends UmbFormControlMixin 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 + label=${this.localize.term('general_choose')} + ?disabled=${this.readonly}> `; } #renderItem(item: UmbMemberItemModel) { if (!item.unique) return nothing; return html` - - ${this.#renderIcon(item)} + - ${this.#renderOpenButton(item)} - this.#onRemove(item)} label=${this.localize.term('general_remove')}> + ${this.#renderOpenButton(item)} ${this.#renderRemoveButton(item)} - + `; } - #renderIcon(item: UmbMemberItemModel) { - if (!item.memberType.icon) return; - return html``; - } - #renderOpenButton(item: UmbMemberItemModel) { if (!this.showOpenButton) return nothing; return html` @@ -213,6 +228,13 @@ export class UmbInputMemberElement extends UmbFormControlMixin this.#onRemove(item)} label=${this.localize.term('general_remove')}> + `; + } + static override styles = [ css` #btn-add { diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/manifests.ts index da67731ba6..1f8c7cfbcc 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/manifests.ts @@ -12,6 +12,7 @@ export const manifests: Array = [ propertyEditorSchemaAlias: 'Umbraco.MemberPicker', icon: 'icon-user', group: 'people', + supportsReadOnly: true, }, }, memberPickerSchemaManifest, diff --git a/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.element.ts b/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.element.ts index a1dc8bd3e6..0ca5e50965 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/members/member/property-editor/member-picker/property-editor-ui-member-picker.element.ts @@ -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``; + return html``; } } diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/components/input-multi-url/input-multi-url.element.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/components/input-multi-url/input-multi-url.element.ts index 1379f62ef0..13be644661 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/components/input-multi-url/input-multi-url.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/components/input-multi-url/input-multi-url.element.ts @@ -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 = []; + /** + * 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 })}> + .href=${this._modalRoute?.({ index: -1 })} + ?disabled=${this.readonly}> `; } @@ -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` + detail=${(link.url || '') + (link.queryString || '')} + ?readonly=${this.readonly}> - - this.#requestRemoveItem(index)} - label=${this.localize.term('general_remove')}> + ${this.#renderEditAction(href)} ${this.#renderRemoveAction(index)} `; } + #renderEditAction(href?: string) { + if (this.readonly || !href) return nothing; + return html` `; + } + + #renderRemoveAction(index: number) { + if (this.readonly) return nothing; + return html` this.#requestRemoveItem(index)} + label=${this.localize.term('general_remove')}>`; + } + static override styles = [ css` #btn-add { diff --git a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts index e9bc9db5d7..88060e1e19 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/multi-url-picker/link-picker-modal/link-picker-modal.element.ts @@ -181,11 +181,6 @@ export class UmbLinkPickerModalElement extends UmbModalBaseElement ${this.localize.term('defaultdialogs_linkToPage')}
- ('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}> `; diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.element.ts index eba6f09baf..14ee1cf435 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/components/input-checkbox-list/input-checkbox-list.element.ts @@ -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``; + return html``; } static override styles = [ diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/manifests.ts index d88679601a..768e35ff23 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/manifests.ts @@ -12,6 +12,7 @@ export const manifests: Array = [ propertyEditorSchemaAlias: 'Umbraco.CheckBoxList', icon: 'icon-bulleted-list', group: 'lists', + supportsReadOnly: true, settings: { properties: [ { diff --git a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.element.ts b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.element.ts index 0eb81cc05c..45d99914cb 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.element.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/property-editors/checkbox-list/property-editor-ui-checkbox-list.element.ts @@ -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 `; } diff --git a/src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts b/src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts index 6bf7af213f..6ef5cd49bd 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/ufm/index.ts @@ -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';