Merge branch 'release/16.4' into v16/dev

This commit is contained in:
Jacob Overgaard
2025-11-24 16:28:24 +01:00
18 changed files with 345 additions and 161 deletions

View File

@@ -23,4 +23,11 @@ public class RichTextBlockValue : BlockValue<RichTextBlockLayoutItem>
/// <inheritdoc />
[JsonIgnore]
public override string PropertyEditorAlias => Constants.PropertyEditors.Aliases.RichText;
/// <inheritdoc />
#pragma warning disable CS0672 // Member overrides obsolete member
#pragma warning disable CS0618 // Type or member is obsolete
public override bool SupportsBlockLayoutAlias(string alias) => base.SupportsBlockLayoutAlias(alias) || alias.Equals("Umbraco.TinyMCE");
#pragma warning restore CS0618 // Type or member is obsolete
#pragma warning restore CS0672 // Member overrides obsolete member
}

View File

@@ -53,6 +53,12 @@ public interface IPublishedContentTypeFactory
/// </summary>
PublishedDataType GetDataType(int id);
/// <summary>
/// Clears the internal data type cache.
/// </summary>
void ClearDataTypeCache()
{ }
/// <summary>
/// Notifies the factory of datatype changes.
/// </summary>

View File

@@ -65,6 +65,22 @@ public class PublishedContentTypeFactory : IPublishedContentTypeFactory
return dataType;
}
/// <inheritdoc />
public void ClearDataTypeCache()
{
if (_publishedDataTypes is null)
{
// Not initialized yet, so skip and avoid lock
return;
}
lock (_publishedDataTypesLocker)
{
// Clear cache (and let it lazy initialize again later)
_publishedDataTypes = null;
}
}
/// <inheritdoc />
public void NotifyDataTypeChanges(params int[] ids)
{

View File

@@ -7,6 +7,7 @@ using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.DependencyInjection;
using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Models.Membership;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Security;
@@ -51,9 +52,12 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
private readonly DistributedCache _distributedCache;
private readonly IScopeAccessor _scopeAccessor;
private readonly ICoreScopeProvider _scopeProvider;
private readonly IPublishedContentTypeFactory _publishedContentTypeFactory;
private bool _rebuildCache;
private bool _invalidateBackofficeUserAccess;
[Obsolete("Please use the constructor taking all parameters. Scheduled for removal in Umbraco 19.")]
public MigrationPlanExecutor(
ICoreScopeProvider scopeProvider,
IScopeAccessor scopeAccessor,
@@ -65,6 +69,33 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
IKeyValueService keyValueService,
IServiceScopeFactory serviceScopeFactory,
AppCaches appCaches)
: this(
scopeProvider,
scopeAccessor,
loggerFactory,
migrationBuilder,
databaseFactory,
databaseCacheRebuilder,
distributedCache,
keyValueService,
serviceScopeFactory,
appCaches,
StaticServiceProvider.Instance.GetRequiredService<IPublishedContentTypeFactory>())
{
}
public MigrationPlanExecutor(
ICoreScopeProvider scopeProvider,
IScopeAccessor scopeAccessor,
ILoggerFactory loggerFactory,
IMigrationBuilder migrationBuilder,
IUmbracoDatabaseFactory databaseFactory,
IDatabaseCacheRebuilder databaseCacheRebuilder,
DistributedCache distributedCache,
IKeyValueService keyValueService,
IServiceScopeFactory serviceScopeFactory,
AppCaches appCaches,
IPublishedContentTypeFactory publishedContentTypeFactory)
{
_scopeProvider = scopeProvider;
_scopeAccessor = scopeAccessor;
@@ -76,6 +107,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
_serviceScopeFactory = serviceScopeFactory;
_appCaches = appCaches;
_distributedCache = distributedCache;
_publishedContentTypeFactory = publishedContentTypeFactory;
_logger = _loggerFactory.CreateLogger<MigrationPlanExecutor>();
}
@@ -301,6 +333,7 @@ public class MigrationPlanExecutor : IMigrationPlanExecutor
_appCaches.IsolatedCaches.ClearAllCaches();
await _databaseCacheRebuilder.RebuildAsync(false);
_distributedCache.RefreshAllPublishedSnapshot();
_publishedContentTypeFactory.ClearDataTypeCache();
}
private async Task RevokeBackofficeTokens()

