* Adding the sorter controller, and fixing some ui elements so you are able to drag the hostname elements around to sort them * Fixed sorting * Changed the html structure and tweaked around with the css to make it look better. Added a description for the Culture section. Alligned the rendered text to allign better with the name "Culture and Hostnames" * Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/culture-and-hostnames-modal.element.ts Forgot to remove this after I was done testing Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/Umbraco.Web.UI.Client/src/packages/documents/documents/entity-actions/culture-and-hostnames/modal/culture-and-hostnames-modal.element.ts Changing grid-gap to just gap Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Removed the disabled and readonly props I added since they are not needed. Removed the conditional rendering that was attached to the readonly and disabled properties * Removed the item id from the element and changed css and sorter logic to target the hostname-item class instead * Updated test * Bumped helpers --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Niels Lyngsø <nsl@umbraco.dk> Co-authored-by: Andreas Zerbst <73799582+andr317c@users.noreply.github.com> Co-authored-by: Andreas Zerbst <andr317c@live.dk>
This commit is contained in:
committed by
GitHub
parent
9c038bc68b
commit
f99e9394f8
@@ -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',
|
||||
@@ -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',
|
||||
@@ -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',
|
||||
@@ -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',
|
||||
@@ -2786,7 +2786,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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
@@ -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() {
|
||||
@@ -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) {
|
||||
@@ -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"
|
||||
@@ -129,47 +210,16 @@ 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`
|
||||
<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}
|
||||
@@ -187,6 +237,8 @@ export class UmbCultureAndHostnamesModalElement extends UmbModalBaseElement<
|
||||
@click=${() => this.#onRemoveDomain(index)}>
|
||||
<uui-icon name="icon-trash"></uui-icon>
|
||||
</uui-button>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
@@ -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%;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
`,
|
||||
];
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.42",
|
||||
"@umbraco/playwright-testhelpers": "^17.0.11",
|
||||
"@umbraco/playwright-testhelpers": "^17.0.12",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"node-fetch": "^2.6.7"
|
||||
@@ -67,9 +67,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@umbraco/playwright-testhelpers": {
|
||||
"version": "17.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-17.0.11.tgz",
|
||||
"integrity": "sha512-+2zijm64oppD17NQg0om7ip1iFJsTQy0ugGgQamZvpf2mUPoGV2CpIz7enPY5YmrQerPacS/1riBMWx/eafqHA==",
|
||||
"version": "17.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@umbraco/playwright-testhelpers/-/playwright-testhelpers-17.0.12.tgz",
|
||||
"integrity": "sha512-GhOj5ytXEY1sG8Nt6CAkJcqjxfRWUFKLl63SCk2quew/1rLCeaUV5I2+YJ3LkfQetMdDlqtMVZP7FdMk+iWJNQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "2.0.42",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@umbraco/json-models-builders": "^2.0.42",
|
||||
"@umbraco/playwright-testhelpers": "^17.0.11",
|
||||
"@umbraco/playwright-testhelpers": "^17.0.12",
|
||||
"camelize": "^1.0.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"node-fetch": "^2.6.7"
|
||||
|
||||
@@ -36,6 +36,7 @@ test('can add a culture', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
|
||||
// Act
|
||||
await umbracoUi.content.clickActionsMenuForContent(contentName);
|
||||
await umbracoUi.content.clickCultureAndHostnamesActionMenuOption();
|
||||
await umbracoUi.content.clickAddNewHostnameButton();
|
||||
await umbracoUi.content.selectCultureLanguageOption(languageName);
|
||||
await umbracoUi.content.clickSaveModalButton();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user