Skip to content
38 changes: 19 additions & 19 deletions src/Umbraco.Web.UI.Client/src/assets/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,26 +113,26 @@ export default {
},
assignDomain: {
permissionDenied: 'Permission denied.',
addNew: 'Add new domain',
addCurrent: 'Add current domain',
addNew: 'Add new hostname',
addCurrent: 'Add current hostname',
remove: 'remove',
invalidNode: 'Invalid node.',
invalidDomain: 'One or more domains have an invalid format.',
duplicateDomain: 'Domain has already been assigned.',
language: 'Language',
domain: 'Domain',
domainCreated: "New domain '%0%' has been created",
domainDeleted: "Domain '%0%' is deleted",
domainExists: "Domain '%0%' has already been assigned",
domainUpdated: "Domain '%0%' has been updated",
orEdit: 'Edit Current Domains',
invalidDomain: 'One or more hostnames have an invalid format.',
duplicateDomain: 'Hostname has already been assigned.',
language: 'Culture',
domain: 'Hostname',
domainCreated: "New hostname '%0%' has been created",
domainDeleted: "Hostname '%0%' is deleted",
domainExists: "Hostname '%0%' has already been assigned",
domainUpdated: "Hostname '%0%' has been updated",
orEdit: 'Edit Current Hostnames',
domainHelpWithVariants:
'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, e.g. "example.com/en" or "/en".',
'Valid hostnames are: "example.com", "www.example.com", "example.com:8080", or "https://www.example.com/". Furthermore also one-level paths in hostnames are supported, e.g. "example.com/en" or "/en".',
inherit: 'Inherit',
setLanguage: 'Culture',
setLanguageHelp:
'Set the culture for nodes below the current node,<br /> or inherit culture from parent nodes. Will also apply<br /> to the current node, unless a domain below applies too.',
setDomains: 'Domains',
'Set the culture for nodes below the current node, or inherit culture from parent nodes. Will also apply to the current node, unless a hostname below applies too.',
setDomains: 'Hostnames',
},
buttons: {
clearSelection: 'Clear selection',
Expand Down Expand Up @@ -191,7 +191,7 @@ export default {
save: 'Media saved',
},
auditTrails: {
assigndomain: 'Domain assigned: %0%',
assigndomain: 'Hostname assigned: %0%',
atViewingFor: 'Viewing for',
delete: 'Content deleted',
unpublish: 'Content unpublished',
Expand All @@ -209,7 +209,7 @@ export default {
custom: '%0%',
contentversionpreventcleanup: 'Clean up disabled for version: %0%',
contentversionenablecleanup: 'Clean up enabled for version: %0%',
smallAssignDomain: 'Assign Domain',
smallAssignDomain: 'Assign Hostname',
smallCopy: 'Copy',
smallPublish: 'Publish',
smallPublishVariant: 'Publish',
Expand Down Expand Up @@ -1562,9 +1562,9 @@ export default {
dictionaryItemExportedError: 'An error occurred while exporting the dictionary item(s)',
dictionaryItemImported: 'The following dictionary item(s) has been imported!',
publishWithNoDomains:
'Domains are not configured for multilingual site, please contact an administrator, see log for more information',
'Hostnames are not configured for multilingual site, please contact an administrator, see log for more information',
publishWithMissingDomain:
'There is no domain configured for %0%, please contact an administrator, see log for more information',
'There is no hostname configured for %0%, please contact an administrator, see log for more information',
copySuccessMessage: 'Your system information has successfully been copied to the clipboard',
cannotCopyInformation: 'Could not copy your system information to the clipboard',
webhookSaved: 'Webhook saved',
Expand Down Expand Up @@ -2772,7 +2772,7 @@ export default {
minimalLevelDescription: 'We will only send an anonymised site ID to let us know that the site exists.',
basicLevelDescription: 'We will send an anonymised site ID, Umbraco version, and packages installed',
detailedLevelDescription:
'We will send: <ul><li>Anonymised 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, Backoffice external login providers, 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, whether the delivery API is enabled, and allows public access, and if you are in debug mode.</li></ul> <em>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 anonymised information being collected.</em>',
'We will send: <ul><li>Anonymised site ID, Umbraco version, and packages installed.</li><li>Number of: Root nodes, Content nodes, Media, Document Types, Templates, Languages, Hostnames, User Group, Users, Members, Backoffice external login providers, 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, whether the delivery API is enabled, and allows public access, and if you are in debug mode.</li></ul> <em>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 anonymised information being collected.</em>',
},
routing: {
routeNotFoundTitle: 'Not found',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,49 @@ import type {
UmbCultureAndHostnamesModalData,
UmbCultureAndHostnamesModalValue,
} from './culture-and-hostnames-modal.token.js';
import { css, customElement, html, query, repeat, state } from '@umbraco-cms/backoffice/external/lit';
import {
css,
customElement,
html,
query,
repeat,
state,
type PropertyValues,
} from '@umbraco-cms/backoffice/external/lit';
import { UmbLanguageCollectionRepository } from '@umbraco-cms/backoffice/language';
import { UmbModalBaseElement } from '@umbraco-cms/backoffice/modal';
import { UmbTextStyles } from '@umbraco-cms/backoffice/style';
import type { DomainPresentationModel } from '@umbraco-cms/backoffice/external/backend-api';
import type { UmbLanguageDetailModel } from '@umbraco-cms/backoffice/language';
import type { UUIInputEvent, UUIPopoverContainerElement, UUISelectEvent } from '@umbraco-cms/backoffice/external/uui';
import { UmbSorterController } from '@umbraco-cms/backoffice/sorter';
import { UmbId } from '@umbraco-cms/backoffice/id';

interface UmbDomainPresentationModel {
unique: string;
domainName: string;
isoCode: string;
}
@customElement('umb-culture-and-hostnames-modal')
export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement<
UmbCultureAndHostnamesModalData,
UmbCultureAndHostnamesModalValue
> {
#sorter = new UmbSorterController(this, {
getUniqueOfElement: (element) => {
return element.getAttribute('data-sort-entry-id');
},
getUniqueOfModel: (modelEntry: UmbDomainPresentationModel) => {
return modelEntry.unique;
},
itemSelector: '.hostname-item',
containerSelector: '#sorter-wrapper',
onChange: ({ model }) => {
const oldValue = this._domains;
this._domains = model;
this.requestUpdate('_domains', oldValue);
},
});

#documentRepository = new UmbDocumentCultureAndHostnamesRepository(this);
#languageCollectionRepository = new UmbLanguageCollectionRepository(this);

Expand All @@ -28,13 +58,20 @@ export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement<
private _defaultIsoCode?: string | null;

@state()
private _domains: Array<DomainPresentationModel> = [];
private _domains: Array<UmbDomainPresentationModel> = [];

@query('#more-options')
popoverContainerElement?: UUIPopoverContainerElement;

// Init

override willUpdate(changedProperties: PropertyValues) {
if (changedProperties.has('_domains')) {
// Update sorter whenever _domains changes
this.#sorter.setModel(this._domains);
}
}

override firstUpdated() {
this.#unique = this.data?.unique;
this.#requestLanguages();
Expand All @@ -47,7 +84,7 @@ export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement<

if (!data) return;
this._defaultIsoCode = data.defaultIsoCode;
this._domains = data.domains;
this._domains = data.domains.map((domain) => ({ ...domain, unique: UmbId.new() }));
}

async #requestLanguages() {
Expand All @@ -57,7 +94,8 @@ export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement<
}

async #handleSave() {
this.value = { defaultIsoCode: this._defaultIsoCode, domains: this._domains };
const cleanDomains = this._domains.map((domain) => ({ domainName: domain.domainName, isoCode: domain.isoCode }));
this.value = { defaultIsoCode: this._defaultIsoCode, domains: cleanDomains };
const { error } = await this.#documentRepository.updateCultureAndHostnames(this.#unique!, this.value);

if (!error) {
Expand Down Expand Up @@ -101,18 +139,61 @@ export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement<
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this.popoverContainerElement?.hidePopover();
this._domains = [...this._domains, { isoCode: defaultModel?.unique ?? '', domainName: window.location.host }];
this._domains = [
...this._domains,
{ isoCode: defaultModel?.unique ?? '', domainName: window.location.host, unique: UmbId.new() },
];

this.#focusNewItem();
} else {
this._domains = [...this._domains, { isoCode: defaultModel?.unique ?? '', domainName: '' }];
this._domains = [...this._domains, { isoCode: defaultModel?.unique ?? '', domainName: '', unique: UmbId.new() }];

this.#focusNewItem();
}
}

async #focusNewItem() {
await this.updateComplete;
const items = this.shadowRoot?.querySelectorAll('div.hostname-item') as NodeListOf<HTMLElement>;
const newItem = items[items.length - 1];
const firstInput = newItem?.querySelector('uui-input') as HTMLElement;
firstInput?.focus();
}

// Renders

override render() {
return html`
<umb-body-layout headline=${this.localize.term('actions_assigndomain')}>
${this.#renderCultureSection()} ${this.#renderDomainSection()}
<uui-box>
<umb-property-layout
label=${this.localize.term('assignDomain_language')}
description=${this.localize.term('assignDomain_setLanguageHelp')}
orientation="vertical"
><div slot="editor">
<uui-combobox
id="select"
label=${this.localize.term('assignDomain_language')}
.value=${(this._defaultIsoCode as string) ?? 'inherit'}
@change=${this.#onChangeLanguage}>
<uui-combobox-list>
<uui-combobox-list-option .value=${'inherit'}>
${this.localize.term('assignDomain_inherit')}
</uui-combobox-list-option>
${this.#renderLanguageModelOptions()}
</uui-combobox-list>
</uui-combobox>
</div>
</umb-property-layout>
</uui-box>
<uui-box>
<umb-property-layout
label=${this.localize.term('assignDomain_setDomains')}
description=${this.localize.term('assignDomain_domainHelpWithVariants')}
orientation="vertical"
><div slot="editor">${this.#renderDomains()} ${this.#renderAddNewDomainButton()}</div></umb-property-layout
>
</uui-box>
<uui-button
slot="actions"
id="close"
Expand All @@ -129,64 +210,35 @@ export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement<
`;
}

#renderCultureSection() {
return html`
<uui-box headline=${this.localize.term('assignDomain_setLanguage')}>
<uui-label for="select">${this.localize.term('assignDomain_language')}</uui-label>
<uui-combobox
id="select"
label=${this.localize.term('assignDomain_language')}
.value=${(this._defaultIsoCode as string) ?? 'inherit'}
@change=${this.#onChangeLanguage}>
<uui-combobox-list>
<uui-combobox-list-option .value=${'inherit'}>
${this.localize.term('assignDomain_inherit')}
</uui-combobox-list-option>
${this.#renderLanguageModelOptions()}
</uui-combobox-list>
</uui-combobox>
</uui-box>
`;
}