View File

@@ -1,12 +1,12 @@
{
"name": "@umbraco-cms/backoffice",
"version": "16.4.0-rc",
"version": "16.4.0-rc3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@umbraco-cms/backoffice",
"version": "16.4.0-rc",
"version": "16.4.0-rc3",
"license": "MIT",
"workspaces": [
"./src/packages/*",

View File

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

View File

@@ -2869,29 +2869,52 @@ export default {
ar: 'العربية',
bs: 'Bosanski',
cs: 'Česky',
'cs-cz': 'Česky (Czechia)',
cy: 'Cymraeg',
'cy-gb': 'Cymraeg (UK)',
da: 'Dansk',
'da-dk': 'Dansk (Danmark)',
de: 'Deutsch',
'de-de': 'Deutsch (Deutschland)',
'de-ch': 'Deutsch (Schweiz)',
en: 'English (UK)',
'en-us': 'English (US)',
es: 'Español',
'es-es': 'Español (España)',
fr: 'Français',
he: 'Hebrew',
'fr-fr': 'Français (France)',
'fr-ch': 'Français (Suisse)',
he: 'עברית',
'he-il': 'עברית (ישראל)',
hr: 'Hrvatski',
'hr-hr': 'Hrvatski (Hrvatska)',
it: 'Italiano',
'it-it': 'Italiano (Italia)',
'it-ch': 'Italiano (Svizzera)',
ja: '日本語',
'ja-jp': '日本語 (日本)',
ko: '한국어',
'ko-kr': '한국어 (한국)',
nb: 'Norsk Bokmål',
'nb-no': 'Norsk (Bokmål)',
nl: 'Nederlands',
'nl-nl': 'Nederlands (Nederland)',
pl: 'Polski',
'pl-pl': 'Polski (Polska)',
pt: 'Português',
'pt-br': 'Português (Brasil)',
ro: 'Romana',
ro: 'Română',
'ro-ro': 'Română (România)',
ru: 'Русский',
'ru-ru': 'Русский (Россия)',
sv: 'Svenska',
'sv-se': 'Svenska (Sverige)',
tr: 'Türkçe',
'tr-tr': 'Türkçe (Türkiye Cumhuriyeti)',
uk: 'Українська',
'uk-ua': 'Українська (Україна)',
zh: '中文',
'zh-cn': '中文(简体,中国)',
'zh-tw': '中文(正體,台灣)',
vi: 'Tiếng Việt',
},

View File

@@ -2836,34 +2836,4 @@ export default {
resetUrlMessage: 'Bạn có chắc chắn muốn đặt lại URL này không?',
resetUrlLabel: 'Đặt lại',
},
uiCulture: {
ar: 'العربية',
bs: 'Bosanski',
cs: 'Česky',
cy: 'Cymraeg',
da: 'Dansk',
de: 'Deutsch',
en: 'English (UK)',
'en-us': 'English (US)',
es: 'Español',
fr: 'Français',
he: 'Hebrew',
hr: 'Hrvatski',
it: 'Italiano',
ja: '日本語',
ko: '한국어',
nb: 'Norsk Bokmål',
nl: 'Nederlands',
pl: 'Polski',
pt: 'Português',
'pt-br': 'Português (Brasil)',
ro: 'Romana',
ru: 'Русский',
sv: 'Svenska',
tr: 'Türkçe',
uk: 'Українська',
zh: '中文',
'zh-tw': '中文(正體,台灣)',
vi: 'Tiếng Việt',
},
} as UmbLocalizationDictionary;

View File

@@ -17,7 +17,7 @@ import type {
UmbPropertyEditorUiElement,
UmbPropertyEditorConfigCollection,
} from '@umbraco-cms/backoffice/property-editor';
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { jsonStringComparison, observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { UMB_PROPERTY_CONTEXT, UMB_PROPERTY_DATASET_CONTEXT } from '@umbraco-cms/backoffice/property';
import { UmbFormControlMixin, UmbValidationContext } from '@umbraco-cms/backoffice/validation';
import type { UmbBlockTypeGroup } from '@umbraco-cms/backoffice/block-type';
@@ -181,15 +181,22 @@ export class UmbPropertyEditorUIBlockGridElement
]).pipe(debounceTime(20)),
([layouts, contents, settings, exposes]) => {
if (layouts.length === 0) {
if (this.value === undefined) {
return;
}
super.value = undefined;
} else {
super.value = {
const newValue = {
...super.value,
layout: { [UMB_BLOCK_GRID_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts },
contentData: contents,
settingsData: settings,
expose: exposes,
};
if (jsonStringComparison(this.value, newValue)) {
return;
}
super.value = newValue;
}
// If we don't have a value set from the outside or an internal value, we don't want to set the value.

View File

@@ -25,7 +25,7 @@ import {
UmbFormControlMixin,
UmbValidationContext,
} from '@umbraco-cms/backoffice/validation';
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { jsonStringComparison, observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { debounceTime } from '@umbraco-cms/backoffice/external/rxjs';
import { UMB_CONTENT_WORKSPACE_CONTEXT } from '@umbraco-cms/backoffice/content';
@@ -339,15 +339,22 @@ export class UmbPropertyEditorUIBlockListElement
]).pipe(debounceTime(20)),
([layouts, contents, settings, exposes]) => {
if (layouts.length === 0) {
if (this.value === undefined) {
return;
}
super.value = undefined;
} else {
super.value = {
const newValue = {
...super.value,
layout: { [UMB_BLOCK_LIST_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts },
contentData: contents,
settingsData: settings,
expose: exposes,
};
if (jsonStringComparison(this.value, newValue)) {
return;
}
super.value = newValue;
}
// If we don't have a value set from the outside or an internal value, we don't want to set the value.

View File

@@ -4,281 +4,335 @@ export const manifests: Array<ManifestLocalization> = [
{
type: 'localization',
alias: 'Umb.Localization.AR',
weight: 100,
name: 'Arabic Backoffice UI Localization',
meta: {
culture: 'ar',
},
meta: { culture: 'ar' },
js: () => import('../../../assets/lang/ar.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.BS',
weight: 100,
name: 'Bosnian Backoffice UI Localization',
meta: {
culture: 'bs',
},
meta: { culture: 'bs' },
js: () => import('../../../assets/lang/bs.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.CS',
weight: 100,
name: 'Czech Backoffice UI Localization',
meta: {
culture: 'cs',
},
meta: { culture: 'cs' },
js: () => import('../../../assets/lang/cs.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.CY',
weight: 100,
name: 'Welsh Backoffice UI Localization',
meta: {
culture: 'cy',
alias: 'Umb.Localization.CS_CZ',
name: 'Czech (Czechia) Backoffice UI Localization',
meta: { culture: 'cs-CZ' },
},
{
type: 'localization',
alias: 'Umb.Localization.CY',
name: 'Welsh Backoffice UI Localization',
meta: { culture: 'cy' },
js: () => import('../../../assets/lang/cy.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.DA',
weight: 100,
name: 'Danish Backoffice UI Localization',
meta: {
culture: 'da',
alias: 'Umb.Localization.CY_GB',
name: 'Welsh (UK) Backoffice UI Localization',
meta: { culture: 'cy-GB' },
},
{
type: 'localization',
alias: 'Umb.Localization.DA',
name: 'Danish Backoffice UI Localization',
meta: { culture: 'da' },
js: () => import('../../../assets/lang/da.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.DE',
weight: 100,
name: 'German Backoffice UI Localization',
meta: {
culture: 'de',
alias: 'Umb.Localization.DA-DK',
name: 'Danish (Denmark) Backoffice UI Localization',
meta: { culture: 'da-DK' },
},
{
type: 'localization',
alias: 'Umb.Localization.DE',
name: 'German Backoffice UI Localization',
meta: { culture: 'de' },
js: () => import('../../../assets/lang/de.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.EN',
weight: 100,
name: 'English (United Kingdom) Backoffice UI Localization',
meta: {
culture: 'en',
alias: 'Umb.Localization.DE_DE',
name: 'German (Germany) Backoffice UI Localization',
meta: { culture: 'de-DE' },
},
{
type: 'localization',
alias: 'Umb.Localization.DE_CH',
name: 'German (Switzerland) Backoffice UI Localization',
meta: { culture: 'de-CH' },
},
{
type: 'localization',
alias: 'Umb.Localization.EN',
name: 'English (United Kingdom) Backoffice UI Localization',
meta: { culture: 'en' },
js: () => import('../../../assets/lang/en.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.EN_US',
weight: 100,
name: 'English (United States) Backoffice UI Localization',
meta: {
culture: 'en-US',
},
meta: { culture: 'en-US' },
js: () => import('../../../assets/lang/en-us.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.ES',
weight: 100,
name: 'Spanish Backoffice UI Localization',
meta: {
culture: 'es',
},
meta: { culture: 'es' },
js: () => import('../../../assets/lang/es.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.FR',
weight: 100,
name: 'French Backoffice UI Localization',
meta: {
culture: 'fr',
alias: 'Umb.Localization.ES_ES',
name: 'Spanish (Spain) Backoffice UI Localization',
meta: { culture: 'es-ES' },
},
{
type: 'localization',
alias: 'Umb.Localization.FR',
name: 'French Backoffice UI Localization',
meta: { culture: 'fr' },
js: () => import('../../../assets/lang/fr.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.HE',
weight: 100,
name: 'Hebrew Backoffice UI Localization',
meta: {
culture: 'he',
alias: 'Umb.Localization.FR_FR',
name: 'French (France) Backoffice UI Localization',
meta: { culture: 'fr-FR' },
},
{
type: 'localization',
alias: 'Umb.Localization.FR_CH',
name: 'French (Switzerland) Backoffice UI Localization',
meta: { culture: 'fr-CH' },
},
{
type: 'localization',
alias: 'Umb.Localization.HE',
name: 'Hebrew Backoffice UI Localization',
meta: { culture: 'he' },
js: () => import('../../../assets/lang/he.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.HR',
weight: 100,
name: 'Croatian Backoffice UI Localization',
meta: {
culture: 'hr',
alias: 'Umb.Localization.HE_IL',
name: 'Hebrew (Israel) Backoffice UI Localization',
meta: { culture: 'he-IL' },
},
{
type: 'localization',
alias: 'Umb.Localization.HR',
name: 'Croatian Backoffice UI Localization',
meta: { culture: 'hr' },
js: () => import('../../../assets/lang/hr.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.IT',
weight: 100,
name: 'Italian Backoffice UI Localization',
meta: {
culture: 'it',
alias: 'Umb.Localization.HR_HR',
name: 'Croatian (Croatia) Backoffice UI Localization',
meta: { culture: 'hr-HR' },
},
{
type: 'localization',
alias: 'Umb.Localization.IT',
name: 'Italian Backoffice UI Localization',
meta: { culture: 'it' },
js: () => import('../../../assets/lang/it.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.JA',
weight: 100,
name: 'Japanese Backoffice UI Localization',
meta: {
culture: 'ja',
alias: 'Umb.Localization.IT_IT',
name: 'Italian (Italy) Backoffice UI Localization',
meta: { culture: 'it-IT' },
},
{
type: 'localization',
alias: 'Umb.Localization.IT_CH',
name: 'Italian (Switzerland) Backoffice UI Localization',
meta: { culture: 'it-CH' },
},
{
type: 'localization',
alias: 'Umb.Localization.JA',
name: 'Japanese Backoffice UI Localization',
meta: { culture: 'ja' },
js: () => import('../../../assets/lang/ja.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.KO',
weight: 100,
name: 'Korean Backoffice UI Localization',
meta: {
culture: 'ko',
alias: 'Umb.Localization.JA_JP',
name: 'Japanese (Japan) Backoffice UI Localization',
meta: { culture: 'ja-JP' },
},
{
type: 'localization',
alias: 'Umb.Localization.KO',
name: 'Korean Backoffice UI Localization',
meta: { culture: 'ko' },
js: () => import('../../../assets/lang/ko.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.NB',
weight: 100,
name: 'Norwegian Backoffice UI Localization',
meta: {
culture: 'nb',
alias: 'Umb.Localization.KO_KR',
name: 'Korean (Korea) Backoffice UI Localization',
meta: { culture: 'ko-KR' },
},
{
type: 'localization',
alias: 'Umb.Localization.NB',
name: 'Norwegian Backoffice UI Localization',
meta: { culture: 'nb' },
js: () => import('../../../assets/lang/nb.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.NL',
weight: 100,
name: 'Dutch Backoffice UI Localization',
meta: {
culture: 'nl',
alias: 'Umb.Localization.NB_NO',
name: 'Norwegian (Norway) Backoffice UI Localization',
meta: { culture: 'nb-NO' },
},
{
type: 'localization',
alias: 'Umb.Localization.NL',
name: 'Dutch Backoffice UI Localization',
meta: { culture: 'nl' },
js: () => import('../../../assets/lang/nl.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.PL',
weight: 100,
name: 'Polish Backoffice UI Localization',
meta: {
culture: 'pl',
alias: 'Umb.Localization.NL_NL',
name: 'Dutch (Netherlands) Backoffice UI Localization',
meta: { culture: 'nl-NL' },
},
{
type: 'localization',
alias: 'Umb.Localization.PL',
name: 'Polish Backoffice UI Localization',
meta: { culture: 'pl' },
js: () => import('../../../assets/lang/pl.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.PT',
weight: 100,
name: 'Portuguese Backoffice UI Localization',
meta: {
culture: 'pt',
alias: 'Umb.Localization.PL_PL',
name: 'Polish (Poland) Backoffice UI Localization',
meta: { culture: 'pl-PL' },
},
{
type: 'localization',
alias: 'Umb.Localization.PT',
name: 'Portuguese Backoffice UI Localization',
meta: { culture: 'pt' },
js: () => import('../../../assets/lang/pt.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.PT_BR',
weight: 100,
name: 'Portuguese (Brazil) Backoffice UI Localization',
meta: {
culture: 'pt-BR',
},
meta: { culture: 'pt-BR' },
js: () => import('../../../assets/lang/pt-br.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.RO',
weight: 100,
name: 'Romanian Backoffice UI Localization',
meta: {
culture: 'ro',
},
meta: { culture: 'ro' },
js: () => import('../../../assets/lang/ro.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.RU',
weight: 100,
name: 'Russian Backoffice UI Localization',
meta: {
culture: 'ru',
alias: 'Umb.Localization.RO_RO',
name: 'Romanian (Romania) Backoffice UI Localization',
meta: { culture: 'ro-RO' },
},
{
type: 'localization',
alias: 'Umb.Localization.RU',
name: 'Russian Backoffice UI Localization',
meta: { culture: 'ru' },
js: () => import('../../../assets/lang/ru.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.SV',
weight: 100,
name: 'Swedish Backoffice UI Localization',
meta: {
culture: 'sv',
alias: 'Umb.Localization.RU_RU',
name: 'Russian (Russia) Backoffice UI Localization',
meta: { culture: 'ru-RU' },
},
{
type: 'localization',
alias: 'Umb.Localization.SV',
name: 'Swedish Backoffice UI Localization',
meta: { culture: 'sv' },
js: () => import('../../../assets/lang/sv.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.TR',
weight: 100,
name: 'Turkish Backoffice UI Localization',
meta: {
culture: 'tr',
alias: 'Umb.Localization.SV_SE',
name: 'Swedish (Sweden) Backoffice UI Localization',
meta: { culture: 'sv-SE' },
},
{
type: 'localization',
alias: 'Umb.Localization.TR',
name: 'Turkish Backoffice UI Localization',
meta: { culture: 'tr' },
js: () => import('../../../assets/lang/tr.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.UK',
weight: 100,
name: 'Ukrainian Backoffice UI Localization',
meta: {
culture: 'uk',
alias: 'Umb.Localization.TR_TR',
name: 'Turkish (Türkiye) Backoffice UI Localization',
meta: { culture: 'tr-TR' },
},
{
type: 'localization',
alias: 'Umb.Localization.UK',
name: 'Ukrainian Backoffice UI Localization',
meta: { culture: 'uk' },
js: () => import('../../../assets/lang/uk.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.ZH',
weight: 100,
name: 'Chinese Backoffice UI Localization',
meta: {
culture: 'zh',
alias: 'Umb.Localization.UK_UA',
name: 'Ukrainian (Ukraine) Backoffice UI Localization',
meta: { culture: 'uk-UA' },
},
{
type: 'localization',
alias: 'Umb.Localization.ZH',
name: 'Chinese Backoffice UI Localization',
meta: { culture: 'zh' },
js: () => import('../../../assets/lang/zh.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.ZH_TW',
weight: 100,
name: 'Chinese (Taiwan) Backoffice UI Localization',
meta: {
culture: 'zh-TW',
alias: 'Umb.Localization.ZH_CN',
name: 'Chinese (Simplified, China) Backoffice UI Localization',
meta: { culture: 'zh-CN' },
},
{
type: 'localization',
alias: 'Umb.Localization.ZH_TW',
name: 'Chinese (Taiwan) Backoffice UI Localization',
meta: { culture: 'zh-TW' },
js: () => import('../../../assets/lang/zh-tw.js'),
},
{
type: 'localization',
alias: 'Umb.Localization.VI',
weight: 100,
name: 'Vietnamese Backoffice UI Localization',
meta: {
culture: 'vi',
},
meta: { culture: 'vi' },
js: () => import('../../../assets/lang/vi.js'),
},
];

View File

@@ -1,6 +1,6 @@
import type { UmbPropertyEditorRteValueType } from '../types.js';
import { UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS } from '../constants.js';
import { observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { jsonStringComparison, observeMultiple } from '@umbraco-cms/backoffice/observable-api';
import { property, state } from '@umbraco-cms/backoffice/external/lit';
import { UmbBlockRteEntriesContext, UmbBlockRteManagerContext } from '@umbraco-cms/backoffice/block-rte';
import { UmbChangeEvent } from '@umbraco-cms/backoffice/event';
@@ -243,9 +243,12 @@ export abstract class UmbPropertyEditorUiRteElementBase
([layouts, contents, settings, exposes]) => {
if (layouts.length === 0) {
if (super.value?.markup === undefined) {
if (this.value === undefined) {
return;
}
super.value = undefined;
} else {
super.value = {
const newValue = {
...super.value,
blocks: {
layout: {},
@@ -254,9 +257,13 @@ export abstract class UmbPropertyEditorUiRteElementBase
expose: [],
},
};
if (jsonStringComparison(this.value, newValue)) {
return;
}
super.value = newValue;
}
} else {
super.value = {
const newValue = {
markup: this._markup,
blocks: {
layout: { [UMB_BLOCK_RTE_PROPERTY_EDITOR_SCHEMA_ALIAS]: layouts },
@@ -265,6 +272,10 @@ export abstract class UmbPropertyEditorUiRteElementBase
expose: exposes,
},
};
if (jsonStringComparison(this.value, newValue)) {
return;
}
super.value = newValue;
}
// If we don't have a value set from the outside or an internal value, we don't want to set the value.

View File

@@ -417,3 +417,22 @@ test('can add a variant block element with invariant RTE Tiptap in the content',
await umbracoApi.documentType.ensureNameNotExists(customElementTypeName);
await umbracoApi.language.ensureNameNotExists('Danish');
});
// Tests regression issue: https://github.com/umbraco/Umbraco-CMS/issues/20680
test('can move away from a content node with a block grid after making no changes without seeing discard unsaved changes', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
// Arrange
const customDataTypeId = await umbracoApi.dataType.createBlockGridWithPermissions(customDataTypeName, elementTypeId, true, true);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
// Assert
// We do this to make sure that there is no discard changes button visible, if the discard changes was visible, we would not be able to go to the document type
await umbracoUi.documentType.goToDocumentType(documentTypeName);
});

View File

@@ -364,3 +364,21 @@ test('can add a variant block element with invariant RTE Tiptap in the content',
await umbracoApi.documentType.ensureNameNotExists(customElementTypeName);
await umbracoApi.language.ensureNameNotExists('Danish');
});
// Tests regression issue: https://github.com/umbraco/Umbraco-CMS/issues/20680
test('can move away from a content node with a block list after making no changes without seeing discard unsaved changes', {tag: '@smoke'}, async ({umbracoApi, umbracoUi}) => {
// Arrange
const customDataTypeId = await umbracoApi.dataType.createBlockListDataTypeWithABlock(customDataTypeName, elementTypeId);
const documentTypeId = await umbracoApi.documentType.createDocumentTypeWithPropertyEditor(documentTypeName, customDataTypeName, customDataTypeId);
await umbracoApi.document.createDefaultDocument(contentName, documentTypeId);
await umbracoUi.goToBackOffice();
await umbracoUi.content.goToSection(ConstantHelper.sections.content);
await umbracoUi.content.goToContentWithName(contentName);
// Act
await umbracoUi.documentType.goToSection(ConstantHelper.sections.settings);
// Assert
// We do this to make sure that there is no discard changes button visible, if the discard changes was visible, we would not be able to go to the document type
await umbracoUi.documentType.goToDocumentType(documentTypeName);
});

View File

@@ -10,6 +10,7 @@ using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Migrations;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Scoping;
using Umbraco.Cms.Core.Services;
@@ -38,6 +39,7 @@ internal sealed class AdvancedMigrationTests : UmbracoIntegrationTest
private IServiceScopeFactory ServiceScopeFactory => GetRequiredService<IServiceScopeFactory>();
private DistributedCache DistributedCache => GetRequiredService<DistributedCache>();
private IDatabaseCacheRebuilder DatabaseCacheRebuilder => GetRequiredService<IDatabaseCacheRebuilder>();
private IPublishedContentTypeFactory PublishedContentTypeFactory => GetRequiredService<IPublishedContentTypeFactory>();
private IMigrationPlanExecutor MigrationPlanExecutor => new MigrationPlanExecutor(
CoreScopeProvider,
ScopeAccessor,
@@ -48,7 +50,8 @@ internal sealed class AdvancedMigrationTests : UmbracoIntegrationTest
DistributedCache,
Mock.Of<IKeyValueService>(),
ServiceScopeFactory,
AppCaches.NoCache);
AppCaches.NoCache,
PublishedContentTypeFactory);
[Test]
public async Task CreateTableOfTDtoAsync()

View File

@@ -110,15 +110,16 @@ public class RichTextPropertyEditorHelperTests
Assert.IsNull(value.Blocks);
}
[Test]
public void Can_Parse_Blocks_With_Both_Content_And_Settings()
[TestCase(Constants.PropertyEditors.Aliases.RichText)]
[TestCase("Umbraco.TinyMCE")]
public void Can_Parse_Blocks_With_Both_Content_And_Settings(string propertyEditorAlias)
{
const string input = """
string input = """
{
"markup": "<p>this is some markup</p><umb-rte-block data-content-key=\"36cc710a-d8a6-45d0-a07f-7bbd8742cf02\"><!--Umbraco-Block--></umb-rte-block>",
"blocks": {
"layout": {
"Umbraco.RichText": [{
"[PropertyEditorAlias]": [{
"contentKey": "36cc710a-d8a6-45d0-a07f-7bbd8742cf02",
"settingsKey": "d2eeef66-4111-42f4-a164-7a523eaffbc2"
}
@@ -143,6 +144,7 @@ public class RichTextPropertyEditorHelperTests
}
}
""";
input = input.Replace("[PropertyEditorAlias]", propertyEditorAlias);
var result = RichTextPropertyEditorHelper.TryParseRichTextEditorValue(input, JsonSerializer(), Logger(), out RichTextEditorValue? value);
Assert.IsTrue(result);
@@ -180,6 +182,12 @@ public class RichTextPropertyEditorHelperTests
Assert.AreEqual("settingsPropertyAlias", settingsProperties.First().Alias);
Assert.AreEqual("A settings property value", settingsProperties.First().Value);
});
Assert.IsTrue(value.Blocks.Layout.ContainsKey(Constants.PropertyEditors.Aliases.RichText));
var layout = value.Blocks.Layout[Constants.PropertyEditors.Aliases.RichText];
Assert.AreEqual(1, layout.Count());
Assert.AreEqual(Guid.Parse("36cc710a-d8a6-45d0-a07f-7bbd8742cf02"), layout.First().ContentKey);
Assert.AreEqual(Guid.Parse("d2eeef66-4111-42f4-a164-7a523eaffbc2"), layout.First().SettingsKey);
}
[Test]

View File

@@ -12,6 +12,7 @@ using NUnit.Framework;
using Umbraco.Cms.Core.Cache;
using Umbraco.Cms.Core.Configuration.Models;
using Umbraco.Cms.Core.Events;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PublishedCache;
using Umbraco.Cms.Core.Services;
using Umbraco.Cms.Core.Sync;
@@ -85,7 +86,8 @@ public class MigrationPlanTests
distributedCache,
Mock.Of<IKeyValueService>(),
Mock.Of<IServiceScopeFactory>(),
appCaches);
appCaches,
Mock.Of<IPublishedContentTypeFactory>());
var plan = new MigrationPlan("default")
.From(string.Empty)

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/main/src/NerdBank.GitVersioning/version.schema.json",
"version": "16.4.0-rc2",
"version": "16.4.0-rc3",
"assemblyVersion": {
"precision": "build"
},