Merge remote-tracking branch 'origin/v8/dev' into v8/feature/5577-trix-property-editor
This commit is contained in:
@@ -52,7 +52,7 @@
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Core.dll" target="lib\net472\Umbraco.Core.dll" />
|
||||
|
||||
<!-- docs -->
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Core.xml" target="lib\Umbraco.Core.xml" />
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Core.xml" target="lib\net472\Umbraco.Core.xml" />
|
||||
|
||||
<!-- symbols -->
|
||||
<file src="$BuildTmp$\bin\Umbraco.Core.pdb" target="lib" />
|
||||
|
||||
@@ -53,9 +53,9 @@
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Examine.dll" target="lib\net472\Umbraco.Examine.dll" />
|
||||
|
||||
<!-- docs -->
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.xml" target="lib\Umbraco.Web.xml" />
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.UI.xml" target="lib\Umbraco.Web.UI.xml" />
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Examine.xml" target="lib\Umbraco.Examine.xml" />
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.xml" target="lib\net472\Umbraco.Web.xml" />
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Web.UI.xml" target="lib\net472\Umbraco.Web.UI.xml" />
|
||||
<file src="$BuildTmp$\WebApp\bin\Umbraco.Examine.xml" target="lib\net472\Umbraco.Examine.xml" />
|
||||
|
||||
<!-- symbols -->
|
||||
<file src="$BuildTmp$\bin\Umbraco.Web.pdb" target="lib" />
|
||||
|
||||
@@ -389,7 +389,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
private DatabaseSchemaResult ValidateSchema(IScope scope)
|
||||
{
|
||||
if (_databaseFactory.Configured == false)
|
||||
if (_databaseFactory.Initialized == false)
|
||||
return new DatabaseSchemaResult(_databaseFactory.SqlContext.SqlSyntax);
|
||||
|
||||
if (_databaseSchemaValidationResult != null)
|
||||
@@ -513,7 +513,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
private Attempt<Result> CheckReadyForInstall()
|
||||
{
|
||||
if (_databaseFactory.Configured == false)
|
||||
if (_databaseFactory.CanConnect == false)
|
||||
{
|
||||
return Attempt.Fail(new Result
|
||||
{
|
||||
|
||||
@@ -10,22 +10,35 @@ namespace Umbraco.Core.Persistence
|
||||
/// <summary>
|
||||
/// Creates a new database.
|
||||
/// </summary>
|
||||
/// <remarks>The new database must be disposed after being used.</remarks>
|
||||
/// <remarks>
|
||||
/// <para>The new database must be disposed after being used.</para>
|
||||
/// <para>Creating a database causes the factory to initialize if it is not already initialized.</para>
|
||||
/// </remarks>
|
||||
IUmbracoDatabase CreateDatabase();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the database factory is configured.
|
||||
/// Gets a value indicating whether the database factory is configured, i.e. whether
|
||||
/// its connection string and provider name have been set. The factory may however not
|
||||
/// be initialized (see <see cref="Initialized"/>).
|
||||
/// </summary>
|
||||
bool Configured { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the database factory is initialized, i.e. whether
|
||||
/// its internal state is ready and it has been possible to connect to the database.
|
||||
/// </summary>
|
||||
bool Initialized { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the connection string.
|
||||
/// </summary>
|
||||
/// <remarks>Throws if the factory is not configured.</remarks>
|
||||
/// <remarks>May return <c>null</c> if the database factory is not configured.</remarks>
|
||||
string ConnectionString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the database can connect.
|
||||
/// Gets a value indicating whether the database factory is configured (see <see cref="Configured"/>),
|
||||
/// and it is possible to connect to the database. The factory may however not be initialized (see
|
||||
/// <see cref="Initialized"/>).
|
||||
/// </summary>
|
||||
bool CanConnect { get; }
|
||||
|
||||
@@ -37,6 +50,9 @@ namespace Umbraco.Core.Persistence
|
||||
/// <summary>
|
||||
/// Gets the Sql context.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Getting the Sql context causes the factory to initialize if it is not already initialized.</para>
|
||||
/// </remarks>
|
||||
ISqlContext SqlContext { get; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -232,6 +232,14 @@ namespace Umbraco.Core.Persistence
|
||||
using (var copy = new SqlBulkCopy(tConnection, SqlBulkCopyOptions.Default, tTransaction) { BulkCopyTimeout = 10000, DestinationTableName = tableName })
|
||||
using (var bulkReader = new PocoDataDataReader<T, SqlServerSyntaxProvider>(records, pocoData, syntax))
|
||||
{
|
||||
//we need to add column mappings here because otherwise columns will be matched by their order and if the order of them are different in the DB compared
|
||||
//to the order in which they are declared in the model then this will not work, so instead we will add column mappings by name so that this explicitly uses
|
||||
//the names instead of their ordering.
|
||||
foreach(var col in bulkReader.ColumnMappings)
|
||||
{
|
||||
copy.ColumnMappings.Add(col.DestinationColumn, col.DestinationColumn);
|
||||
}
|
||||
|
||||
copy.WriteToServer(bulkReader);
|
||||
return bulkReader.RecordsAffected;
|
||||
}
|
||||
|
||||
@@ -140,10 +140,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
while (templateDtoIx < templateDtos.Count && templateDtos[templateDtoIx].ContentTypeNodeId == contentType.Id)
|
||||
{
|
||||
var allowedDto = templateDtos[templateDtoIx];
|
||||
templateDtoIx++;
|
||||
if (!templates.TryGetValue(allowedDto.TemplateNodeId, out var template)) continue;
|
||||
allowedTemplates.Add(template);
|
||||
templateDtoIx++;
|
||||
|
||||
|
||||
if (allowedDto.IsDefault)
|
||||
defaultTemplateId = template.Id;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ namespace Umbraco.Core.Persistence
|
||||
{
|
||||
private readonly Lazy<IMapperCollection> _mappers;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
|
||||
|
||||
private object _lock = new object();
|
||||
|
||||
private DatabaseFactory _npocoDatabaseFactory;
|
||||
private IPocoDataFactory _pocoDataFactory;
|
||||
@@ -36,12 +37,13 @@ namespace Umbraco.Core.Persistence
|
||||
private string _providerName;
|
||||
private DbProviderFactory _dbProviderFactory;
|
||||
private DatabaseType _databaseType;
|
||||
private bool _serverVersionDetected;
|
||||
private ISqlSyntaxProvider _sqlSyntax;
|
||||
private RetryPolicy _connectionRetryPolicy;
|
||||
private RetryPolicy _commandRetryPolicy;
|
||||
private NPoco.MapperCollection _pocoMappers;
|
||||
private SqlContext _sqlContext;
|
||||
private bool _upgrading;
|
||||
private bool _initialized;
|
||||
|
||||
#region Constructors
|
||||
|
||||
@@ -106,36 +108,30 @@ namespace Umbraco.Core.Persistence
|
||||
#endregion
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Configured { get; private set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string ConnectionString
|
||||
public bool Configured
|
||||
{
|
||||
get
|
||||
{
|
||||
EnsureConfigured();
|
||||
return _connectionString;
|
||||
lock (_lock)
|
||||
{
|
||||
return !_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool CanConnect
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Configured || !DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName)) return false;
|
||||
public bool Initialized => Volatile.Read(ref _initialized);
|
||||
|
||||
if (_serverVersionDetected) return true;
|
||||
/// <inheritdoc />
|
||||
public string ConnectionString => _connectionString;
|
||||
|
||||
if (_databaseType.IsSqlServer())
|
||||
DetectSqlServerVersion();
|
||||
_serverVersionDetected = true;
|
||||
/// <inheritdoc />
|
||||
public bool CanConnect =>
|
||||
// actually tries to connect to the database (regardless of configured/initialized)
|
||||
!_connectionString.IsNullOrWhiteSpace() && !_providerName.IsNullOrWhiteSpace() &&
|
||||
DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private void DetectSqlServerVersion()
|
||||
private void UpdateSqlServerDatabaseType()
|
||||
{
|
||||
// replace NPoco database type by a more efficient one
|
||||
|
||||
@@ -171,7 +167,15 @@ namespace Umbraco.Core.Persistence
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ISqlContext SqlContext { get; private set; }
|
||||
public ISqlContext SqlContext
|
||||
{
|
||||
get
|
||||
{
|
||||
// must be initialized to have a context
|
||||
EnsureInitialized();
|
||||
return _sqlContext;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void ConfigureForUpgrade()
|
||||
@@ -182,63 +186,79 @@ namespace Umbraco.Core.Persistence
|
||||
/// <inheritdoc />
|
||||
public void Configure(string connectionString, string providerName)
|
||||
{
|
||||
try
|
||||
if (connectionString.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(connectionString));
|
||||
if (providerName.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(providerName));
|
||||
|
||||
lock (_lock)
|
||||
{
|
||||
_lock.EnterWriteLock();
|
||||
|
||||
_logger.Debug<UmbracoDatabaseFactory>("Configuring.");
|
||||
|
||||
if (Configured) throw new InvalidOperationException("Already configured.");
|
||||
|
||||
if (connectionString.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(connectionString));
|
||||
if (providerName.IsNullOrWhiteSpace()) throw new ArgumentNullException(nameof(providerName));
|
||||
if (Volatile.Read(ref _initialized))
|
||||
throw new InvalidOperationException("Already initialized.");
|
||||
|
||||
_connectionString = connectionString;
|
||||
_providerName = providerName;
|
||||
|
||||
_connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionString);
|
||||
_commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionString);
|
||||
|
||||
_dbProviderFactory = DbProviderFactories.GetFactory(_providerName);
|
||||
if (_dbProviderFactory == null)
|
||||
throw new Exception($"Can't find a provider factory for provider name \"{_providerName}\".");
|
||||
_databaseType = DatabaseType.Resolve(_dbProviderFactory.GetType().Name, _providerName);
|
||||
if (_databaseType == null)
|
||||
throw new Exception($"Can't find an NPoco database type for provider name \"{_providerName}\".");
|
||||
|
||||
_sqlSyntax = GetSqlSyntaxProvider(_providerName);
|
||||
if (_sqlSyntax == null)
|
||||
throw new Exception($"Can't find a sql syntax provider for provider name \"{_providerName}\".");
|
||||
|
||||
// ensure we have only 1 set of mappers, and 1 PocoDataFactory, for all database
|
||||
// so that everything NPoco is properly cached for the lifetime of the application
|
||||
_pocoMappers = new NPoco.MapperCollection { new PocoMapper() };
|
||||
var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver);
|
||||
_pocoDataFactory = factory;
|
||||
var config = new FluentConfig(xmappers => factory);
|
||||
|
||||
// create the database factory
|
||||
_npocoDatabaseFactory = DatabaseFactory.Config(x => x
|
||||
.UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances
|
||||
.WithFluentConfig(config)); // with proper configuration
|
||||
|
||||
if (_npocoDatabaseFactory == null) throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance.");
|
||||
|
||||
SqlContext = new SqlContext(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers);
|
||||
|
||||
_logger.Debug<UmbracoDatabaseFactory>("Configured.");
|
||||
Configured = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsWriteLockHeld)
|
||||
_lock.ExitWriteLock();
|
||||
}
|
||||
|
||||
// rest to be lazy-initialized
|
||||
}
|
||||
|
||||
private void EnsureInitialized()
|
||||
{
|
||||
LazyInitializer.EnsureInitialized(ref _sqlContext, ref _initialized, ref _lock, Initialize);
|
||||
}
|
||||
|
||||
private SqlContext Initialize()
|
||||
{
|
||||
_logger.Debug<UmbracoDatabaseFactory>("Initializing.");
|
||||
|
||||
if (_connectionString.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper connection string.");
|
||||
if (_providerName.IsNullOrWhiteSpace()) throw new InvalidOperationException("The factory has not been configured with a proper provider name.");
|
||||
|
||||
// cannot initialize without being able to talk to the database
|
||||
if (!DbConnectionExtensions.IsConnectionAvailable(_connectionString, _providerName))
|
||||
throw new Exception("Cannot connect to the database.");
|
||||
|
||||
_connectionRetryPolicy = RetryPolicyFactory.GetDefaultSqlConnectionRetryPolicyByConnectionString(_connectionString);
|
||||
_commandRetryPolicy = RetryPolicyFactory.GetDefaultSqlCommandRetryPolicyByConnectionString(_connectionString);
|
||||
|
||||
_dbProviderFactory = DbProviderFactories.GetFactory(_providerName);
|
||||
if (_dbProviderFactory == null)
|
||||
throw new Exception($"Can't find a provider factory for provider name \"{_providerName}\".");
|
||||
_databaseType = DatabaseType.Resolve(_dbProviderFactory.GetType().Name, _providerName);
|
||||
if (_databaseType == null)
|
||||
throw new Exception($"Can't find an NPoco database type for provider name \"{_providerName}\".");
|
||||
|
||||
_sqlSyntax = GetSqlSyntaxProvider(_providerName);
|
||||
if (_sqlSyntax == null)
|
||||
throw new Exception($"Can't find a sql syntax provider for provider name \"{_providerName}\".");
|
||||
|
||||
if (_databaseType.IsSqlServer())
|
||||
UpdateSqlServerDatabaseType();
|
||||
|
||||
// ensure we have only 1 set of mappers, and 1 PocoDataFactory, for all database
|
||||
// so that everything NPoco is properly cached for the lifetime of the application
|
||||
_pocoMappers = new NPoco.MapperCollection { new PocoMapper() };
|
||||
var factory = new FluentPocoDataFactory(GetPocoDataFactoryResolver);
|
||||
_pocoDataFactory = factory;
|
||||
var config = new FluentConfig(xmappers => factory);
|
||||
|
||||
// create the database factory
|
||||
_npocoDatabaseFactory = DatabaseFactory.Config(x => x
|
||||
.UsingDatabase(CreateDatabaseInstance) // creating UmbracoDatabase instances
|
||||
.WithFluentConfig(config)); // with proper configuration
|
||||
|
||||
if (_npocoDatabaseFactory == null)
|
||||
throw new NullReferenceException("The call to UmbracoDatabaseFactory.Config yielded a null UmbracoDatabaseFactory instance.");
|
||||
|
||||
_logger.Debug<UmbracoDatabaseFactory>("Initialized.");
|
||||
|
||||
return new SqlContext(_sqlSyntax, _databaseType, _pocoDataFactory, _mappers);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUmbracoDatabase CreateDatabase()
|
||||
{
|
||||
// must be initialized to create a database
|
||||
EnsureInitialized();
|
||||
return (IUmbracoDatabase) _npocoDatabaseFactory.GetDatabase();
|
||||
}
|
||||
|
||||
@@ -260,22 +280,6 @@ namespace Umbraco.Core.Persistence
|
||||
}
|
||||
}
|
||||
|
||||
// ensures that the database is configured, else throws
|
||||
private void EnsureConfigured()
|
||||
{
|
||||
_lock.EnterReadLock();
|
||||
try
|
||||
{
|
||||
if (Configured == false)
|
||||
throw new InvalidOperationException("Not configured.");
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (_lock.IsReadLockHeld)
|
||||
_lock.ExitReadLock();
|
||||
}
|
||||
}
|
||||
|
||||
// method used by NPoco's UmbracoDatabaseFactory to actually create the database instance
|
||||
private UmbracoDatabase CreateDatabaseInstance()
|
||||
{
|
||||
@@ -292,7 +296,7 @@ namespace Umbraco.Core.Persistence
|
||||
//var db = _umbracoDatabaseAccessor.UmbracoDatabase;
|
||||
//_umbracoDatabaseAccessor.UmbracoDatabase = null;
|
||||
//db?.Dispose();
|
||||
Configured = false;
|
||||
Volatile.Write(ref _initialized, false);
|
||||
}
|
||||
|
||||
// during tests, the thread static var can leak between tests
|
||||
|
||||
@@ -43,16 +43,6 @@ namespace Umbraco.Tests.Persistence
|
||||
_databaseFactory = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDatabaseType()
|
||||
{
|
||||
using (var database = _databaseFactory.CreateDatabase())
|
||||
{
|
||||
var databaseType = database.DatabaseType;
|
||||
Assert.AreEqual(DatabaseType.SQLCe, databaseType);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CreateDatabase() // FIXME: move to DatabaseBuilderTest!
|
||||
{
|
||||
@@ -79,6 +69,13 @@ namespace Umbraco.Tests.Persistence
|
||||
// re-create the database factory and database context with proper connection string
|
||||
_databaseFactory = new UmbracoDatabaseFactory(connString, Constants.DbProviderNames.SqlCe, _logger, new Lazy<IMapperCollection>(() => Mock.Of<IMapperCollection>()));
|
||||
|
||||
// test get database type (requires an actual database)
|
||||
using (var database = _databaseFactory.CreateDatabase())
|
||||
{
|
||||
var databaseType = database.DatabaseType;
|
||||
Assert.AreEqual(DatabaseType.SQLCe, databaseType);
|
||||
}
|
||||
|
||||
// create application context
|
||||
//var appCtx = new ApplicationContext(
|
||||
// _databaseFactory,
|
||||
|
||||
+72
-4
@@ -1,10 +1,12 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function EditorContentHeader() {
|
||||
function EditorContentHeader(serverValidationManager) {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
if (!scope.serverValidationNameField) {
|
||||
scope.serverValidationNameField = "Name";
|
||||
}
|
||||
@@ -15,9 +17,44 @@
|
||||
scope.vm = {};
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.vm.currentVariant = "";
|
||||
|
||||
scope.vm.variantsWithError = [];
|
||||
scope.vm.defaultVariant = null;
|
||||
|
||||
scope.vm.errorsOnOtherVariants = false;// indicating wether to show that other variants, than the current, have errors.
|
||||
|
||||
function checkErrorsOnOtherVariants() {
|
||||
var check = false;
|
||||
angular.forEach(scope.content.variants, function (variant) {
|
||||
if (scope.openVariants.indexOf(variant.language.culture) === -1 && scope.variantHasError(variant.language.culture)) {
|
||||
check = true;
|
||||
}
|
||||
});
|
||||
scope.vm.errorsOnOtherVariants = check;
|
||||
}
|
||||
|
||||
function onCultureValidation(valid, errors, allErrors, culture) {
|
||||
var index = scope.vm.variantsWithError.indexOf(culture);
|
||||
if(valid === true) {
|
||||
if (index !== -1) {
|
||||
scope.vm.variantsWithError.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
if (index === -1) {
|
||||
scope.vm.variantsWithError.push(culture);
|
||||
}
|
||||
}
|
||||
checkErrorsOnOtherVariants();
|
||||
}
|
||||
|
||||
function onInit() {
|
||||
|
||||
// find default.
|
||||
angular.forEach(scope.content.variants, function (variant) {
|
||||
if (variant.language.isDefault) {
|
||||
scope.vm.defaultVariant = variant;
|
||||
}
|
||||
});
|
||||
|
||||
setCurrentVariant();
|
||||
|
||||
angular.forEach(scope.content.apps, (app) => {
|
||||
@@ -26,12 +63,22 @@
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
angular.forEach(scope.content.variants, function (variant) {
|
||||
unsubscribe.push(serverValidationManager.subscribe(null, variant.language.culture, null, onCultureValidation));
|
||||
});
|
||||
|
||||
unsubscribe.push(serverValidationManager.subscribe(null, null, null, onCultureValidation));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function setCurrentVariant() {
|
||||
angular.forEach(scope.content.variants, function (variant) {
|
||||
if (variant.active) {
|
||||
scope.vm.currentVariant = variant;
|
||||
checkErrorsOnOtherVariants();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -80,9 +127,24 @@
|
||||
* @param {any} culture
|
||||
*/
|
||||
scope.variantIsOpen = function(culture) {
|
||||
if(scope.openVariants.indexOf(culture) !== -1) {
|
||||
return (scope.openVariants.indexOf(culture) !== -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a variant has a error, used to display errors in variant switcher.
|
||||
* @param {any} culture
|
||||
*/
|
||||
scope.variantHasError = function(culture) {
|
||||
// if we are looking for the default language we also want to check for invariant.
|
||||
if (culture === scope.vm.defaultVariant.language.culture) {
|
||||
if(scope.vm.variantsWithError.indexOf("invariant") !== -1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if(scope.vm.variantsWithError.indexOf(culture) !== -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onInit();
|
||||
@@ -103,6 +165,12 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scope.$on('$destroy', function () {
|
||||
for (var u in unsubscribe) {
|
||||
unsubscribe[u]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
+7
-1
@@ -101,8 +101,14 @@ angular.module('umbraco.directives')
|
||||
var eventBindings = [];
|
||||
|
||||
function oneTimeClick(event) {
|
||||
var el = event.target.nodeName;
|
||||
|
||||
//ignore link and button clicks
|
||||
var els = ["INPUT","A","BUTTON"];
|
||||
if(els.indexOf(el) >= 0){return;}
|
||||
|
||||
// ignore clicks on new overlay
|
||||
var parents = $(event.target).parents(".umb-overlay,.umb-tour");
|
||||
var parents = $(event.target).parents("a,button,.umb-overlay,.umb-tour");
|
||||
if(parents.length > 0){
|
||||
return;
|
||||
}
|
||||
|
||||
+1
@@ -505,6 +505,7 @@
|
||||
property.showOnMemberProfile = propertyModel.showOnMemberProfile;
|
||||
property.memberCanEdit = propertyModel.memberCanEdit;
|
||||
property.isSensitiveValue = propertyModel.isSensitiveValue;
|
||||
property.allowCultureVariant = propertyModel.allowCultureVariant;
|
||||
|
||||
// update existing data types
|
||||
if(model.updateSameDataTypes) {
|
||||
|
||||
+29
-13
@@ -12,27 +12,50 @@
|
||||
function valPropertyMsg(serverValidationManager) {
|
||||
|
||||
return {
|
||||
require: ['^^form', '^^valFormManager', '^^umbProperty'],
|
||||
require: ['^^form', '^^valFormManager', '^^umbProperty', '?^^umbVariantContent'],
|
||||
replace: true,
|
||||
restrict: "E",
|
||||
template: "<div ng-show=\"errorMsg != ''\" class='alert alert-error property-error' >{{errorMsg}}</div>",
|
||||
scope: {},
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
|
||||
var unsubscribe = [];
|
||||
var watcher = null;
|
||||
var hasError = false;
|
||||
|
||||
//create properties on our custom scope so we can use it in our template
|
||||
scope.errorMsg = "";
|
||||
|
||||
|
||||
//the property form controller api
|
||||
var formCtrl = ctrl[0];
|
||||
//the valFormManager controller api
|
||||
var valFormManager = ctrl[1];
|
||||
//the property controller api
|
||||
var umbPropCtrl = ctrl[2];
|
||||
//the variants controller api
|
||||
var umbVariantCtrl = ctrl[3];
|
||||
|
||||
scope.currentProperty = umbPropCtrl.property;
|
||||
var currentProperty = umbPropCtrl.property;
|
||||
scope.currentProperty = currentProperty;
|
||||
var currentCulture = currentProperty.culture;
|
||||
|
||||
//if the property is invariant (no culture), then we will explicitly set it to the string 'invariant'
|
||||
//since this matches the value that the server will return for an invariant property.
|
||||
var currentCulture = scope.currentProperty.culture || "invariant";
|
||||
if (umbVariantCtrl) {
|
||||
//if we are inside of an umbVariantContent directive
|
||||
|
||||
var watcher = null;
|
||||
var currentVariant = umbVariantCtrl.editor.content;
|
||||
|
||||
// Lets check if we have variants and we are on the default language then ...
|
||||
if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) {
|
||||
//This property is locked cause its a invariant property shown on a non-default language.
|
||||
//Therefor do not validate this field.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language.
|
||||
currentCulture = currentCulture || "invariant";
|
||||
|
||||
|
||||
// Gets the error message to display
|
||||
function getErrorMsg() {
|
||||
@@ -138,13 +161,6 @@ function valPropertyMsg(serverValidationManager) {
|
||||
}
|
||||
|
||||
|
||||
var hasError = false;
|
||||
|
||||
//create properties on our custom scope so we can use it in our template
|
||||
scope.errorMsg = "";
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
//listen for form validation changes.
|
||||
//The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then
|
||||
// subscribing to this single watch.
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
**/
|
||||
function valServer(serverValidationManager) {
|
||||
return {
|
||||
require: ['ngModel', '?^^umbProperty'],
|
||||
require: ['ngModel', '?^^umbProperty', '?^^umbVariantContent'],
|
||||
restrict: "A",
|
||||
scope: {},
|
||||
link: function (scope, element, attr, ctrls) {
|
||||
@@ -18,9 +18,29 @@ function valServer(serverValidationManager) {
|
||||
//we cannot proceed, this validator will be disabled
|
||||
return;
|
||||
}
|
||||
|
||||
// optional reference to the varaint-content-controller, needed to avoid validation when the field is invariant on non-default languages.
|
||||
var umbVariantCtrl = ctrls.length > 2 ? ctrls[2] : null;
|
||||
|
||||
var currentProperty = umbPropCtrl.property;
|
||||
var currentCulture = currentProperty.culture;
|
||||
|
||||
if (umbVariantCtrl) {
|
||||
//if we are inside of an umbVariantContent directive
|
||||
|
||||
var currentVariant = umbVariantCtrl.editor.content;
|
||||
|
||||
// Lets check if we have variants and we are on the default language then ...
|
||||
if (umbVariantCtrl.content.variants.length > 1 && !currentVariant.language.isDefault && !currentCulture && !currentProperty.unlockInvariantValue) {
|
||||
//This property is locked cause its a invariant property shown on a non-default language.
|
||||
//Therefor do not validate this field.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we have reached this part, and there is no culture, then lets fallback to invariant. To get the validation feedback for invariant language.
|
||||
currentCulture = currentCulture || "invariant";
|
||||
|
||||
var watcher = null;
|
||||
var unsubscribe = [];
|
||||
|
||||
|
||||
@@ -184,8 +184,7 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
|
||||
serverValidationManager.addPropertyError(propertyAlias, culture, "", modelState[e][0]);
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
|
||||
//Everthing else is just a 'Field'... the field name could contain any level of 'parts' though, for example:
|
||||
// Groups[0].Properties[2].Alias
|
||||
|
||||
@@ -13,12 +13,15 @@ function serverValidationManager($timeout) {
|
||||
var callbacks = [];
|
||||
|
||||
/** calls the callback specified with the errors specified, used internally */
|
||||
function executeCallback(self, errorsForCallback, callback) {
|
||||
function executeCallback(self, errorsForCallback, callback, culture) {
|
||||
|
||||
callback.apply(self, [
|
||||
false, //pass in a value indicating it is invalid
|
||||
errorsForCallback, //pass in the errors for this item
|
||||
self.items]); //pass in all errors in total
|
||||
false, // pass in a value indicating it is invalid
|
||||
errorsForCallback, // pass in the errors for this item
|
||||
self.items, // pass in all errors in total
|
||||
culture // pass the culture that we are listing for.
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
function getFieldErrors(self, fieldName) {
|
||||
@@ -28,7 +31,7 @@ function serverValidationManager($timeout) {
|
||||
|
||||
//find errors for this field name
|
||||
return _.filter(self.items, function (item) {
|
||||
return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName);
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -39,27 +42,50 @@ function serverValidationManager($timeout) {
|
||||
if (fieldName && !angular.isString(fieldName)) {
|
||||
throw "fieldName must be a string";
|
||||
}
|
||||
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
//find all errors for this property
|
||||
return _.filter(self.items, function (item) {
|
||||
return _.filter(self.items, function (item) {
|
||||
return (item.propertyAlias === propertyAlias && item.culture === culture && (item.fieldName === fieldName || (fieldName === undefined || fieldName === "")));
|
||||
});
|
||||
}
|
||||
|
||||
function getCultureErrors(self, culture) {
|
||||
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
//find all errors for this property
|
||||
return _.filter(self.items, function (item) {
|
||||
return (item.culture === culture);
|
||||
});
|
||||
}
|
||||
|
||||
function notifyCallbacks(self) {
|
||||
for (var cb in callbacks) {
|
||||
if (callbacks[cb].propertyAlias === null) {
|
||||
if (callbacks[cb].propertyAlias === null && callbacks[cb].fieldName !== null) {
|
||||
//its a field error callback
|
||||
var fieldErrors = getFieldErrors(self, callbacks[cb].fieldName);
|
||||
if (fieldErrors.length > 0) {
|
||||
executeCallback(self, fieldErrors, callbacks[cb].callback);
|
||||
executeCallback(self, fieldErrors, callbacks[cb].callback, callbacks[cb].culture);
|
||||
}
|
||||
}
|
||||
else {
|
||||
else if (callbacks[cb].propertyAlias != null) {
|
||||
//its a property error
|
||||
var propErrors = getPropertyErrors(self, callbacks[cb].propertyAlias, callbacks[cb].culture, callbacks[cb].fieldName);
|
||||
if (propErrors.length > 0) {
|
||||
executeCallback(self, propErrors, callbacks[cb].callback);
|
||||
executeCallback(self, propErrors, callbacks[cb].callback, callbacks[cb].culture);
|
||||
}
|
||||
}
|
||||
else {
|
||||
//its a culture error
|
||||
var cultureErrors = getCultureErrors(self, callbacks[cb].culture);
|
||||
if (cultureErrors.length > 0) {
|
||||
executeCallback(self, cultureErrors, callbacks[cb].callback, callbacks[cb].culture);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,11 +156,14 @@ function serverValidationManager($timeout) {
|
||||
}
|
||||
|
||||
var id = String.CreateGuid();
|
||||
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
if (propertyAlias === null) {
|
||||
callbacks.push({
|
||||
propertyAlias: null,
|
||||
culture: null,
|
||||
culture: culture,
|
||||
fieldName: fieldName,
|
||||
callback: callback,
|
||||
id: id
|
||||
@@ -142,12 +171,11 @@ function serverValidationManager($timeout) {
|
||||
}
|
||||
else if (propertyAlias !== undefined) {
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = null;
|
||||
}
|
||||
|
||||
callbacks.push({
|
||||
propertyAlias: propertyAlias,
|
||||
culture: culture, fieldName: fieldName,
|
||||
culture: culture,
|
||||
fieldName: fieldName,
|
||||
callback: callback,
|
||||
id: id
|
||||
});
|
||||
@@ -173,21 +201,20 @@ function serverValidationManager($timeout) {
|
||||
*/
|
||||
unsubscribe: function (propertyAlias, culture, fieldName) {
|
||||
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
if (propertyAlias === null) {
|
||||
|
||||
//remove all callbacks for the content field
|
||||
callbacks = _.reject(callbacks, function (item) {
|
||||
return item.propertyAlias === null && item.culture === null && item.fieldName === fieldName;
|
||||
return item.propertyAlias === null && item.culture === culture && item.fieldName === fieldName;
|
||||
});
|
||||
|
||||
}
|
||||
else if (propertyAlias !== undefined) {
|
||||
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = null;
|
||||
}
|
||||
|
||||
//remove all callbacks for the content property
|
||||
callbacks = _.reject(callbacks, function (item) {
|
||||
return item.propertyAlias === propertyAlias && item.culture === culture &&
|
||||
@@ -213,7 +240,7 @@ function serverValidationManager($timeout) {
|
||||
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = null;
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
@@ -235,7 +262,24 @@ function serverValidationManager($timeout) {
|
||||
getFieldCallbacks: function (fieldName) {
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly against the field
|
||||
return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName);
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
|
||||
});
|
||||
return found;
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name getCultureCallbacks
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Gets all callbacks that has been registered using the subscribe method for the culture.
|
||||
*/
|
||||
getCultureCallbacks: function (culture) {
|
||||
var found = _.filter(callbacks, function (item) {
|
||||
//returns any callback that have been registered directly/ONLY against the culture
|
||||
return (item.culture === culture && item.propertyAlias === null && item.fieldName === null);
|
||||
});
|
||||
return found;
|
||||
},
|
||||
@@ -258,7 +302,7 @@ function serverValidationManager($timeout) {
|
||||
if (!this.hasFieldError(fieldName)) {
|
||||
this.items.push({
|
||||
propertyAlias: null,
|
||||
culture: null,
|
||||
culture: "invariant",
|
||||
fieldName: fieldName,
|
||||
errorMsg: errorMsg
|
||||
});
|
||||
@@ -270,7 +314,7 @@ function serverValidationManager($timeout) {
|
||||
var cbs = this.getFieldCallbacks(fieldName);
|
||||
//call each callback for this error
|
||||
for (var cb in cbs) {
|
||||
executeCallback(this, errorsForCallback, cbs[cb].callback);
|
||||
executeCallback(this, errorsForCallback, cbs[cb].callback, null);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -288,9 +332,9 @@ function serverValidationManager($timeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
//normalize culture to null
|
||||
//normalize culture to "invariant"
|
||||
if (!culture) {
|
||||
culture = null;
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
//only add the item if it doesn't exist
|
||||
@@ -309,9 +353,16 @@ function serverValidationManager($timeout) {
|
||||
var cbs = this.getPropertyCallbacks(propertyAlias, culture, fieldName);
|
||||
//call each callback for this error
|
||||
for (var cb in cbs) {
|
||||
executeCallback(this, errorsForCallback, cbs[cb].callback);
|
||||
executeCallback(this, errorsForCallback, cbs[cb].callback, culture);
|
||||
}
|
||||
},
|
||||
|
||||
//execute culture specific callbacks here too when a propery error is added
|
||||
var cultureCbs = this.getCultureCallbacks(culture);
|
||||
//call each callback for this error
|
||||
for (var cb in cultureCbs) {
|
||||
executeCallback(this, errorsForCallback, cultureCbs[cb].callback, culture);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
@@ -330,7 +381,7 @@ function serverValidationManager($timeout) {
|
||||
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = null;
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
//remove the item
|
||||
@@ -384,7 +435,7 @@ function serverValidationManager($timeout) {
|
||||
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = null;
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
var err = _.find(this.items, function (item) {
|
||||
@@ -406,7 +457,7 @@ function serverValidationManager($timeout) {
|
||||
getFieldError: function (fieldName) {
|
||||
var err = _.find(this.items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName);
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
|
||||
});
|
||||
return err;
|
||||
},
|
||||
@@ -424,7 +475,7 @@ function serverValidationManager($timeout) {
|
||||
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = null;
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
var err = _.find(this.items, function (item) {
|
||||
@@ -446,11 +497,33 @@ function serverValidationManager($timeout) {
|
||||
hasFieldError: function (fieldName) {
|
||||
var err = _.find(this.items, function (item) {
|
||||
//return true if the property alias matches and if an empty field name is specified or the field name matches
|
||||
return (item.propertyAlias === null && item.culture === null && item.fieldName === fieldName);
|
||||
return (item.propertyAlias === null && item.culture === "invariant" && item.fieldName === fieldName);
|
||||
});
|
||||
return err ? true : false;
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name hasCultureError
|
||||
* @methodOf umbraco.services.serverValidationManager
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Checks if the given culture has an error
|
||||
*/
|
||||
hasCultureError: function (culture) {
|
||||
|
||||
//normalize culture to null
|
||||
if (!culture) {
|
||||
culture = "invariant";
|
||||
}
|
||||
|
||||
var err = _.find(this.items, function (item) {
|
||||
return item.culture === culture;
|
||||
});
|
||||
return err ? true : false;
|
||||
},
|
||||
/** The array of error messages */
|
||||
items: []
|
||||
};
|
||||
|
||||
@@ -174,6 +174,8 @@ a.umb-editor-header__close-split-view:hover {
|
||||
max-width: 50%;
|
||||
white-space: nowrap;
|
||||
|
||||
user-select: none;
|
||||
|
||||
span {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
@@ -189,6 +191,25 @@ a.umb-variant-switcher__toggle {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&.--error {
|
||||
&::before {
|
||||
content: '!';
|
||||
position: absolute;
|
||||
top: -8px;
|
||||
right: -10px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
background-color: @errorBackground;
|
||||
color: @errorText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.umb-variant-switcher__expand {
|
||||
@@ -205,20 +226,16 @@ a.umb-variant-switcher__toggle {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid @gray-9;
|
||||
position: relative;
|
||||
&:hover .umb-variant-switcher__name-wrapper {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.umb-variant-switcher__item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.umb-variant-switcher_item--current {
|
||||
.umb-variant-switcher__item.--current {
|
||||
color: @ui-light-active-type;
|
||||
}
|
||||
.umb-variant-switcher_item--current .umb-variant-switcher__name-wrapper {
|
||||
//background-color: @gray-10;
|
||||
.umb-variant-switcher__item.--current .umb-variant-switcher__name-wrapper {
|
||||
border-left: 4px solid @ui-active;
|
||||
}
|
||||
|
||||
@@ -227,7 +244,7 @@ a.umb-variant-switcher__toggle {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.umb-variant-switcher_item--not-allowed:not(.umb-variant-switcher_item--current) .umb-variant-switcher__name-wrapper:hover {
|
||||
.umb-variant-switcher__item.--not-allowed:not(.--current) .umb-variant-switcher__name-wrapper:hover {
|
||||
//background-color: @white !important;
|
||||
cursor: default;
|
||||
}
|
||||
@@ -237,6 +254,29 @@ a.umb-variant-switcher__toggle {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.umb-variant-switcher__item.--error {
|
||||
.umb-variant-switcher__name {
|
||||
color: @red;
|
||||
&::after {
|
||||
content: '!';
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 5px;
|
||||
top: -6px;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border-radius: 7px;
|
||||
font-size: 8px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
background-color: @errorBackground;
|
||||
color: @errorText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.umb-variant-switcher__name-wrapper {
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@
|
||||
autocomplete="off" maxlength="255" />
|
||||
</ng-form>
|
||||
|
||||
<a ng-if="content.variants.length > 0 && hideChangeVariant !== true" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen">
|
||||
<a ng-if="content.variants.length > 0 && hideChangeVariant !== true" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen" ng-class="{'--error': vm.errorsOnOtherVariants}">
|
||||
<span>{{vm.currentVariant.language.name}}</span>
|
||||
<ins class="umb-variant-switcher__expand" ng-class="{'icon-navigation-down': !vm.dropdownOpen, 'icon-navigation-up': vm.dropdownOpen}"> </ins>
|
||||
</a>
|
||||
@@ -39,7 +39,7 @@
|
||||
</span>
|
||||
|
||||
<umb-dropdown ng-if="vm.dropdownOpen" style="min-width: 100%; max-height: 250px; overflow-y: auto; margin-top: 5px;" on-close="vm.dropdownOpen = false" umb-keyboard-list>
|
||||
<umb-dropdown-item class="umb-variant-switcher__item" ng-class="{'umb-variant-switcher_item--current': variant.active, 'umb-variant-switcher_item--not-allowed': variantIsOpen(variant.language.culture)}" ng-repeat="variant in content.variants">
|
||||
<umb-dropdown-item class="umb-variant-switcher__item" ng-class="{'--current': variant.active, '--not-allowed': variantIsOpen(variant.language.culture), '--error': variantHasError(variant.language.culture)}" ng-repeat="variant in content.variants">
|
||||
<a href="" class="umb-variant-switcher__name-wrapper" ng-click="selectVariant($event, variant)" prevent-default>
|
||||
<span class="umb-variant-switcher__name" ng-class="{'bold': variant.language.isDefault}">{{variant.language.name}}</span>
|
||||
<umb-variant-state variant="variant" class="umb-variant-switcher__state"></umb-variant-state>
|
||||
|
||||
@@ -20,6 +20,13 @@ angular.module("umbraco")
|
||||
editorConfig.maxImageSize = tinyMceService.defaultPrevalues().maxImageSize;
|
||||
}
|
||||
|
||||
var width = editorConfig.dimensions ? parseInt(editorConfig.dimensions.width, 10) || null : null;
|
||||
var height = editorConfig.dimensions ? parseInt(editorConfig.dimensions.height, 10) || null : null;
|
||||
|
||||
$scope.containerWidth = editorConfig.mode === "distraction-free" ? (width ? width : "auto") : "auto";
|
||||
$scope.containerHeight = editorConfig.mode === "distraction-free" ? (height ? height : "auto") : "auto";
|
||||
$scope.containerOverflow = editorConfig.mode === "distraction-free" ? (height ? "auto" : "inherit") : "inherit";
|
||||
|
||||
var promises = [];
|
||||
|
||||
//queue file loading
|
||||
@@ -41,10 +48,16 @@ angular.module("umbraco")
|
||||
$q.all(promises).then(function (result) {
|
||||
|
||||
var standardConfig = result[promises.length - 1];
|
||||
|
||||
|
||||
if (height !== null) {
|
||||
standardConfig.plugins.splice(standardConfig.plugins.indexOf("autoresize"), 1);
|
||||
}
|
||||
|
||||
//create a baseline Config to extend upon
|
||||
var baseLineConfigObj = {
|
||||
maxImageSize: editorConfig.maxImageSize
|
||||
maxImageSize: editorConfig.maxImageSize,
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
|
||||
angular.extend(baseLineConfigObj, standardConfig);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div ng-controller="Umbraco.PropertyEditors.RTEController" class="umb-property-editor umb-rte">
|
||||
<umb-load-indicator ng-if="isLoading"></umb-load-indicator>
|
||||
|
||||
<div ng-style="{ visibility : isLoading ? 'hidden' : 'visible'}"
|
||||
<div ng-style="{ visibility : isLoading ? 'hidden' : 'visible', width :containerWidth, height: containerHeight, overflow: containerOverflow}"
|
||||
id="{{textAreaHtmlId}}" class="umb-rte-editor"></div>
|
||||
</div>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
expect(err.propertyAlias).toBeNull();
|
||||
expect(err.fieldName).toEqual("Name");
|
||||
expect(err.errorMsg).toEqual("Required");
|
||||
expect(err.culture).toEqual("invariant");
|
||||
});
|
||||
|
||||
it('will return null for a non-existing field error', function () {
|
||||
@@ -72,11 +73,43 @@
|
||||
expect(err1.propertyAlias).toEqual("myProperty");
|
||||
expect(err1.fieldName).toEqual("value1");
|
||||
expect(err1.errorMsg).toEqual("Some value 1");
|
||||
expect(err1.culture).toEqual("invariant");
|
||||
|
||||
expect(err2).not.toBeUndefined();
|
||||
expect(err2.propertyAlias).toEqual("myProperty");
|
||||
expect(err2.fieldName).toEqual("value2");
|
||||
expect(err2.errorMsg).toEqual("Another value 2");
|
||||
expect(err2.culture).toEqual("invariant");
|
||||
});
|
||||
|
||||
it('can retrieve property validation errors for a sub field for culture', function () {
|
||||
|
||||
//arrange
|
||||
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 2");
|
||||
|
||||
//act
|
||||
var err1 = serverValidationManager.getPropertyError("myProperty", "en-US", "value1");
|
||||
var err1NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2");
|
||||
var err2 = serverValidationManager.getPropertyError("myProperty", "fr-FR", "value2");
|
||||
var err2NotFound = serverValidationManager.getPropertyError("myProperty", null, "value2");
|
||||
|
||||
|
||||
//assert
|
||||
expect(err1NotFound).toBeUndefined();
|
||||
expect(err2NotFound).toBeUndefined();
|
||||
|
||||
expect(err1).not.toBeUndefined();
|
||||
expect(err1.propertyAlias).toEqual("myProperty");
|
||||
expect(err1.fieldName).toEqual("value1");
|
||||
expect(err1.errorMsg).toEqual("Some value 1");
|
||||
expect(err1.culture).toEqual("en-US");
|
||||
|
||||
expect(err2).not.toBeUndefined();
|
||||
expect(err2.propertyAlias).toEqual("myProperty");
|
||||
expect(err2.fieldName).toEqual("value2");
|
||||
expect(err2.errorMsg).toEqual("Another value 2");
|
||||
expect(err2.culture).toEqual("fr-FR");
|
||||
});
|
||||
|
||||
it('can add a property errors with multiple sub fields and it the first will be retreived with only the property alias', function () {
|
||||
@@ -93,6 +126,7 @@
|
||||
expect(err.propertyAlias).toEqual("myProperty");
|
||||
expect(err.fieldName).toEqual("value1");
|
||||
expect(err.errorMsg).toEqual("Some value 1");
|
||||
expect(err.culture).toEqual("invariant");
|
||||
});
|
||||
|
||||
it('will return null for a non-existing property error', function () {
|
||||
@@ -162,6 +196,26 @@
|
||||
|
||||
});
|
||||
|
||||
describe('managing culture validation errors', function () {
|
||||
|
||||
it('can retrieve culture validation errors', function () {
|
||||
|
||||
//arrange
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value2", "Another value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", "fr-FR", "value2", "Another value 3");
|
||||
|
||||
//assert
|
||||
expect(serverValidationManager.hasCultureError(null)).toBe(true);
|
||||
expect(serverValidationManager.hasCultureError("en-US")).toBe(true);
|
||||
expect(serverValidationManager.hasCultureError("fr-FR")).toBe(true);
|
||||
expect(serverValidationManager.hasCultureError("es-ES")).toBe(false);
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('validation error subscriptions', function() {
|
||||
|
||||
it('can subscribe to a field error', function() {
|
||||
@@ -267,6 +321,45 @@
|
||||
// if the property has errors existing.
|
||||
expect(numCalled).toEqual(3);
|
||||
});
|
||||
|
||||
it('can subscribe to a culture error for both a property and its sub field', function () {
|
||||
var args1;
|
||||
var args2;
|
||||
var numCalled = 0;
|
||||
|
||||
//arrange
|
||||
serverValidationManager.subscribe(null, "en-US", null, function (isValid, propertyErrors, allErrors) {
|
||||
numCalled++;
|
||||
args1 = {
|
||||
isValid: isValid,
|
||||
propertyErrors: propertyErrors,
|
||||
allErrors: allErrors
|
||||
};
|
||||
});
|
||||
|
||||
serverValidationManager.subscribe(null, "es-ES", null, function (isValid, propertyErrors, allErrors) {
|
||||
numCalled++;
|
||||
args2 = {
|
||||
isValid: isValid,
|
||||
propertyErrors: propertyErrors,
|
||||
allErrors: allErrors
|
||||
};
|
||||
});
|
||||
|
||||
//act
|
||||
serverValidationManager.addPropertyError("myProperty", null, "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "en-US", "value1", "Some value 1");
|
||||
serverValidationManager.addPropertyError("myProperty", "en-US", "value2", "Some value 2");
|
||||
serverValidationManager.addPropertyError("myProperty", "fr-FR", "", "Some value 3");
|
||||
|
||||
//assert
|
||||
expect(args1).not.toBeUndefined();
|
||||
expect(args1.isValid).toBe(false);
|
||||
|
||||
expect(args2).toBeUndefined();
|
||||
|
||||
expect(numCalled).toEqual(2);
|
||||
});
|
||||
|
||||
// TODO: Finish testing the rest!
|
||||
|
||||
|
||||
@@ -304,9 +304,12 @@ namespace Umbraco.Web.Editors
|
||||
//check if the type is trying to allow type 0 below itself - id zero refers to the currently unsaved type
|
||||
//always filter these 0 types out
|
||||
var allowItselfAsChild = false;
|
||||
var allowIfselfAsChildSortOrder = -1;
|
||||
if (contentTypeSave.AllowedContentTypes != null)
|
||||
{
|
||||
allowIfselfAsChildSortOrder = contentTypeSave.AllowedContentTypes.IndexOf(0);
|
||||
allowItselfAsChild = contentTypeSave.AllowedContentTypes.Any(x => x == 0);
|
||||
|
||||
contentTypeSave.AllowedContentTypes = contentTypeSave.AllowedContentTypes.Where(x => x > 0).ToList();
|
||||
}
|
||||
|
||||
@@ -335,10 +338,12 @@ namespace Umbraco.Web.Editors
|
||||
saveContentType(newCt);
|
||||
|
||||
//we need to save it twice to allow itself under itself.
|
||||
if (allowItselfAsChild)
|
||||
if (allowItselfAsChild && newCt != null)
|
||||
{
|
||||
//NOTE: This will throw if the composition isn't right... but it shouldn't be at this stage
|
||||
newCt.AddContentType(newCt);
|
||||
newCt.AllowedContentTypes =
|
||||
newCt.AllowedContentTypes.Union(
|
||||
new []{ new ContentTypeSort(newCt.Id, allowIfselfAsChildSortOrder) }
|
||||
);
|
||||
saveContentType(newCt);
|
||||
}
|
||||
return newCt;
|
||||
|
||||
@@ -148,11 +148,33 @@ namespace Umbraco.Web
|
||||
var delimitedParts = string.Join(".", parts);
|
||||
foreach (var memberName in result.MemberNames)
|
||||
{
|
||||
modelState.AddModelError($"{delimitedParts}.{memberName}", result.ErrorMessage);
|
||||
modelState.TryAddModelError($"{delimitedParts}.{memberName}", result.ErrorMessage);
|
||||
withNames = true;
|
||||
}
|
||||
if (!withNames)
|
||||
modelState.AddModelError($"{delimitedParts}", result.ErrorMessage);
|
||||
{
|
||||
modelState.TryAddModelError($"{delimitedParts}", result.ErrorMessage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Will add an error to model state for a key if that key and error don't already exist
|
||||
/// </summary>
|
||||
/// <param name="modelState"></param>
|
||||
/// <param name="key"></param>
|
||||
/// <param name="errorMsg"></param>
|
||||
/// <returns></returns>
|
||||
private static bool TryAddModelError(this System.Web.Http.ModelBinding.ModelStateDictionary modelState, string key, string errorMsg)
|
||||
{
|
||||
if (modelState.TryGetValue(key, out var errs))
|
||||
{
|
||||
foreach(var e in errs.Errors)
|
||||
if (e.ErrorMessage == errorMsg) return false; //if this same error message exists for the same key, just exit
|
||||
}
|
||||
|
||||
modelState.AddModelError(key, errorMsg);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IDictionary<string, object> ToErrorDictionary(this System.Web.Http.ModelBinding.ModelStateDictionary modelState)
|
||||
|
||||
@@ -8,9 +8,9 @@ namespace Umbraco.Web.Routing
|
||||
/// <summary>
|
||||
/// Tries to find and assign an Umbraco document to a <c>PublishedRequest</c>.
|
||||
/// </summary>
|
||||
/// <param name="frequest">The <c>PublishedRequest</c>.</param>
|
||||
/// <param name="request">The <c>PublishedRequest</c>.</param>
|
||||
/// <returns>A value indicating whether an Umbraco document was found and assigned.</returns>
|
||||
/// <remarks>Optionally, can also assign the template or anything else on the document request, although that is not required.</remarks>
|
||||
bool TryFindContent(PublishedRequest frequest);
|
||||
bool TryFindContent(PublishedRequest request);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user