#renderDomainSection() {
return html`
<uui-box headline=${this.localize.term('assignDomain_setDomains')}>
<umb-localize key="assignDomain_domainHelpWithVariants">
Valid domain names are: "example.com", "www.example.com", "example.com:8080", or
"https://www.example.com/".<br />Furthermore also one-level paths in domains are supported, eg.
"example.com/en" or "/en".
</umb-localize>
${this.#renderDomains()} ${this.#renderAddNewDomainButton()}
</uui-box>
`;
}

#renderDomains() {
if (!this._domains?.length) return;
return html`
<div id="domains">
<div id="sorter-wrapper">
${repeat(
this._domains,
(domain) => domain.isoCode,
(domain) => domain.unique,
(domain, index) => html`
<uui-input
label=${this.localize.term('assignDomain_domain')}
.value=${domain.domainName}
@change=${(e: UUIInputEvent) => this.#onChangeDomainHostname(e, index)}></uui-input>
<uui-combobox
.value=${domain.isoCode as string}
label=${this.localize.term('assignDomain_language')}
@change=${(e: UUISelectEvent) => this.#onChangeDomainLanguage(e, index)}>
<uui-combobox-list> ${this.#renderLanguageModelOptions()} </uui-combobox-list>
</uui-combobox>
<uui-button
look="outline"
color="danger"
label=${this.localize.term('assignDomain_remove')}
@click=${() => this.#onRemoveDomain(index)}>
<uui-icon name="icon-trash"></uui-icon>
</uui-button>
<div class="hostname-item" data-sort-entry-id=${domain.unique}>
<uui-icon name="icon-grip" class="handle"></uui-icon>
<div class="hostname-wrapper">
<uui-input
label=${this.localize.term('assignDomain_domain')}
.value=${domain.domainName}
@change=${(e: UUIInputEvent) => this.#onChangeDomainHostname(e, index)}></uui-input>
<uui-combobox
.value=${domain.isoCode as string}
label=${this.localize.term('assignDomain_language')}
@change=${(e: UUISelectEvent) => this.#onChangeDomainLanguage(e, index)}>
<uui-combobox-list> ${this.#renderLanguageModelOptions()} </uui-combobox-list>
</uui-combobox>
<uui-button
look="outline"
color="danger"
label=${this.localize.term('assignDomain_remove')}
@click=${() => this.#onRemoveDomain(index)}>
<uui-icon name="icon-trash"></uui-icon>
</uui-button>
</div>
</div>
`,
)}
</div>
Expand Down Expand Up @@ -229,6 +281,9 @@ export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement<
static override styles = [
UmbTextStyles,
css`
umb-property-layout[orientation='vertical'] {
padding: 0;
}
uui-button-group {
width: 100%;
}
Expand All @@ -241,12 +296,49 @@ export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement<
flex-grow: 0;
}

#domains {
margin-top: var(--uui-size-layout-1);
margin-bottom: var(--uui-size-2);
.hostname-item {
position: relative;
display: flex;
gap: var(--uui-size-1);
align-items: center;
}

.hostname-wrapper {
position: relative;
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr auto;
grid-gap: var(--uui-size-1);
gap: var(--uui-size-1);
}

#sorter-wrapper {
margin-bottom: var(--uui-size-2);
display: flex;
flex-direction: column;
gap: var(--uui-size-1);
}

.handle {
cursor: grab;
}

.handle:active {
cursor: grabbing;
}
#action {
display: block;
}

.--umb-sorter-placeholder {
position: relative;
visibility: hidden;
}
.--umb-sorter-placeholder::after {
content: '';
position: absolute;
inset: 0px;
border-radius: var(--uui-border-radius);
border: 1px dashed var(--uui-color-divider-emphasis);
}
`,
];
Expand Down
Loading