diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames.action.ts deleted file mode 100644 index ba8c0c7be5..0000000000 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames.action.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { UmbDocumentDetailRepository } from '../repository/index.js'; -import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; -import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; - -export class UmbDocumentCultureAndHostnamesEntityAction extends UmbEntityActionBase { - constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { - super(host, repositoryAlias, unique, entityType); - } - - async execute() { - console.log(`execute for: ${this.unique}`); - //await this.repository?.setCultureAndHostnames(); - } -} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/culture-and-hostnames.action.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/culture-and-hostnames.action.ts new file mode 100644 index 0000000000..f22531fd36 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/culture-and-hostnames.action.ts @@ -0,0 +1,28 @@ +import { UmbEntityActionBase } from '@umbraco-cms/backoffice/entity-action'; +import type { UmbControllerHostElement } from '@umbraco-cms/backoffice/controller-api'; +import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; +import { UMB_CULTURE_AND_HOSTNAMES_MODAL, type UmbDocumentDetailRepository } from '@umbraco-cms/backoffice/document'; + +export class UmbDocumentCultureAndHostnamesEntityAction extends UmbEntityActionBase { + #modalContext?: typeof UMB_MODAL_MANAGER_CONTEXT.TYPE; + + constructor(host: UmbControllerHostElement, repositoryAlias: string, unique: string, entityType: string) { + super(host, repositoryAlias, unique, entityType); + + this.consumeContext(UMB_MODAL_MANAGER_CONTEXT, (instance) => { + this.#modalContext = instance; + }); + } + + async execute() { + if (!this.repository) return; + this._openModal(this.unique || null); + } + + private async _openModal(unique: string | null) { + if (!this.#modalContext) return; + this.#modalContext.open(UMB_CULTURE_AND_HOSTNAMES_MODAL, { + data: { unique }, + }); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/index.ts new file mode 100644 index 0000000000..e130217e61 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/index.ts @@ -0,0 +1,2 @@ +export * from './modal/index.js'; +export * from './repository/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/manifests.ts new file mode 100644 index 0000000000..ca988d19ad --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/manifests.ts @@ -0,0 +1,31 @@ +import { UMB_DOCUMENT_ENTITY_TYPE } from '../../entity.js'; +import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS } from '../../repository/index.js'; +import { UmbDocumentCultureAndHostnamesEntityAction } from './culture-and-hostnames.action.js'; +import type { ManifestEntityAction, ManifestModal } from '@umbraco-cms/backoffice/extension-registry'; + +const entityActions: Array = [ + { + type: 'entityAction', + alias: 'Umb.EntityAction.Document.CultureAndHostnames', + name: 'Culture And Hostnames Document Entity Action', + weight: 400, + api: UmbDocumentCultureAndHostnamesEntityAction, + meta: { + icon: 'icon-home', + label: 'Culture and Hostnames', + repositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, + entityTypes: [UMB_DOCUMENT_ENTITY_TYPE], + }, + }, +]; + +const manifestModals: Array = [ + { + type: 'modal', + alias: 'Umb.Modal.CultureAndHostnames', + name: 'Culture And Hostnames Modal', + js: () => import('./modal/culture-and-hostnames-modal.element.js'), + }, +]; + +export const manifests = [...entityActions, ...manifestModals]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/culture-and-hostnames-modal.element.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/culture-and-hostnames-modal.element.ts new file mode 100644 index 0000000000..343e6ff0a1 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/culture-and-hostnames-modal.element.ts @@ -0,0 +1,247 @@ +import { html, customElement, state, css, repeat, query } from '@umbraco-cms/backoffice/external/lit'; +import { UmbTextStyles } from '@umbraco-cms/backoffice/style'; +import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal'; +import { UmbLanguageRepository } from '@umbraco-cms/backoffice/language'; +import type { DomainPresentationModel, LanguageResponseModel } from '@umbraco-cms/backoffice/backend-api'; +import { + UmbDocumentCultureAndHostnamesRepository, + type UmbCultureAndHostnamesModalData, + type UmbCultureAndHostnamesModalValue, +} from '@umbraco-cms/backoffice/document'; +import type { UUIInputEvent, UUIPopoverContainerElement, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui'; + +@customElement('umb-culture-and-hostnames-modal') +export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement< + UmbCultureAndHostnamesModalData, + UmbCultureAndHostnamesModalValue +> { + #documentRepository = new UmbDocumentCultureAndHostnamesRepository(this); + #languageRepository = new UmbLanguageRepository(this); + + #unique?: string | null; + + @state() + private _languageModel: Array = []; + + @state() + private _defaultIsoCode?: string | null; + + @state() + private _domains: Array = []; + + @query('#more-options') + popoverContainerElement?: UUIPopoverContainerElement; + + // Init + + firstUpdated() { + this.#unique = this.data?.unique; + this.#requestLanguages(); + this.#readDomains(); + } + + async #readDomains() { + if (!this.#unique) return; + const { data } = await this.#documentRepository.readCultureAndHostnames(this.#unique); + + if (!data) return; + this._defaultIsoCode = data.defaultIsoCode; + this._domains = data.domains; + } + + async #requestLanguages() { + const { data } = await this.#languageRepository.requestLanguages(); + if (!data) return; + this._languageModel = data.items; + } + + // Modal + + async #handleSave() { + this.value = { defaultIsoCode: this._defaultIsoCode, domains: this._domains }; + await this.#documentRepository.updateCultureAndHostnames(this.#unique!, this.value); + } + + #handleClose() { + this.modalContext?.submit(); + } + + // Events + + #onChangeLanguage(e: UUISelectEvent) { + const defaultIsoCode = e.target.value as string; + if (defaultIsoCode === 'inherit') { + this._defaultIsoCode = null; + } else { + this._defaultIsoCode = defaultIsoCode; + } + } + + #onChangeDomainLanguage(e: UUISelectEvent, index: number) { + const isoCode = e.target.value as string; + this._domains = this._domains.map((domain, i) => (index === i ? { ...domain, isoCode } : domain)); + } + + #onChangeDomainHostname(e: UUIInputEvent, index: number) { + const domainName = e.target.value as string; + this._domains = this._domains.map((domain, i) => (index === i ? { ...domain, domainName } : domain)); + } + + async #onRemoveDomain(index: number) { + this._domains = this._domains.filter((d, i) => index !== i); + } + + #onAddDomain(useCurrentDomain?: boolean) { + const defaultModel = this._languageModel.find((model) => model.isDefault); + if (useCurrentDomain) { + // TODO: This ignorer is just needed for JSON SCHEMA TO WORK, As its not updated with latest TS jet. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.popoverContainerElement?.hidePopover(); + this._domains = [...this._domains, { isoCode: defaultModel?.isoCode ?? '', domainName: window.location.host }]; + } else { + this._domains = [...this._domains, { isoCode: defaultModel?.isoCode ?? '', domainName: '' }]; + } + } + + // Renders + + render() { + return html` + + ${this.#renderCultureSection()} ${this.#renderDomainSection()} + + + + `; + } + + #renderCultureSection() { + return html` + ${this.localize.term('assignDomain_language')} + + + + ${this.localize.term('assignDomain_inherit')} + + ${this.#renderLanguageModelOptions()} + + + `; + } + + #renderDomainSection() { + return html` + + Valid domain names are: "example.com", "www.example.com", "example.com:8080", or "https://www.example.com/".
Furthermore + also one-level paths in domains are supported, eg. "example.com/en" or "/en". +
+ ${this.#renderDomains()} ${this.#renderAddNewDomainButton()} +
`; + } + + #renderDomains() { + if (!this._domains?.length) return; + return html`
+ ${repeat( + this._domains, + (domain) => domain.isoCode, + (domain, index) => html` + this.#onChangeDomainHostname(e, index)}> + this.#onChangeDomainLanguage(e, index)}> + ${this.#renderLanguageModelOptions()} + + this.#onRemoveDomain(index)}> + + + `, + )} +
`; + } + + #renderLanguageModelOptions() { + return html`${repeat( + this._languageModel, + (model) => model.isoCode, + (model) => html`${model.name}`, + )}`; + } + + #renderAddNewDomainButton() { + return html` + this.#onAddDomain()}> + + + + + + this.#onAddDomain(true)}> + + + `; + } + + static styles = [ + UmbTextStyles, + css` + uui-button-group { + width: 100%; + } + + uui-box:first-child { + margin-bottom: var(--uui-size-layout-1); + } + + #dropdown { + flex-grow: 0; + } + + #domains { + margin-top: var(--uui-size-layout-1); + margin-bottom: var(--uui-size-2); + display: grid; + grid-template-columns: 1fr 1fr auto; + grid-gap: var(--uui-size-1); + } + `, + ]; +} + +export default UmbCultureAndHostnamesModalElement; + +declare global { + interface HTMLElementTagNameMap { + 'umb-culture-and-hostnames-modal': UmbCultureAndHostnamesModalElement; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/culture-and-hostnames-modal.token.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/culture-and-hostnames-modal.token.ts new file mode 100644 index 0000000000..b6c802805a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/culture-and-hostnames-modal.token.ts @@ -0,0 +1,21 @@ +import type { DomainPresentationModel } from '@umbraco-cms/backoffice/backend-api'; +import { UmbModalToken } from '@umbraco-cms/backoffice/modal'; + +export interface UmbCultureAndHostnamesModalData { + unique: string | null; +} + +export interface UmbCultureAndHostnamesModalValue { + defaultIsoCode?: string | null; + domains: Array; +} + +export const UMB_CULTURE_AND_HOSTNAMES_MODAL = new UmbModalToken< + UmbCultureAndHostnamesModalData, + UmbCultureAndHostnamesModalValue +>('Umb.Modal.CultureAndHostnames', { + modal: { + type: 'sidebar', + size: 'medium', + }, +}); diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/index.ts new file mode 100644 index 0000000000..0b0238bc88 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/index.ts @@ -0,0 +1,2 @@ +export * from './culture-and-hostnames-modal.token.js'; +export * from './culture-and-hostnames-modal.element.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/culture-and-hostnames.repository.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/culture-and-hostnames.repository.ts new file mode 100644 index 0000000000..219c5a75f6 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/culture-and-hostnames.repository.ts @@ -0,0 +1,42 @@ +import { UmbDocumentCultureAndHostnamesServerDataSource } from './culture-and-hostnames.server.data.js'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { UmbBaseController } from '@umbraco-cms/backoffice/class-api'; +import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; +import type { UmbApi } from '@umbraco-cms/backoffice/extension-api'; +import type { DomainsPresentationModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; + +export class UmbDocumentCultureAndHostnamesRepository extends UmbBaseController implements UmbApi { + #dataSource = new UmbDocumentCultureAndHostnamesServerDataSource(this); + + #notificationContext?: typeof UMB_NOTIFICATION_CONTEXT.TYPE; + + constructor(host: UmbControllerHost) { + super(host); + + this.consumeContext(UMB_NOTIFICATION_CONTEXT, (instance) => { + this.#notificationContext = instance; + }); + } + + async readCultureAndHostnames(unique: string) { + if (!unique) throw new Error('Unique is missing'); + + const { data, error } = await this.#dataSource.read(unique); + if (!error) { + return { data }; + } + return { error }; + } + + async updateCultureAndHostnames(unique: string, data: DomainsPresentationModelBaseModel) { + if (!unique) throw new Error('Unique is missing'); + if (!data) throw new Error('Data is missing'); + + const { error } = await this.#dataSource.update(unique, data); + if (!error) { + const notification = { data: { message: `Cultures and hostnames saved` } }; + this.#notificationContext?.peek('positive', notification); + } + return { error }; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/culture-and-hostnames.server.data.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/culture-and-hostnames.server.data.ts new file mode 100644 index 0000000000..c3cd1dabe2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/culture-and-hostnames.server.data.ts @@ -0,0 +1,44 @@ +import { DocumentResource } from '@umbraco-cms/backoffice/backend-api'; +import type { DomainsPresentationModelBaseModel } from '@umbraco-cms/backoffice/backend-api'; +import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; +import { tryExecuteAndNotify } from '@umbraco-cms/backoffice/resources'; + +/** + * A data source for the Document Culture and Hostnames that fetches data from the server + * @export + * @class UmbDocumentCultureAndHostnamesServerDataSource + * @implements {RepositoryDetailDataSource} + */ +export class UmbDocumentCultureAndHostnamesServerDataSource { + #host: UmbControllerHost; + + /** + * Creates an instance of UmbDocumentCultureAndHostnamesServerDataSource. + * @param {UmbControllerHost} host + * @memberof UmbDocumentCultureAndHostnamesServerDataSource + */ + constructor(host: UmbControllerHost) { + this.#host = host; + } + + /** + * Fetches the Culture and Hostnames for the given Document unique + * @param {string} unique + * @memberof UmbDocumentCultureAndHostnamesServerDataSource + */ + async read(unique: string) { + if (!unique) throw new Error('Unique is missing'); + return tryExecuteAndNotify(this.#host, DocumentResource.getDocumentByIdDomains({ id: unique })); + } + + /** + * Updates Culture and Hostnames for the given Document unique + * @param {string} unique + * @param {DomainsPresentationModelBaseModel} data + * @memberof UmbDocumentCultureAndHostnamesServerDataSource + */ + async update(unique: string, data: DomainsPresentationModelBaseModel) { + if (!unique) throw new Error('Unique is missing'); + return tryExecuteAndNotify(this.#host, DocumentResource.putDocumentByIdDomains({ id: unique, requestBody: data })); + } +} diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/index.ts new file mode 100644 index 0000000000..76199887dd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/index.ts @@ -0,0 +1,2 @@ +export { UmbDocumentCultureAndHostnamesRepository } from './culture-and-hostnames.repository.js'; +export { UMB_DOCUMENT_CULTURE_AND_HOSTNAMES_REPOSITORY_ALIAS } from './manifests.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/manifests.ts new file mode 100644 index 0000000000..603ca30ed7 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/repository/manifests.ts @@ -0,0 +1,13 @@ +import { UmbDocumentCultureAndHostnamesRepository } from '../repository/index.js'; +import type { ManifestRepository } from '@umbraco-cms/backoffice/extension-registry'; + +export const UMB_DOCUMENT_CULTURE_AND_HOSTNAMES_REPOSITORY_ALIAS = 'Umb.Repository.Document.CultureAndHostnames'; + +const repository: ManifestRepository = { + type: 'repository', + alias: UMB_DOCUMENT_CULTURE_AND_HOSTNAMES_REPOSITORY_ALIAS, + name: 'Document Culture And Hostnames Repository', + api: UmbDocumentCultureAndHostnamesRepository, +}; + +export const manifests = [repository]; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/index.ts new file mode 100644 index 0000000000..a25927e33b --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/index.ts @@ -0,0 +1 @@ +export * from './culture-and-hostnames/index.js'; diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts index 5f36414b85..19ee0b9fb0 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/manifests.ts @@ -1,13 +1,13 @@ import { UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS } from '../repository/index.js'; import { UMB_DOCUMENT_ENTITY_TYPE, UMB_DOCUMENT_ROOT_ENTITY_TYPE } from '../entity.js'; import { UmbPublishDocumentEntityAction } from './publish.action.js'; -import { UmbDocumentCultureAndHostnamesEntityAction } from './culture-and-hostnames.action.js'; import { UmbCreateDocumentBlueprintEntityAction } from './create-blueprint.action.js'; import { UmbDocumentPublicAccessEntityAction } from './public-access.action.js'; import { UmbUnpublishDocumentEntityAction } from './unpublish.action.js'; import { UmbRollbackDocumentEntityAction } from './rollback.action.js'; import { manifests as createManifests } from './create/manifests.js'; import { manifests as permissionManifests } from './permissions/manifests.js'; +import { manifests as cultureAndHostnamesManifests } from './culture-and-hostnames/manifests.js'; import { UmbCopyEntityAction, UmbMoveEntityAction, @@ -18,6 +18,7 @@ import type { ManifestTypes } from '@umbraco-cms/backoffice/extension-registry'; const entityActions: Array = [ ...createManifests, ...permissionManifests, + ...cultureAndHostnamesManifests, { type: 'entityAction', alias: 'Umb.EntityAction.Document.CreateBlueprint', @@ -70,19 +71,6 @@ const entityActions: Array = [ entityTypes: [UMB_DOCUMENT_ROOT_ENTITY_TYPE, UMB_DOCUMENT_ENTITY_TYPE], }, }, - { - type: 'entityAction', - alias: 'Umb.EntityAction.Document.CultureAndHostnames', - name: 'Culture And Hostnames Document Entity Action', - weight: 400, - api: UmbDocumentCultureAndHostnamesEntityAction, - meta: { - icon: 'icon-home', - label: 'Culture And Hostnames (TBD)', - repositoryAlias: UMB_DOCUMENT_DETAIL_REPOSITORY_ALIAS, - entityTypes: [UMB_DOCUMENT_ENTITY_TYPE], - }, - }, { type: 'entityAction', alias: 'Umb.EntityAction.Document.PublicAccess', diff --git a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts index 6791477575..50f3d55158 100644 --- a/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts +++ b/src/Umbraco.Web.UI.Client/src/packages/documents/documents/index.ts @@ -7,6 +7,7 @@ export * from './recycle-bin/index.js'; export * from './user-permissions/index.js'; export * from './components/index.js'; export * from './entity.js'; +export * from './entity-actions/index.js'; export { UMB_DOCUMENT_TREE_ALIAS } from './tree/index.js'; export { UMB_CONTENT_MENU_ALIAS } from './menu.manifests.js';