Merge remote-tracking branch 'origin/v8/dev' into v8/dev

This commit is contained in:
Bjarke Berg
2019-10-22 16:39:36 +02:00
149 changed files with 4785 additions and 3285 deletions
+2 -2
View File
@@ -1,4 +1,4 @@
# Umbraco Cms Build
# Umbraco CMS Build
## Are you sure?
@@ -66,7 +66,7 @@ The Visual Studio object is `null` when Visual Studio has not been detected (eg
* `Path`: Visual Studio installation path (eg some place under `Program Files`)
* `Major`: Visual Studio major version (eg `15` for VS 2017)
* `Minor`: Visual Studio minor version
* `MsBUild`: the absolute path to the MsBuild executable
* `MsBuild`: the absolute path to the MsBuild executable
#### GetUmbracoVersion
+1 -1
View File
@@ -29,4 +29,4 @@ Don't rest on your laurels and never accept the status quo. Contribute and give
## Friendly
Dont judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and builds long lasting relationships.
Dont judge upon mistakes made but rather upon the speed and quality with which mistakes are corrected. Friendly posts and contributions generate smiles and build long lasting relationships.
+1 -1
View File
@@ -38,7 +38,7 @@ This document gives you a quick overview on how to get started.
### Guidelines for contributions we welcome
Not all changes are wanted, so on occassion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
Not all changes are wanted, so on occasion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes.
+2 -2
View File
@@ -13,7 +13,7 @@ Were usually able to handle small PRs pretty quickly. A community volunteer w
Umbraco HQ will regularly mark newly created issues on the issue tracker with the `Up for grabs` tag. This means that the proposed changes are wanted in Umbraco but the HQ does not have the time to make them at this time. We encourage anyone to pick them up and help out.
If you do start working on something, make sure leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
If you do start working on something, make sure to leave a small comment on the issue saying something like: "I'm working on this". That way other people stumbling upon the issue know they don't need to pick it up, someone already has.
## Large PRs
New features and large refactorings - can be recognized by seeing a large number of changes, plenty of new files, updates to package manager files (NuGets packages.config, NPMs packages.json, etc.).
@@ -30,6 +30,6 @@ It is highly recommended that you speak to the HQ before making large, complex c
### Pull request or package?
If it doesnt fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and to fix bugs.
If it doesnt fit in CMS right now, we will likely encourage you to make it into a package instead. A package is a great way to check out popularity of a feature, learn how people use it, validate good usability and fix bugs.
Eventually, a package could "graduate" to be included in the CMS.
+1 -1
View File
@@ -1,4 +1,4 @@
# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/dev)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![pullreminders](https://pullreminders.com/badge.svg)](https://pullreminders.com?ref=badge)
# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/dev)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco)
Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
+2 -2
View File
@@ -53,7 +53,7 @@
<add name="umbracoBaseRequestModule" xdt:Locator="Match(name)" xdt:Transform="Remove" />
<add name="viewstateMoverModule" xdt:Locator="Match(name)" xdt:Transform="Remove" />
<add name=" UmbracoModule" xdt:Locator="Match(name)" xdt:Transform="Remove" />
<add name="UmbracoModule" type="Umbraco.Web.UmbracoModule,umbraco" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
<add name="UmbracoModule" type="Umbraco.Web.UmbracoModule,Umbraco.Web" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
</httpModules>
<httpHandlers xdt:Transform="InsertIfMissing" />
@@ -76,7 +76,7 @@
<!-- Note, we're removing the one that starts with a space here, don't correct it -->
<!-- This to fix a quirk we for a lot of releases where we added it with the space by default -->
<add name=" UmbracoModule" xdt:Locator="Match(name)" xdt:Transform="Remove" />
<add name="UmbracoModule" type="Umbraco.Web.UmbracoModule,umbraco" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
<add name="UmbracoModule" type="Umbraco.Web.UmbracoModule,Umbraco.Web" xdt:Locator="Match(name)" xdt:Transform="InsertIfMissing" />
</modules>
<staticContent xdt:Transform="InsertIfMissing" />
+11 -7
View File
@@ -343,16 +343,20 @@ namespace Umbraco.Core.Mapping
if (ctor == null) return null;
if (_ctors.ContainsKey(sourceType))
_ctors.AddOrUpdate(sourceType, sourceCtor, (k, v) =>
{
// Add missing constructors
foreach (var c in sourceCtor)
{
if (!_ctors[sourceType].TryGetValue(c.Key, out _))
_ctors[sourceType].Add(c.Key, c.Value);
}
}
else
_ctors[sourceType] = sourceCtor;
if (!v.ContainsKey(c.Key))
{
v.Add(c.Key, c.Value);
}
}
return v;
});
return ctor;
}
@@ -74,9 +74,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
.From<DataTypeDto>()
.Where<DataTypeDto>(x => x.NodeId == group.Key)).First();
// check for duplicate aliases
var aliases = group.Select(x => x.Alias).Where(x => !string.IsNullOrWhiteSpace(x)).ToArray();
if (aliases.Distinct().Count() != aliases.Length)
throw new InvalidOperationException($"Cannot migrate prevalues for datatype id={dataType.NodeId}, editor={dataType.EditorAlias}: duplicate alias.");
// handle null/empty aliases
int index = 0;
var dictionary = group.ToDictionary(x => string.IsNullOrWhiteSpace(x.Alias) ? index++.ToString() : x.Alias);
// migrate the preValues to configuration
var migrator = _preValueMigrators.GetMigrator(dataType.EditorAlias) ?? new DefaultPreValueMigrator();
var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, group.ToDictionary(x => x.Alias, x => x));
var config = migrator.GetConfiguration(dataType.NodeId, dataType.EditorAlias, dictionary);
var json = JsonConvert.SerializeObject(config);
// validate - and kill the migration if it fails
@@ -24,8 +24,8 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0.DataTypes
}
// assuming we don't want to fall back to array
if (aliases.Length != preValuesA.Count || aliases.Any(string.IsNullOrWhiteSpace))
throw new InvalidOperationException($"Cannot migrate datatype w/ id={dataTypeId} preValues: duplicate or null/empty alias.");
if (aliases.Any(string.IsNullOrWhiteSpace))
throw new InvalidOperationException($"Cannot migrate prevalues for datatype id={dataTypeId}, editor={editorAlias}: null/empty alias.");
// dictionary-base prevalues
return GetPreValues(preValuesA).ToDictionary(x => x.Alias, GetPreValueValue);
@@ -1,43 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Runtime.CompilerServices;
namespace Umbraco.Core.Persistence
{
internal static class DatabaseNodeLockExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ValidateDatabase(IUmbracoDatabase database)
{
if (database == null)
throw new ArgumentNullException("database");
if (database.GetCurrentTransactionIsolationLevel() < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
}
// updating a record within a repeatable-read transaction gets an exclusive lock on
// that record which will be kept until the transaction is ended, effectively locking
// out all other accesses to that record - thus obtaining an exclusive lock over the
// protected resources.
public static void AcquireLockNodeWriteLock(this IUmbracoDatabase database, int nodeId)
{
ValidateDatabase(database);
database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id",
new { @id = nodeId });
}
// reading a record within a repeatable-read transaction gets a shared lock on
// that record which will be kept until the transaction is ended, effectively preventing
// other write accesses to that record - thus obtaining a shared lock over the protected
// resources.
public static void AcquireLockNodeReadLock(this IUmbracoDatabase database, int nodeId)
{
ValidateDatabase(database);
database.ExecuteScalar<int>("SELECT value FROM umbracoLock WHERE id=@id",
new { @id = nodeId });
}
}
}
@@ -7,5 +7,12 @@ namespace Umbraco.Core.Persistence.Repositories
public interface IDataTypeRepository : IReadWriteQueryRepository<int, IDataType>
{
IEnumerable<MoveEventInfo<IDataType>> Move(IDataType toMove, EntityContainer container);
/// <summary>
/// Returns a dictionary of content type <see cref="Udi"/>s and the property type aliases that use a <see cref="IDataType"/>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
IReadOnlyDictionary<Udi, IEnumerable<string>> FindUsages(int id);
}
}
@@ -1,4 +1,5 @@
using System;

using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
@@ -218,6 +219,7 @@ AND umbracoNode.nodeObjectType = @objectType",
protected void PersistUpdatedBaseContentType(IContentTypeComposition entity)
{
CorrectPropertyTypeVariations(entity);
ValidateVariations(entity);
var dto = ContentTypeFactory.BuildContentTypeDto(entity);
@@ -410,26 +412,7 @@ AND umbracoNode.id <> @id",
// note: this only deals with *local* property types, we're dealing w/compositions later below
foreach (var propertyType in entity.PropertyTypes)
{
if (contentTypeVariationChanging)
{
// content type is changing
switch (newContentTypeVariation)
{
case ContentVariation.Nothing: // changing to Nothing
// all property types must change to Nothing
propertyType.Variations = ContentVariation.Nothing;
break;
case ContentVariation.Culture: // changing to Culture
// all property types can remain Nothing
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
default:
throw new NotSupportedException(); // TODO: Support this
}
}
// then, track each property individually
// track each property individually
if (propertyType.IsPropertyDirty("Variations"))
{
// allocate the list only when needed
@@ -455,23 +438,19 @@ AND umbracoNode.id <> @id",
// via composition, with their original variations (ie not filtered by this
// content type variations - we need this true value to make decisions.
foreach (var propertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
propertyTypeVariationChanges = propertyTypeVariationChanges ?? new Dictionary<int, (ContentVariation, ContentVariation)>();
foreach (var composedPropertyType in ((ContentTypeCompositionBase)entity).RawComposedPropertyTypes)
{
if (propertyType.VariesBySegment() || newContentTypeVariation.VariesBySegment())
throw new NotSupportedException(); // TODO: support this
if (composedPropertyType.Variations == ContentVariation.Nothing) continue;
if (propertyType.Variations == ContentVariation.Culture)
{
if (propertyTypeVariationChanges == null)
propertyTypeVariationChanges = new Dictionary<int, (ContentVariation, ContentVariation)>();
// Determine target variation of the composed property type.
// The composed property is only considered culture variant when the base content type is also culture variant.
// The composed property is only considered segment variant when the base content type is also segment variant.
// Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture
var target = newContentTypeVariation & composedPropertyType.Variations;
// if content type moves to Culture, property type becomes Culture here again
// if content type moves to Nothing, property type becomes Nothing here
if (newContentTypeVariation == ContentVariation.Culture)
propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Nothing, ContentVariation.Culture);
else if (newContentTypeVariation == ContentVariation.Nothing)
propertyTypeVariationChanges[propertyType.Id] = (ContentVariation.Culture, ContentVariation.Nothing);
}
propertyTypeVariationChanges[composedPropertyType.Id] = (composedPropertyType.Variations, target);
}
}
@@ -512,7 +491,7 @@ AND umbracoNode.id <> @id",
var impacted = GetImpactedContentTypes(entity, all);
// if some property types have actually changed, move their variant data
if (propertyTypeVariationChanges != null)
if (propertyTypeVariationChanges?.Count > 0)
MovePropertyTypeVariantData(propertyTypeVariationChanges, impacted);
// deal with orphan properties: those that were in a deleted tab,
@@ -524,23 +503,40 @@ AND umbracoNode.id <> @id",
CommonRepository.ClearCache(); // always
}
/// <summary>
/// Corrects the property type variations for the given entity
/// to make sure the property type variation is compatible with the
/// variation set on the entity itself.
/// </summary>
/// <param name="entity">Entity to correct properties for</param>
private void CorrectPropertyTypeVariations(IContentTypeComposition entity)
{
// Update property variations based on the content type variation
foreach (var propertyType in entity.PropertyTypes)
{
// Determine variation for the property type.
// The property is only considered culture variant when the base content type is also culture variant.
// The property is only considered segment variant when the base content type is also segment variant.
// Example: Culture variant content type with a Culture+Segment variant property type will become ContentVariation.Culture
propertyType.Variations = entity.Variations & propertyType.Variations;
}
}
/// <summary>
/// Ensures that no property types are flagged for a variance that is not supported by the content type itself
/// </summary>
/// <param name="entity"></param>
/// <param name="entity">The entity for which the property types will be validated</param>
private void ValidateVariations(IContentTypeComposition entity)
{
//if the entity does not vary at all, then the property cannot have a variance value greater than it
if (entity.Variations == ContentVariation.Nothing)
foreach (var prop in entity.PropertyTypes)
{
foreach (var prop in entity.PropertyTypes)
{
if (prop.IsPropertyDirty(nameof(prop.Variations)) && prop.Variations > entity.Variations)
throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
}
// The variation of a property is only allowed if all its variation flags
// are also set on the entity itself. It cannot set anything that is not also set by the content type.
// For example, when entity.Variations is set to Culture a property cannot be set to Segment.
var isValid = entity.Variations.HasFlag(prop.Variations);
if (!isValid)
throw new InvalidOperationException($"The property {prop.Alias} cannot have variations of {prop.Variations} with the content type variations of {entity.Variations}");
}
}
private IEnumerable<IContentTypeComposition> GetImpactedContentTypes(IContentTypeComposition contentType, IEnumerable<IContentTypeComposition> all)
@@ -661,27 +657,27 @@ AND umbracoNode.id <> @id",
var impactedL = impacted.Select(x => x.Id).ToList();
//Group by the "To" variation so we can bulk update in the correct batches
foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value.ToVariation))
foreach (var grouping in propertyTypeChanges.GroupBy(x => x.Value))
{
var propertyTypeIds = grouping.Select(x => x.Key).ToList();
var toVariation = grouping.Key;
var (FromVariation, ToVariation) = grouping.Key;
switch (toVariation)
var fromCultureEnabled = FromVariation.HasFlag(ContentVariation.Culture);
var toCultureEnabled = ToVariation.HasFlag(ContentVariation.Culture);
if (!fromCultureEnabled && toCultureEnabled)
{
case ContentVariation.Culture:
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.Nothing:
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
default:
throw new NotSupportedException(); // TODO: Support this
// Culture has been enabled
CopyPropertyData(null, defaultLanguageId, propertyTypeIds, impactedL);
CopyTagData(null, defaultLanguageId, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
}
else if (fromCultureEnabled && !toCultureEnabled)
{
// Culture has been disabled
CopyPropertyData(defaultLanguageId, null, propertyTypeIds, impactedL);
CopyTagData(defaultLanguageId, null, propertyTypeIds, impactedL);
RenormalizeDocumentEditedFlags(propertyTypeIds, impactedL);
}
}
}
@@ -693,78 +689,72 @@ AND umbracoNode.id <> @id",
{
var defaultLanguageId = GetDefaultLanguageId();
switch (toVariation)
var cultureIsNotEnabled = !fromVariation.HasFlag(ContentVariation.Culture);
var cultureWillBeEnabled = toVariation.HasFlag(ContentVariation.Culture);
if (cultureIsNotEnabled && cultureWillBeEnabled)
{
case ContentVariation.Culture:
//move the names
//first clear out any existing names that might already exists under the default lang
//there's 2x tables to update
//move the names
//first clear out any existing names that might already exists under the default lang
//there's 2x tables to update
//clear out the versionCultureVariation table
var sqlSelect = Sql().Select<ContentVersionCultureVariationDto>(x => x.Id)
.From<ContentVersionCultureVariationDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>(x => x.Id, x => x.VersionId)
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
var sqlDelete = Sql()
.Delete<ContentVersionCultureVariationDto>()
.WhereIn<ContentVersionCultureVariationDto>(x => x.Id, sqlSelect);
//clear out the versionCultureVariation table
var sqlSelect = Sql().Select<ContentVersionCultureVariationDto>(x => x.Id)
.From<ContentVersionCultureVariationDto>()
.InnerJoin<ContentVersionDto>().On<ContentVersionDto, ContentVersionCultureVariationDto>(x => x.Id, x => x.VersionId)
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
.Where<ContentVersionCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
var sqlDelete = Sql()
.Delete<ContentVersionCultureVariationDto>()
.WhereIn<ContentVersionCultureVariationDto>(x => x.Id, sqlSelect);
Database.Execute(sqlDelete);
Database.Execute(sqlDelete);
//clear out the documentCultureVariation table
sqlSelect = Sql().Select<DocumentCultureVariationDto>(x => x.Id)
.From<DocumentCultureVariationDto>()
.InnerJoin<ContentDto>().On<ContentDto, DocumentCultureVariationDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
.Where<DocumentCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
sqlDelete = Sql()
.Delete<DocumentCultureVariationDto>()
.WhereIn<DocumentCultureVariationDto>(x => x.Id, sqlSelect);
//clear out the documentCultureVariation table
sqlSelect = Sql().Select<DocumentCultureVariationDto>(x => x.Id)
.From<DocumentCultureVariationDto>()
.InnerJoin<ContentDto>().On<ContentDto, DocumentCultureVariationDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id)
.Where<DocumentCultureVariationDto>(x => x.LanguageId == defaultLanguageId);
sqlDelete = Sql()
.Delete<DocumentCultureVariationDto>()
.WhereIn<DocumentCultureVariationDto>(x => x.Id, sqlSelect);
Database.Execute(sqlDelete);
Database.Execute(sqlDelete);
//now we need to insert names into these 2 tables based on the invariant data
//now we need to insert names into these 2 tables based on the invariant data
//insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
var cols = Sql().Columns<ContentVersionCultureVariationDto>(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
sqlSelect = Sql().Select<ContentVersionDto>(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
.Append($", {defaultLanguageId}") //default language ID
.From<ContentVersionDto>()
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
//insert rows into the versionCultureVariationDto table based on the data from contentVersionDto for the default lang
var cols = Sql().Columns<ContentVersionCultureVariationDto>(x => x.VersionId, x => x.Name, x => x.UpdateUserId, x => x.UpdateDate, x => x.LanguageId);
sqlSelect = Sql().Select<ContentVersionDto>(x => x.Id, x => x.Text, x => x.UserId, x => x.VersionDate)
.Append($", {defaultLanguageId}") //default language ID
.From<ContentVersionDto>()
.InnerJoin<ContentDto>().On<ContentDto, ContentVersionDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
var sqlInsert = Sql($"INSERT INTO {ContentVersionCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
Database.Execute(sqlInsert);
Database.Execute(sqlInsert);
//insert rows into the documentCultureVariation table
cols = Sql().Columns<DocumentCultureVariationDto>(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
sqlSelect = Sql().Select<DocumentDto>(x => x.NodeId, x => x.Edited, x => x.Published)
.AndSelect<NodeDto>(x => x.Text)
.Append($", 1, {defaultLanguageId}") //make Available + default language ID
.From<DocumentDto>()
.InnerJoin<NodeDto>().On<NodeDto, DocumentDto>(x => x.NodeId, x => x.NodeId)
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
//insert rows into the documentCultureVariation table
cols = Sql().Columns<DocumentCultureVariationDto>(x => x.NodeId, x => x.Edited, x => x.Published, x => x.Name, x => x.Available, x => x.LanguageId);
sqlSelect = Sql().Select<DocumentDto>(x => x.NodeId, x => x.Edited, x => x.Published)
.AndSelect<NodeDto>(x => x.Text)
.Append($", 1, {defaultLanguageId}") //make Available + default language ID
.From<DocumentDto>()
.InnerJoin<NodeDto>().On<NodeDto, DocumentDto>(x => x.NodeId, x => x.NodeId)
.InnerJoin<ContentDto>().On<ContentDto, NodeDto>(x => x.NodeId, x => x.NodeId)
.Where<ContentDto>(x => x.ContentTypeId == contentType.Id);
sqlInsert = Sql($"INSERT INTO {DocumentCultureVariationDto.TableName} ({cols})").Append(sqlSelect);
Database.Execute(sqlInsert);
}
else
{
//we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
Database.Execute(sqlInsert);
break;
case ContentVariation.Nothing:
//we don't need to move the names! this is because we always keep the invariant names with the name of the default language.
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
// if we want these SQL statements back, look into GIT history
break;
case ContentVariation.CultureAndSegment:
case ContentVariation.Segment:
default:
throw new NotSupportedException(); // TODO: Support this
//however, if we were to move names, we could do this: BUT this doesn't work with SQLCE, for that we'd have to update row by row :(
// if we want these SQL statements back, look into GIT history
}
}
@@ -279,6 +279,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return moveInfo;
}
public IReadOnlyDictionary<Udi, IEnumerable<string>> FindUsages(int id)
{
if (id == default)
return new Dictionary<Udi, IEnumerable<string>>();
var sql = Sql()
.Select<ContentTypeDto>(ct => ct.Select(node => node.NodeDto))
.AndSelect<PropertyTypeDto>(pt => Alias(pt.Alias, "ptAlias"), pt => Alias(pt.Name, "ptName"))
.From<PropertyTypeDto>()
.InnerJoin<ContentTypeDto>().On<ContentTypeDto, PropertyTypeDto>(ct => ct.NodeId, pt => pt.ContentTypeId)
.InnerJoin<NodeDto>().On<NodeDto, ContentTypeDto>(n => n.NodeId, ct => ct.NodeId)
.Where<PropertyTypeDto>(pt => pt.DataTypeId == id)
.OrderBy<NodeDto>(node => node.NodeId)
.AndBy<PropertyTypeDto>(pt => pt.Alias);
var dtos = Database.FetchOneToMany<ContentTypeReferenceDto>(ct => ct.PropertyTypes, sql);
return dtos.ToDictionary(
x => (Udi)new GuidUdi(ObjectTypes.GetUdiType(x.NodeDto.NodeObjectType.Value), x.NodeDto.UniqueId).EnsureClosed(),
x => (IEnumerable<string>)x.PropertyTypes.Select(p => p.Alias).ToList());
}
private string EnsureUniqueNodeName(string nodeName, int id = 0)
{
var template = SqlContext.Templates.Get("Umbraco.Core.DataTypeDefinitionRepository.EnsureUniqueNodeName", tsql => tsql
@@ -291,5 +313,24 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
return SimilarNodeName.GetUniqueName(names, id, nodeName);
}
[TableName(Constants.DatabaseSchema.Tables.ContentType)]
private class ContentTypeReferenceDto : ContentTypeDto
{
[ResultColumn]
[Reference(ReferenceType.Many)]
public List<PropertyTypeReferenceDto> PropertyTypes { get; set; }
}
[TableName(Constants.DatabaseSchema.Tables.PropertyType)]
private class PropertyTypeReferenceDto
{
[Column("ptAlias")]
public string Alias { get; set; }
[Column("ptName")]
public string Name { get; set; }
}
}
}
@@ -182,6 +182,19 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
throw new InvalidOperationException($"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead.");
}
if (entity.IsPropertyDirty(nameof(ILanguage.IsoCode)))
{
//if the iso code is changing, ensure there's not another lang with the same code already assigned
var sameCode = Sql()
.SelectCount()
.From<LanguageDto>()
.Where<LanguageDto>(x => x.IsoCode == entity.IsoCode && x.Id != entity.Id);
var countOfSameCode = Database.ExecuteScalar<int>(sameCode);
if (countOfSameCode > 0)
throw new InvalidOperationException($"Cannot update the language to a new culture: {entity.IsoCode} since that culture is already assigned to another language entity.");
}
// fallback cycles are detected at service level
// update
@@ -133,7 +133,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
// joining the type so we can do a query against the member type - not sure if this adds much overhead or not?
// the execution plan says it doesn't so we'll go with that and in that case, it might be worth joining the content
// types by default on the document and media repo's so we can query by content type there too.
// types by default on the document and media repos so we can query by content type there too.
.InnerJoin<ContentTypeDto>().On<ContentDto, ContentTypeDto>(left => left.ContentTypeId, right => right.NodeId);
sql.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId);
@@ -546,6 +546,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
if (ordering.OrderBy.InvariantEquals("userName"))
return SqlSyntax.GetFieldName<MemberDto>(x => x.LoginName);
if (ordering.OrderBy.InvariantEquals("updateDate"))
return SqlSyntax.GetFieldName<ContentVersionDto>(x => x.VersionDate);
if (ordering.OrderBy.InvariantEquals("createDate"))
return SqlSyntax.GetFieldName<NodeDto>(x => x.CreateDate);
if (ordering.OrderBy.InvariantEquals("contentTypeAlias"))
return SqlSyntax.GetFieldName<ContentTypeDto>(x => x.Alias);
return base.ApplySystemOrdering(ref sql, ordering);
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Text.RegularExpressions;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
@@ -76,6 +77,11 @@ namespace Umbraco.Core.Persistence.SqlSyntax
string ConvertIntegerToOrderableString { get; }
string ConvertDateToOrderableString { get; }
string ConvertDecimalToOrderableString { get; }
/// <summary>
/// Returns the default isolation level for the database
/// </summary>
IsolationLevel DefaultIsolationLevel { get; }
IEnumerable<string> GetTablesInSchema(IDatabase db);
IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db);
@@ -121,5 +127,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
/// unspecified.</para>
/// </remarks>
bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
void ReadLock(IDatabase db, params int[] lockIds);
void WriteLock(IDatabase db, params int[] lockIds);
}
}
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlServerCe;
using System.Linq;
using NPoco;
using Umbraco.Core.Persistence.DatabaseAnnotations;
@@ -52,6 +54,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return "(" + string.Join("+", args) + ")";
}
public override System.Data.IsolationLevel DefaultIsolationLevel => System.Data.IsolationLevel.RepeatableRead;
public override string FormatColumnRename(string tableName, string oldName, string newName)
{
//NOTE Sql CE doesn't support renaming a column, so a new column needs to be created, then copy data and finally remove old column
@@ -152,6 +156,39 @@ where table_name=@0 and column_name=@1", tableName, columnName).FirstOrDefault()
return result > 0;
}
public override void WriteLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
db.Execute(@"SET LOCK_TIMEOUT 1800;");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = db.Execute(@"UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
if (i == 0) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
}
}
public override void ReadLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
if (i == null) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
}
}
protected override string FormatIdentity(ColumnDefinition column)
{
return column.IsIdentity ? GetIdentityString(column) : string.Empty;
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Linq;
using NPoco;
using Umbraco.Core.Logging;
@@ -179,6 +180,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return items.Select(x => x.TABLE_NAME).Cast<string>().ToList();
}
public override IsolationLevel DefaultIsolationLevel => IsolationLevel.ReadCommitted;
public override IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db)
{
var items = db.Fetch<dynamic>("SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT, IS_NULLABLE, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = (SELECT SCHEMA_NAME())");
@@ -246,6 +249,41 @@ where tbl.[name]=@0 and col.[name]=@1;", tableName, columnName)
return result > 0;
}
public override void WriteLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
db.Execute(@"SET LOCK_TIMEOUT 1800;");
var i = db.Execute(@"UPDATE umbracoLock WITH (REPEATABLEREAD) SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
if (i == 0) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.");
}
}
public override void ReadLock(IDatabase db, params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (db.Transaction.IsolationLevel < IsolationLevel.ReadCommitted)
throw new InvalidOperationException("A transaction with minimum ReadCommitted isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = db.ExecuteScalar<int?>("SELECT value FROM umbracoLock WITH (REPEATABLEREAD) WHERE id=@id", new { id = lockId });
if (i == null) // ensure we are actually locking!
throw new ArgumentException($"LockObject with id={lockId} does not exist.", nameof(lockIds));
}
}
public override string FormatColumnRename(string tableName, string oldName, string newName)
{
return string.Format(RenameColumn, tableName, oldName, newName);
@@ -200,7 +200,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
return "NVARCHAR";
}
public abstract IsolationLevel DefaultIsolationLevel { get; }
public virtual IEnumerable<string> GetTablesInSchema(IDatabase db)
{
return new List<string>();
@@ -225,6 +227,9 @@ namespace Umbraco.Core.Persistence.SqlSyntax
public abstract bool TryGetDefaultConstraint(IDatabase db, string tableName, string columnName, out string constraintName);
public abstract void ReadLock(IDatabase db, params int[] lockIds);
public abstract void WriteLock(IDatabase db, params int[] lockIds);
public virtual bool DoesTableExist(IDatabase db, string tableName)
{
return false;
@@ -20,9 +20,6 @@ namespace Umbraco.Core.Persistence
/// </remarks>
public class UmbracoDatabase : Database, IUmbracoDatabase
{
// Umbraco's default isolation level is RepeatableRead
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
private readonly ILogger _logger;
private readonly RetryPolicy _connectionRetryPolicy;
private readonly RetryPolicy _commandRetryPolicy;
@@ -38,7 +35,7 @@ namespace Umbraco.Core.Persistence
/// <para>Also used by DatabaseBuilder for creating databases and installing/upgrading.</para>
/// </remarks>
public UmbracoDatabase(string connectionString, ISqlContext sqlContext, DbProviderFactory provider, ILogger logger, RetryPolicy connectionRetryPolicy = null, RetryPolicy commandRetryPolicy = null)
: base(connectionString, sqlContext.DatabaseType, provider, DefaultIsolationLevel)
: base(connectionString, sqlContext.DatabaseType, provider, sqlContext.SqlSyntax.DefaultIsolationLevel)
{
SqlContext = sqlContext;
@@ -54,7 +51,7 @@ namespace Umbraco.Core.Persistence
/// </summary>
/// <remarks>Internal for unit tests only.</remarks>
internal UmbracoDatabase(DbConnection connection, ISqlContext sqlContext, ILogger logger)
: base(connection, sqlContext.DatabaseType, DefaultIsolationLevel)
: base(connection, sqlContext.DatabaseType, sqlContext.SqlSyntax.DefaultIsolationLevel)
{
SqlContext = sqlContext;
_logger = logger;
+3 -33
View File
@@ -33,8 +33,6 @@ namespace Umbraco.Core.Scoping
private ICompletable _fscope;
private IEventDispatcher _eventDispatcher;
private const IsolationLevel DefaultIsolationLevel = IsolationLevel.RepeatableRead;
// initializes a new scope
private Scope(ScopeProvider scopeProvider,
ILogger logger, FileSystems fileSystems, Scope parent, ScopeContext scopeContext, bool detachable,
@@ -205,7 +203,7 @@ namespace Umbraco.Core.Scoping
{
if (_isolationLevel != IsolationLevel.Unspecified) return _isolationLevel;
if (ParentScope != null) return ParentScope.IsolationLevel;
return DefaultIsolationLevel;
return Database.SqlContext.SqlSyntax.DefaultIsolationLevel;
}
}
@@ -488,37 +486,9 @@ namespace Umbraco.Core.Scoping
?? (_logUncompletedScopes = Current.Configs.CoreDebug().LogUncompletedScopes)).Value;
/// <inheritdoc />
public void ReadLock(params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = Database.ExecuteScalar<int?>("SELECT value FROM umbracoLock WHERE id=@id", new { id = lockId });
if (i == null) // ensure we are actually locking!
throw new Exception($"LockObject with id={lockId} does not exist.");
}
}
public void ReadLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.ReadLock(Database, lockIds);
/// <inheritdoc />
public void WriteLock(params int[] lockIds)
{
// soon as we get Database, a transaction is started
if (Database.Transaction.IsolationLevel < IsolationLevel.RepeatableRead)
throw new InvalidOperationException("A transaction with minimum RepeatableRead isolation level is required.");
// *not* using a unique 'WHERE IN' query here because the *order* of lockIds is important to avoid deadlocks
foreach (var lockId in lockIds)
{
var i = Database.Execute("UPDATE umbracoLock SET value = (CASE WHEN (value=1) THEN -1 ELSE 1 END) WHERE id=@id", new { id = lockId });
if (i == 0) // ensure we are actually locking!
throw new Exception($"LockObject with id={lockId} does not exist.");
}
}
public void WriteLock(params int[] lockIds) => Database.SqlContext.SqlSyntax.WriteLock(Database, lockIds);
}
}
@@ -25,7 +25,7 @@ namespace Umbraco.Core.Services
/// <summary>
/// Gets a content type.
/// </summary>
TItem Get(int id);
new TItem Get(int id);
/// <summary>
/// Gets a content type.
@@ -40,6 +40,7 @@ namespace Umbraco.Core.Services
int Count();
IEnumerable<TItem> GetAll(params int[] ids);
IEnumerable<TItem> GetAll(IEnumerable<Guid> ids);
IEnumerable<TItem> GetDescendants(int id, bool andSelf); // parent-child axis
IEnumerable<TItem> GetComposedOf(int id); // composition axis
@@ -10,6 +10,13 @@ namespace Umbraco.Core.Services
/// </summary>
public interface IDataTypeService : IService
{
/// <summary>
/// Returns a dictionary of content type <see cref="Udi"/>s and the property type aliases that use a <see cref="IDataType"/>
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id);
Attempt<OperationResult<OperationResultType, EntityContainer>> CreateContainer(int parentId, string name, int userId = Constants.Security.SuperUserId);
Attempt<OperationResult> SaveContainer(EntityContainer container, int userId = Constants.Security.SuperUserId);
EntityContainer GetContainer(int containerId);
@@ -252,12 +252,12 @@ namespace Umbraco.Core.Services.Implement
}
}
public IEnumerable<TItem> GetAll(params Guid[] ids)
public IEnumerable<TItem> GetAll(IEnumerable<Guid> ids)
{
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
{
scope.ReadLock(ReadLockIds);
return Repository.GetMany(ids);
return Repository.GetMany(ids.ToArray());
}
}
@@ -466,6 +466,14 @@ namespace Umbraco.Core.Services.Implement
}
}
public IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id)
{
using (var scope = ScopeProvider.CreateScope(autoComplete:true))
{
return _dataTypeRepository.FindUsages(id);
}
}
private void Audit(AuditType type, int userId, int objectId)
{
_auditRepository.Save(new AuditItem(objectId, type, userId, ObjectTypes.GetName(UmbracoObjectTypes.DataType)));
-1
View File
@@ -984,7 +984,6 @@
<Compile Include="Persistence\DatabaseModelDefinitions\ModificationType.cs" />
<Compile Include="Persistence\DatabaseModelDefinitions\SystemMethods.cs" />
<Compile Include="Persistence\DatabaseModelDefinitions\TableDefinition.cs" />
<Compile Include="Persistence\DatabaseNodeLockExtensions.cs" />
<Compile Include="Persistence\DbCommandExtensions.cs" />
<Compile Include="Persistence\DbConnectionExtensions.cs" />
<Compile Include="Persistence\EntityNotFoundException.cs" />
@@ -4,6 +4,7 @@ using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
namespace Umbraco.Tests.Composing
@@ -35,7 +36,7 @@ namespace Umbraco.Tests.Composing
.Returns(() => factoryFactory?.Invoke(mockedFactory));
var logger = new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>());
var typeLoader = new TypeLoader(Mock.Of<IAppPolicyCache>(), "", logger);
var typeLoader = new TypeLoader(Mock.Of<IAppPolicyCache>(), IOHelper.MapPath("~/App_Data/TEMP"), logger);
var composition = new Composition(mockedRegister, typeLoader, logger, Mock.Of<IRuntimeState>());
// create the factory, ensure it is the mocked factory
@@ -21,7 +21,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
/// <summary>
/// Implements a published snapshot service.
/// </summary>
internal class PublishedSnapshotService : PublishedSnapshotServiceBase
internal class XmlPublishedSnapshotService : PublishedSnapshotServiceBase
{
private readonly XmlStore _xmlStore;
private readonly RoutesCache _routesCache;
@@ -41,7 +41,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
#region Constructors
// used in WebBootManager + tests
public PublishedSnapshotService(ServiceContext serviceContext,
public XmlPublishedSnapshotService(ServiceContext serviceContext,
IPublishedContentTypeFactory publishedContentTypeFactory,
IScopeProvider scopeProvider,
IAppCache requestCache,
@@ -65,7 +65,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
}
// used in some tests
internal PublishedSnapshotService(ServiceContext serviceContext,
internal XmlPublishedSnapshotService(ServiceContext serviceContext,
IPublishedContentTypeFactory publishedContentTypeFactory,
IScopeProvider scopeProvider,
IAppCache requestCache,
@@ -32,7 +32,7 @@ namespace Umbraco.Tests.LegacyXmlPublishedCache
/// Represents the Xml storage for the Xml published cache.
/// </summary>
/// <remarks>
/// <para>One instance of <see cref="XmlStore"/> is instantiated by the <see cref="PublishedSnapshotService"/> and
/// <para>One instance of <see cref="XmlStore"/> is instantiated by the <see cref="XmlPublishedSnapshotService"/> and
/// then passed to all <see cref="PublishedContentCache"/> instances that are created (one per request).</para>
/// <para>This class should *not* be public.</para>
/// </remarks>
@@ -57,6 +57,9 @@ namespace Umbraco.Tests.Models
[TestCase("1,-1", "1", "1")] // was an issue
[TestCase("-1,1", "1", "1")] // was an issue
[TestCase("-1", "", "-1")]
[TestCase("", "-1", "-1")]
public void CombineStartNodes(string groupSn, string userSn, string expected)
{
// 1
+6 -7
View File
@@ -5,7 +5,6 @@ using System.Threading;
using NPoco;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
@@ -37,7 +36,7 @@ namespace Umbraco.Tests.Persistence
{
using (var scope = ScopeProvider.CreateScope())
{
scope.Database.AcquireLockNodeReadLock(Constants.Locks.Servers);
scope.ReadLock(Constants.Locks.Servers);
scope.Complete();
}
}
@@ -62,7 +61,7 @@ namespace Umbraco.Tests.Persistence
{
try
{
scope.Database.AcquireLockNodeReadLock(Constants.Locks.Servers);
scope.ReadLock(Constants.Locks.Servers);
lock (locker)
{
acquired++;
@@ -131,7 +130,7 @@ namespace Umbraco.Tests.Persistence
if (entered == threadCount) m1.Set();
}
ms[ic].WaitOne();
scope.Database.AcquireLockNodeWriteLock(Constants.Locks.Servers);
scope.WriteLock(Constants.Locks.Servers);
lock (locker)
{
acquired++;
@@ -221,7 +220,7 @@ namespace Umbraco.Tests.Persistence
{
otherEv.WaitOne();
Console.WriteLine($"[{id1}] WAIT {id1}");
scope.Database.AcquireLockNodeWriteLock(id1);
scope.WriteLock(id1);
Console.WriteLine($"[{id1}] GRANT {id1}");
WriteLocks(scope.Database);
myEv.Set();
@@ -232,7 +231,7 @@ namespace Umbraco.Tests.Persistence
Thread.Sleep(200); // cannot wait due to deadlock... just give it a bit of time
Console.WriteLine($"[{id1}] WAIT {id2}");
scope.Database.AcquireLockNodeWriteLock(id2);
scope.WriteLock(id2);
Console.WriteLine($"[{id1}] GRANT {id2}");
WriteLocks(scope.Database);
}
@@ -284,7 +283,7 @@ namespace Umbraco.Tests.Persistence
{
otherEv.WaitOne();
Console.WriteLine($"[{id}] WAIT {id}");
scope.Database.AcquireLockNodeWriteLock(id);
scope.WriteLock(id);
Console.WriteLine($"[{id}] GRANT {id}");
WriteLocks(scope.Database);
myEv.Set();
@@ -28,6 +28,68 @@ namespace Umbraco.Tests.Persistence.Repositories
return new EntityContainerRepository(scopeAccessor, AppCaches.Disabled, Logger, Constants.ObjectTypes.DataTypeContainer);
}
[Test]
public void Can_Find_Usages()
{
var provider = TestObjects.GetScopeProvider(Logger);
using (provider.CreateScope())
{
var dtRepo = CreateRepository();
IDataType dataType1 = new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService)) { Name = "dt1" };
dtRepo.Save(dataType1);
IDataType dataType2 = new DataType(new RadioButtonsPropertyEditor(Logger, ServiceContext.TextService)) { Name = "dt2" };
dtRepo.Save(dataType2);
var ctRepo = Factory.GetInstance<IContentTypeRepository>();
IContentType ct = new ContentType(-1)
{
Alias = "ct1",
Name = "CT1",
AllowedAsRoot = true,
Icon = "icon-home",
PropertyGroups = new PropertyGroupCollection
{
new PropertyGroup(true)
{
Name = "PG1",
PropertyTypes = new PropertyTypeCollection(true)
{
new PropertyType(dataType1, "pt1")
{
Name = "PT1"
},
new PropertyType(dataType1, "pt2")
{
Name = "PT2"
},
new PropertyType(dataType2, "pt3")
{
Name = "PT3"
}
}
}
}
};
ctRepo.Save(ct);
var usages = dtRepo.FindUsages(dataType1.Id);
var key = usages.First().Key;
Assert.AreEqual(ct.Key, ((GuidUdi)key).Guid);
Assert.AreEqual(2, usages[key].Count());
Assert.AreEqual("pt1", usages[key].ElementAt(0));
Assert.AreEqual("pt2", usages[key].ElementAt(1));
usages = dtRepo.FindUsages(dataType2.Id);
key = usages.First().Key;
Assert.AreEqual(ct.Key, ((GuidUdi)key).Guid);
Assert.AreEqual(1, usages[key].Count());
Assert.AreEqual("pt3", usages[key].ElementAt(0));
}
}
[Test]
public void Can_Move()
{
@@ -1,4 +1,5 @@
using System.Globalization;
using System;
using System.Globalization;
using System.Linq;
using Moq;
using NUnit.Framework;
@@ -297,6 +298,24 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
[Test]
public void Perform_Update_With_Existing_Culture()
{
// Arrange
var provider = TestObjects.GetScopeProvider(Logger);
using (var scope = provider.CreateScope())
{
var repository = CreateRepository(provider);
// Act
var language = repository.Get(5);
language.IsoCode = "da-DK";
language.CultureName = "da-DK";
Assert.Throws<InvalidOperationException>(() => repository.Save(language));
}
}
[Test]
public void Can_Perform_Delete_On_LanguageRepository()
{
@@ -14,7 +14,7 @@ namespace Umbraco.Tests.Persistence
public void ReadLockNonExisting()
{
var provider = TestObjects.GetScopeProvider(Logger);
Assert.Throws<Exception>(() =>
Assert.Throws<ArgumentException>(() =>
{
using (var scope = provider.CreateScope())
{
@@ -39,7 +39,7 @@ namespace Umbraco.Tests.Persistence
public void WriteLockNonExisting()
{
var provider = TestObjects.GetScopeProvider(Logger);
Assert.Throws<Exception>(() =>
Assert.Throws<ArgumentException>(() =>
{
using (var scope = provider.CreateScope())
{
@@ -41,6 +41,12 @@ namespace Umbraco.Tests.PublishedContent
private ContentType _contentTypeVariant;
private TestDataSource _source;
[TearDown]
public void Teardown()
{
_snapshotService?.Dispose();
}
private void Init(IEnumerable<ContentNodeKit> kits)
{
Current.Reset();
@@ -36,6 +36,12 @@ namespace Umbraco.Tests.PublishedContent
private ContentType _contentType;
private PropertyType _propertyType;
[TearDown]
public void Teardown()
{
_snapshotService?.Dispose();
}
private void Init()
{
Current.Reset();
@@ -303,5 +309,6 @@ namespace Umbraco.Tests.PublishedContent
Assert.IsFalse(c2.IsPublished("dk-DA"));
Assert.IsTrue(c2.IsPublished("de-DE"));
}
}
}
+1 -1
View File
@@ -73,7 +73,7 @@ namespace Umbraco.Tests.Scoping
// xmlStore.Xml - the actual main xml document
// publishedContentCache.GetXml() - the captured xml
private static XmlStore XmlStore => (Current.Factory.GetInstance<IPublishedSnapshotService>() as PublishedSnapshotService).XmlStore;
private static XmlStore XmlStore => (Current.Factory.GetInstance<IPublishedSnapshotService>() as XmlPublishedSnapshotService).XmlStore;
private static XmlDocument XmlMaster => XmlStore.Xml;
private static XmlDocument XmlInContext => ((PublishedContentCache) Umbraco.Web.Composing.Current.UmbracoContext.Content).GetXml(false);
@@ -105,13 +105,26 @@ namespace Umbraco.Tests.Services
}
}
[Test]
public void Change_Content_Type_Variation_Clears_Redirects()
[TestCase(ContentVariation.Nothing, ContentVariation.Nothing, false)]
[TestCase(ContentVariation.Nothing, ContentVariation.Culture, true)]
[TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment, true)]
[TestCase(ContentVariation.Nothing, ContentVariation.Segment, true)]
[TestCase(ContentVariation.Culture, ContentVariation.Nothing, true)]
[TestCase(ContentVariation.Culture, ContentVariation.Culture, false)]
[TestCase(ContentVariation.Culture, ContentVariation.Segment, true)]
[TestCase(ContentVariation.Culture, ContentVariation.CultureAndSegment, true)]
[TestCase(ContentVariation.Segment, ContentVariation.Nothing, true)]
[TestCase(ContentVariation.Segment, ContentVariation.Culture, true)]
[TestCase(ContentVariation.Segment, ContentVariation.Segment, false)]
[TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment, true)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing, true)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Culture, true)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment, true)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.CultureAndSegment, false)]
public void Change_Content_Type_Variation_Clears_Redirects(ContentVariation startingContentTypeVariation, ContentVariation changedContentTypeVariation, bool shouldUrlRedirectsBeCleared)
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = startingContentTypeVariation;
ServiceContext.ContentTypeService.Save(contentType);
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
ServiceContext.ContentTypeService.Save(contentType2);
@@ -119,6 +132,11 @@ namespace Umbraco.Tests.Services
//create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = "Hello1";
if(startingContentTypeVariation.HasFlag(ContentVariation.Culture))
{
doc.SetCultureName(doc.Name, "en-US");
}
ServiceContext.ContentService.Save(doc);
IContent doc2 = MockedContent.CreateBasicContent(contentType2);
@@ -127,24 +145,27 @@ namespace Umbraco.Tests.Services
ServiceContext.RedirectUrlService.Register("hello/world", doc.Key);
ServiceContext.RedirectUrlService.Register("hello2/world2", doc2.Key);
// These 2 assertions should probably be moved to a test for the Register() method?
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
//change variation
contentType.Variations = ContentVariation.Culture;
contentType.Variations = changedContentTypeVariation;
ServiceContext.ContentTypeService.Save(contentType);
Assert.AreEqual(0, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
var expectedRedirectUrlCount = shouldUrlRedirectsBeCleared ? 0 : 1;
Assert.AreEqual(expectedRedirectUrlCount, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc.Key).Count());
Assert.AreEqual(1, ServiceContext.RedirectUrlService.GetContentRedirectUrls(doc2.Key).Count());
}
[Test]
public void Change_Content_Type_From_Invariant_Variant()
{
[TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
[TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
[TestCase(ContentVariation.Segment, ContentVariation.Culture)]
[TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
public void Change_Content_Type_From_No_Culture_To_Culture(ContentVariation from, ContentVariation to)
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.Variations = from;
var properties = CreatePropertyCollection(("title", from));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -159,12 +180,12 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("Hello1", doc.Name);
Assert.AreEqual("hello world", doc.GetValue("title"));
Assert.IsTrue(doc.Edited);
Assert.IsFalse (doc.IsCultureEdited("en-US"));
Assert.IsFalse(doc.IsCultureEdited("en-US"));
//change the content type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello2";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
contentType.Variations = to;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -176,7 +197,7 @@ namespace Umbraco.Tests.Services
//change back property type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello3", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
contentType.Variations = from;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -186,12 +207,15 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(doc.IsCultureEdited("en-US"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant()
[TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
[TestCase(ContentVariation.Culture, ContentVariation.Segment)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
public void Change_Content_Type_From_Culture_To_No_Culture(ContentVariation startingContentTypeVariation, ContentVariation changeContentTypeVariationTo)
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.Variations = startingContentTypeVariation;
var properties = CreatePropertyCollection(("title", startingContentTypeVariation));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -210,7 +234,7 @@ namespace Umbraco.Tests.Services
//change the content type to be invariant, we will also update the name here to detect the copy changes
doc.SetCultureName("Hello2", "en-US");
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Nothing;
contentType.Variations = changeContentTypeVariationTo;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -222,19 +246,20 @@ namespace Umbraco.Tests.Services
//change back property type to be variant, we will also update the name here to detect the copy changes
doc.Name = "Hello3";
ServiceContext.ContentService.Save(doc);
contentType.Variations = ContentVariation.Culture;
contentType.Variations = startingContentTypeVariation;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
//at this stage all property types were switched to invariant so even though the variant value
//exists it will not be returned because the property type is invariant,
//so this check proves that null will be returned
Assert.AreEqual("Hello3", doc.Name);
Assert.IsNull(doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US")); // this is true because the name change is copied to the default language
//we can now switch the property type to be variant and the value can be returned again
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
contentType.PropertyTypes.First().Variations = startingContentTypeVariation;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -242,32 +267,129 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
Assert.IsTrue(doc.Edited);
Assert.IsTrue(doc.IsCultureEdited("en-US"));
}
[Test]
public void Change_Property_Type_From_To_Variant_On_Invariant_Content_Type()
[TestCase(ContentVariation.Nothing, ContentVariation.Nothing)]
[TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
[TestCase(ContentVariation.Nothing, ContentVariation.Segment)]
[TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
[TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
[TestCase(ContentVariation.Culture, ContentVariation.Culture)]
[TestCase(ContentVariation.Culture, ContentVariation.Segment)]
[TestCase(ContentVariation.Culture, ContentVariation.CultureAndSegment)]
[TestCase(ContentVariation.Segment, ContentVariation.Nothing)]
[TestCase(ContentVariation.Segment, ContentVariation.Culture)]
[TestCase(ContentVariation.Segment, ContentVariation.Segment)]
[TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Culture)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.CultureAndSegment)]
public void Preserve_Content_Name_After_Content_Type_Variation_Change(ContentVariation contentTypeVariationFrom, ContentVariation contentTypeVariationTo)
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Nothing;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
contentType.Variations = contentTypeVariationFrom;
ServiceContext.ContentTypeService.Save(contentType);
var invariantContentName = "Content Invariant";
var defaultCultureContentName = "Content en-US";
var defaultCulture = "en-US";
var nlContentName = "Content nl-NL";
var nlCulture = "nl-NL";
ServiceContext.LocalizationService.Save(new Language(nlCulture));
var includeCultureNames = contentType.Variations.HasFlag(ContentVariation.Culture);
// Create some content of this content type
IContent doc = MockedContent.CreateBasicContent(contentType);
doc.Name = invariantContentName;
if (includeCultureNames)
{
Assert.DoesNotThrow(() => doc.SetCultureName(defaultCultureContentName, defaultCulture));
Assert.DoesNotThrow(() => doc.SetCultureName(nlContentName, nlCulture));
} else
{
Assert.Throws<NotSupportedException>(() => doc.SetCultureName(defaultCultureContentName, defaultCulture));
Assert.Throws<NotSupportedException>(() => doc.SetCultureName(nlContentName, nlCulture));
}
ServiceContext.ContentService.Save(doc);
doc = ServiceContext.ContentService.GetById(doc.Id);
AssertAll();
// Change variation
contentType.Variations = contentTypeVariationTo;
ServiceContext.ContentService.Save(doc);
doc = ServiceContext.ContentService.GetById(doc.Id);
AssertAll();
void AssertAll()
{
if (includeCultureNames)
{
// Invariant content name is not preserved when content type is set to culture
Assert.AreEqual(defaultCultureContentName, doc.Name);
Assert.AreEqual(doc.Name, doc.GetCultureName(defaultCulture));
Assert.AreEqual(nlContentName, doc.GetCultureName(nlCulture));
}
else
{
Assert.AreEqual(invariantContentName, doc.Name);
Assert.AreEqual(null, doc.GetCultureName(defaultCulture));
Assert.AreEqual(null, doc.GetCultureName(nlCulture));
}
}
}
[TestCase(ContentVariation.Nothing, ContentVariation.Nothing)]
[TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
[TestCase(ContentVariation.Nothing, ContentVariation.Segment)]
[TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
[TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
[TestCase(ContentVariation.Culture, ContentVariation.Culture)]
[TestCase(ContentVariation.Culture, ContentVariation.Segment)]
[TestCase(ContentVariation.Culture, ContentVariation.CultureAndSegment)]
[TestCase(ContentVariation.Segment, ContentVariation.Nothing)]
[TestCase(ContentVariation.Segment, ContentVariation.Culture)]
[TestCase(ContentVariation.Segment, ContentVariation.Segment)]
[TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Culture)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.CultureAndSegment)]
public void Verify_If_Property_Type_Variation_Is_Correctly_Corrected_When_Content_Type_Is_Updated(ContentVariation contentTypeVariation, ContentVariation propertyTypeVariation)
{
var contentType = MockedContentTypes.CreateBasicContentType();
// We test an updated content type so it has to be saved first.
ServiceContext.ContentTypeService.Save(contentType);
// Update it
contentType.Variations = contentTypeVariation;
var properties = CreatePropertyCollection(("title", propertyTypeVariation));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//change the property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
//Cannot change a property type to be variant if the content type itself is not variant
Assert.Throws<InvalidOperationException>(() => ServiceContext.ContentTypeService.Save(contentType));
// Check if property type variations have been updated correctly
Assert.AreEqual(properties.First().Variations, contentTypeVariation & propertyTypeVariation);
}
[Test]
public void Change_Property_Type_From_Invariant_Variant()
[TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
[TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
[TestCase(ContentVariation.Segment, ContentVariation.Culture)]
[TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
public void Change_Property_Type_From_Invariant_Variant(ContentVariation invariant, ContentVariation variant)
{
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Nothing));
// content type supports all variations
contentType.Variations = ContentVariation.Culture | ContentVariation.Segment;
var properties = CreatePropertyCollection(("title", invariant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -283,7 +405,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(doc.Edited);
//change the property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
contentType.PropertyTypes.First().Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -292,7 +414,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(doc.Edited);
//change back property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
contentType.PropertyTypes.First().Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
@@ -301,13 +423,17 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(doc.Edited);
}
[Test]
public void Change_Property_Type_From_Variant_Invariant()
[TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
[TestCase(ContentVariation.Culture, ContentVariation.Segment)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
public void Change_Property_Type_From_Variant_Invariant(ContentVariation variant, ContentVariation invariant)
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
// content type supports all variations
contentType.Variations = ContentVariation.Culture | ContentVariation.Segment;
var properties = CreatePropertyCollection(("title", variant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -320,33 +446,37 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
contentType.PropertyTypes.First().Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
contentType.PropertyTypes.First().Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
Assert.AreEqual("hello world", doc.GetValue("title", "en-US"));
}
[Test]
public void Change_Property_Type_From_Variant_Invariant_On_A_Composition()
[TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
[TestCase(ContentVariation.Culture, ContentVariation.Segment)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
public void Change_Property_Type_From_Variant_Invariant_On_A_Composition(ContentVariation variant, ContentVariation invariant)
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
// content type supports all variations
contentType.Variations = ContentVariation.Culture | ContentVariation.Segment;
var properties = CreatePropertyCollection(("title", variant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.Variations = contentType.Variations;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
@@ -362,7 +492,7 @@ namespace Umbraco.Tests.Services
ServiceContext.ContentService.Save(doc2);
//change the property type to be invariant
contentType.PropertyTypes.First().Variations = ContentVariation.Nothing;
contentType.PropertyTypes.First().Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
@@ -371,7 +501,7 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back property type to be variant
contentType.PropertyTypes.First().Variations = ContentVariation.Culture;
contentType.PropertyTypes.First().Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
@@ -380,19 +510,22 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc2.GetValue("title", "en-US"));
}
[Test]
public void Change_Content_Type_From_Variant_Invariant_On_A_Composition()
[TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
[TestCase(ContentVariation.Culture, ContentVariation.Segment)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
public void Change_Content_Type_From_Variant_Invariant_On_A_Composition(ContentVariation variant, ContentVariation invariant)
{
//create content type with a property type that varies by culture
var contentType = MockedContentTypes.CreateBasicContentType();
contentType.Variations = ContentVariation.Culture;
contentType.Variations = variant;
var properties = CreatePropertyCollection(("title", ContentVariation.Culture));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
//compose this from the other one
var contentType2 = MockedContentTypes.CreateBasicContentType("test");
contentType2.Variations = ContentVariation.Culture;
contentType2.Variations = contentType.Variations;
contentType2.AddContentType(contentType);
ServiceContext.ContentTypeService.Save(contentType2);
@@ -408,7 +541,7 @@ namespace Umbraco.Tests.Services
ServiceContext.ContentService.Save(doc2);
//change the content type to be invariant
contentType.Variations = ContentVariation.Nothing;
contentType.Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
@@ -417,7 +550,7 @@ namespace Umbraco.Tests.Services
Assert.AreEqual("hello world", doc2.GetValue("title"));
//change back content type to be variant
contentType.Variations = ContentVariation.Culture;
contentType.Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
doc = ServiceContext.ContentService.GetById(doc.Id); //re-get
doc2 = ServiceContext.ContentService.GetById(doc2.Id); //re-get
@@ -693,22 +826,25 @@ namespace Umbraco.Tests.Services
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
}
[Test]
public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized()
[TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
[TestCase(ContentVariation.Culture, ContentVariation.Segment)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Nothing)]
[TestCase(ContentVariation.CultureAndSegment, ContentVariation.Segment)]
public void Change_Property_Variations_From_Variant_To_Invariant_And_Ensure_Edited_Values_Are_Renormalized(ContentVariation variant, ContentVariation invariant)
{
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
CreateFrenchAndEnglishLangs();
var contentType = CreateContentType(ContentVariation.Culture);
var contentType = CreateContentType(ContentVariation.Culture | ContentVariation.Segment);
var properties = CreatePropertyCollection(("value1", ContentVariation.Culture));
var properties = CreatePropertyCollection(("value1", variant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
var document = (IContent)new Content("document", -1, contentType);
IContent document = new Content("document", -1, contentType);
document.SetCultureName("doc1en", "en");
document.SetCultureName("doc1fr", "fr");
document.SetValue("value1", "v1en-init", "en");
@@ -737,7 +873,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(document.Edited);
// switch property type to Invariant
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
document = ServiceContext.ContentService.GetById(document.Id);
@@ -764,7 +900,7 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(document.Edited);
// switch property back to Culture
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = variant;
ServiceContext.ContentTypeService.Save(contentType);
document = ServiceContext.ContentService.GetById(document.Id);
@@ -793,17 +929,20 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(document.Edited);
}
[Test]
public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized()
[TestCase(ContentVariation.Nothing, ContentVariation.Culture)]
[TestCase(ContentVariation.Nothing, ContentVariation.CultureAndSegment)]
[TestCase(ContentVariation.Segment, ContentVariation.Culture)]
[TestCase(ContentVariation.Segment, ContentVariation.CultureAndSegment)]
public void Change_Property_Variations_From_Invariant_To_Variant_And_Ensure_Edited_Values_Are_Renormalized(ContentVariation invariant, ContentVariation variant)
{
// one simple content type, variant, with both variant and invariant properties
// can change an invariant property to variant and back
CreateFrenchAndEnglishLangs();
var contentType = CreateContentType(ContentVariation.Culture);
var contentType = CreateContentType(ContentVariation.Culture | ContentVariation.Segment);
var properties = CreatePropertyCollection(("value1", ContentVariation.Nothing));
var properties = CreatePropertyCollection(("value1", invariant));
contentType.PropertyGroups.Add(new PropertyGroup(properties) { Name = "Content" });
ServiceContext.ContentTypeService.Save(contentType);
@@ -833,7 +972,7 @@ namespace Umbraco.Tests.Services
Assert.IsTrue(document.Edited);
// switch property type to Culture
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = variant;
ServiceContext.ContentTypeService.Save(contentType); //This is going to have to re-normalize the "Edited" flag
document = ServiceContext.ContentService.GetById(document.Id);
@@ -858,7 +997,7 @@ namespace Umbraco.Tests.Services
Assert.IsFalse(document.Edited);
// switch property back to Invariant
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = invariant;
ServiceContext.ContentTypeService.Save(contentType);
document = ServiceContext.ContentService.GetById(document.Id);
@@ -96,8 +96,7 @@ namespace Umbraco.Tests.Services
var properties = pmember.Properties.ToList();
for (var i = 0; i < aliases.Length; i++)
Assert.AreEqual(properties[i].Alias, aliases[i]);
Assert.IsTrue(properties.Select(x => x.Alias).ContainsAll(aliases));
var email = properties[aliases.IndexOf("Email")];
Assert.AreEqual("xemail", email.GetSourceValue());
@@ -329,6 +329,11 @@ namespace Umbraco.Tests.TestHelpers
{
throw new NotImplementedException();
}
public IReadOnlyDictionary<Udi, IEnumerable<string>> GetReferences(int id)
{
throw new NotImplementedException();
}
}
#endregion
@@ -259,7 +259,7 @@ namespace Umbraco.Tests.TestHelpers
var publishedSnapshotAccessor = new UmbracoContextPublishedSnapshotAccessor(Umbraco.Web.Composing.Current.UmbracoContextAccessor);
var variationContextAccessor = new TestVariationContextAccessor();
var service = new PublishedSnapshotService(
var service = new XmlPublishedSnapshotService(
ServiceContext,
Factory.GetInstance<IPublishedContentTypeFactory>(),
ScopeProvider,
@@ -357,14 +357,14 @@ namespace Umbraco.Tests.TestHelpers
protected UmbracoContext GetUmbracoContext(string url, int templateId = 1234, RouteData routeData = null, bool setSingleton = false, IUmbracoSettingsSection umbracoSettings = null, IEnumerable<IUrlProvider> urlProviders = null, IEnumerable<IMediaUrlProvider> mediaUrlProviders = null, IGlobalSettings globalSettings = null, IPublishedSnapshotService snapshotService = null)
{
// ensure we have a PublishedCachesService
var service = snapshotService ?? PublishedSnapshotService as PublishedSnapshotService;
var service = snapshotService ?? PublishedSnapshotService as XmlPublishedSnapshotService;
if (service == null)
throw new Exception("Not a proper XmlPublishedCache.PublishedCachesService.");
if (service is PublishedSnapshotService)
if (service is XmlPublishedSnapshotService)
{
// re-initialize PublishedCacheService content with an Xml source with proper template id
((PublishedSnapshotService)service).XmlStore.GetXmlDocument = () =>
((XmlPublishedSnapshotService)service).XmlStore.GetXmlDocument = () =>
{
var doc = new XmlDocument();
doc.LoadXml(GetXmlContent(templateId));
+1 -1
View File
@@ -511,7 +511,7 @@
<Compile Include="LegacyXmlPublishedCache\PublishedMediaCache.cs" />
<Compile Include="LegacyXmlPublishedCache\PublishedMemberCache.cs" />
<Compile Include="LegacyXmlPublishedCache\PublishedSnapshot.cs" />
<Compile Include="LegacyXmlPublishedCache\PublishedSnapshotService.cs" />
<Compile Include="LegacyXmlPublishedCache\XmlPublishedSnapshotService.cs" />
<Compile Include="LegacyXmlPublishedCache\RoutesCache.cs" />
<Compile Include="LegacyXmlPublishedCache\SafeXmlReaderWriter.cs" />
<Compile Include="LegacyXmlPublishedCache\UmbracoContextCache.cs" />
@@ -29,7 +29,7 @@ namespace Umbraco.Tests.Web.Mvc
[UmbracoTest(WithApplication = true)]
public class UmbracoViewPageTests : UmbracoTestBase
{
private PublishedSnapshotService _service;
private XmlPublishedSnapshotService _service;
[TearDown]
public override void TearDown()
@@ -421,7 +421,7 @@ namespace Umbraco.Tests.Web.Mvc
var scopeProvider = TestObjects.GetScopeProvider(Mock.Of<ILogger>());
var factory = Mock.Of<IPublishedContentTypeFactory>();
var umbracoContextAccessor = Mock.Of<IUmbracoContextAccessor>();
_service = new PublishedSnapshotService(svcCtx, factory, scopeProvider, cache,
_service = new XmlPublishedSnapshotService(svcCtx, factory, scopeProvider, cache,
null, null,
umbracoContextAccessor, null, null, null,
new TestDefaultCultureAccessor(),
@@ -42,6 +42,11 @@ gulp.task('dependencies', function () {
"src": ["./node_modules/angular/angular.js"],
"base": "./node_modules/angular"
},
{
"name": "angular-aria",
"src": ["./node_modules/angular-aria/angular-aria.min.js"],
"base": "./node_modules/angular-aria"
},
{
"name": "angular-cookies",
"src": ["./node_modules/angular-cookies/angular-cookies.js"],
@@ -100,7 +105,7 @@ gulp.task('dependencies', function () {
"name": "angular-messages",
"src": ["./node_modules/angular-messages/angular-messages.js"],
"base": "./node_modules/angular-messages"
},
},
{
"name": "angular-mocks",
"src": ["./node_modules/angular-mocks/angular-mocks.js"],
@@ -241,3 +241,7 @@ table th[class*="span"],
background-color: darken(@infoBackground, 5%);
}
}
.table .icon {
vertical-align: bottom;
}
+25 -39
View File
@@ -916,6 +916,11 @@
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.5.tgz",
"integrity": "sha512-kU/fHIGf2a4a3bH7E1tzALTHk+QfoUSCK9fEcMFisd6ZWvNDwPzXWAilItqOC3EDiAXPmGHaNc9/aXiD9xrAxQ=="
},
"angular-aria": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.7.5.tgz",
"integrity": "sha512-X2dGRw+PK7hrV7/X1Ns4e5P3KC/OBFi1l7z//D/v7zbZObsAx48qBoX7unsck+s4+mnO+ikNNkHG5N49VfAyRw=="
},
"angular-chart.js": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/angular-chart.js/-/angular-chart.js-1.1.1.tgz",
@@ -5302,8 +5307,7 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"aproba": {
"version": "1.2.0",
@@ -5324,14 +5328,12 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5346,20 +5348,17 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"core-util-is": {
"version": "1.0.2",
@@ -5476,8 +5475,7 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"ini": {
"version": "1.3.5",
@@ -5489,7 +5487,6 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -5504,7 +5501,6 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -5512,14 +5508,12 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"minipass": {
"version": "2.3.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.2",
"yallist": "^3.0.0"
@@ -5538,7 +5532,6 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -5619,8 +5612,7 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"object-assign": {
"version": "4.1.1",
@@ -5632,7 +5624,6 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -5718,8 +5709,7 @@
"safe-buffer": {
"version": "5.1.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"safer-buffer": {
"version": "2.1.2",
@@ -5755,7 +5745,6 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -5775,7 +5764,6 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -5819,14 +5807,12 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true
"dev": true
},
"yallist": {
"version": "3.0.3",
"bundled": true,
"dev": true,
"optional": true
"dev": true
}
}
},
@@ -8616,9 +8602,9 @@
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==",
"version": "4.17.13",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.13.tgz",
"integrity": "sha512-vm3/XWXfWtRua0FkUyEHBZy8kCPjErNBT9fJx8Zvs+U6zjqPbTUOpkaoum3O5uiA8sm+yNMHXfYkTUHFoMxFNA==",
"dev": true
},
"lodash._basecopy": {
@@ -8956,9 +8942,9 @@
}
},
"marked": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.5.2.tgz",
"integrity": "sha512-fdZvBa7/vSQIZCi4uuwo2N3q+7jJURpMVCcbaX0S1Mg65WZ5ilXvC67MviJAsdjqqgD+CEq4RKo5AYGgINkVAA==",
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.6.3.tgz",
"integrity": "sha512-Fqa7eq+UaxfMriqzYLayfqAE40WN03jf+zHjT18/uXNuzjq3TY0XTbrAoPeqSJrAmPz11VuUA+kBPYOhHt9oOQ==",
"dev": true
},
"matchdep": {
@@ -9356,9 +9342,9 @@
"dev": true
},
"nouislider": {
"version": "14.0.1",
"resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.1.tgz",
"integrity": "sha512-YNLKuABWYxmC5WXJ9TUj3N7+iyL/xT3+jm1mgOMXoqBhAL0Pj9BMgyKmLgwRnrxNN+C/fe7sFmpQDDPsxbMT2w=="
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.2.tgz",
"integrity": "sha512-N4AQStV4frh+XcLUwMI/hZpBP6tRboDE/4LZ7gzfxMVXFi/2J9URphnm40Ff4KEyrAVGSGaWApvljoMzTNWBlA=="
},
"npm": {
"version": "6.11.3",
+1
View File
@@ -10,6 +10,7 @@
"ace-builds": "1.4.2",
"angular": "1.7.5",
"angular-animate": "1.7.5",
"angular-aria": "1.7.5",
"angular-chart.js": "^1.1.1",
"angular-cookies": "1.7.5",
"angular-dynamic-locale": "0.1.37",
+1
View File
@@ -12,6 +12,7 @@ var app = angular.module('umbraco', [
'ngSanitize',
'ngTouch',
'ngMessages',
'ngAria',
'tmh.dynamicLocale',
'ngFileUpload',
'LocalStorageModule',
@@ -4,7 +4,7 @@
function ContentEditController($rootScope, $scope, $routeParams, $q, $window,
appState, contentResource, entityResource, navigationService, notificationsService,
serverValidationManager, contentEditingHelper, localizationService, formHelper, umbRequestHelper,
editorState, $http, eventsService, overlayService, $location, localStorageService) {
editorState, $http, eventsService, overlayService, $location, localStorageService, treeService) {
var evts = [];
var infiniteMode = $scope.infiniteModel && $scope.infiniteModel.infiniteMode;
@@ -201,6 +201,12 @@
$scope.page.buttonGroupState = "success";
}));
evts.push(eventsService.on("rte.shortcut.save", function(){
if ($scope.page.showSaveButton) {
$scope.save();
}
}));
evts.push(eventsService.on("content.saved", function(){
// Clear out localstorage keys that start with tinymce__
// When we save/perist a content node
@@ -305,7 +311,7 @@
}
/** Syncs the content item to it's tree node - this occurs on first load and after saving */
function syncTreeNode(content, path, initialLoad) {
function syncTreeNode(content, path, initialLoad, reloadChildren) {
if (infiniteMode || !path) {
return;
@@ -315,6 +321,9 @@
navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true })
.then(function (syncArgs) {
$scope.page.menu.currentNode = syncArgs.node;
if (reloadChildren && syncArgs.node.expanded) {
treeService.loadNodeChildren({node: syncArgs.node});
}
}, function () {
//handle the rejection
console.log("A problem occurred syncing the tree! A path is probably incorrect.")
@@ -446,7 +455,7 @@
//needs to be manually set for infinite editing mode
$scope.page.isNew = false;
syncTreeNode($scope.content, data.path);
syncTreeNode($scope.content, data.path, false, args.reloadChildren);
eventsService.emit("content.saved", { content: $scope.content, action: args.action });
@@ -851,7 +860,8 @@
return contentResource.publishWithDescendants(content, create, model.includeUnpublished, files, showNotifications);
},
action: "publishDescendants",
showNotifications: false
showNotifications: false,
reloadChildren: model.includeUnpublished
}).then(function (data) {
//show all notifications manually here since we disabled showing them automatically in the save method
formHelper.showNotifications(data);
@@ -26,6 +26,7 @@
@param {string} value Set the value of the checkbox.
@param {string} name Set the name of the checkbox.
@param {string} text Set the text for the checkbox label.
@param {string} labelKey Set a dictinary/localization string for the checkbox label
@param {string} serverValidationField Set the <code>val-server-field</code> of the checkbox.
@param {boolean} disabled Set the checkbox to be disabled.
@param {boolean} required Set the checkbox to be required.
@@ -35,13 +36,25 @@
(function () {
'use strict';
function UmbCheckboxController($timeout) {
function UmbCheckboxController($timeout, localizationService) {
var vm = this;
vm.$onInit = onInit;
vm.change = change;
function onInit() {
// If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [
if (vm.labelKey) {
localizationService.localize(vm.labelKey).then(function (data) {
if(data.indexOf('[') === -1){
vm.text = data;
}
});
}
}
function change() {
if (vm.onChange) {
$timeout(function () {
@@ -50,7 +63,7 @@
}
}
}
var component = {
templateUrl: 'views/components/forms/umb-checkbox.html',
controller: UmbCheckboxController,
@@ -61,6 +74,7 @@
value: "@",
name: "@",
text: "@",
labelKey: "@?",
serverValidationField: "@",
disabled: "<",
required: "<",
@@ -22,6 +22,7 @@
</pre>
@param {boolean} model Set to <code>true</code> or <code>false</code> to set the radiobutton to checked or unchecked.
@param {string} inputId Set the <code>id</code> of the radiobutton.
@param {string} value Set the value of the radiobutton.
@param {string} name Set the name of the radiobutton.
@param {string} text Set the text for the radiobutton label.
@@ -55,6 +56,7 @@
controllerAs: 'vm',
bindings: {
model: "=",
inputId: "@",
value: "@",
name: "@",
text: "@",
@@ -45,7 +45,7 @@ Use this directive to construct a title. Recommended to use it inside an {@link
(function(){
'use strict';
function BoxHeaderDirective() {
function BoxHeaderDirective(localizationService) {
var directive = {
restrict: 'E',
@@ -57,6 +57,26 @@ Use this directive to construct a title. Recommended to use it inside an {@link
title: "@?",
descriptionKey: "@?",
description: "@?"
},
link: function (scope) {
scope.titleLabel = scope.title;
if (scope.titleKey) {
localizationService.localize(scope.titleKey, [], scope.title).then((data) => {
scope.titleLabel = data;
});
}
scope.descriptionLabel = scope.description;
if (scope.descriptionKey) {
localizationService.localize(scope.descriptionKey, [], scope.description).then((data) => {
scope.descriptionLabel = data;
});
}
}
};
@@ -66,4 +86,4 @@ Use this directive to construct a title. Recommended to use it inside an {@link
angular.module('umbraco.directives').directive('umbBoxHeader', BoxHeaderDirective);
})();
})();
@@ -18,6 +18,7 @@ Use this directive to generate color swatches to pick from.
@param {string} size (<code>attribute</code>): The size (s, m).
@param {string} useLabel (<code>attribute</code>): Specify if labels should be used.
@param {string} useColorClass (<code>attribute</code>): Specify if color values are css classes.
@param {string} colorClassNamePrefix (<code>attribute</code>): Specify the prefix used for the class for each color (defaults to "btn").
@param {function} onSelect (<code>expression</code>): Callback function when the item is selected.
**/
@@ -32,6 +33,11 @@ Use this directive to generate color swatches to pick from.
if (angular.isUndefined(scope.useColorClass)) {
scope.useColorClass = false;
}
// Set default to "btn" if not defined
if (angular.isUndefined(scope.colorClassNamePrefix)) {
scope.colorClassNamePrefix = "btn";
}
scope.setColor = function (color, $index, $event) {
if (scope.onSelect) {
@@ -66,7 +72,8 @@ Use this directive to generate color swatches to pick from.
selectedColor: '=',
onSelect: '&',
useLabel: '=',
useColorClass: '=?'
useColorClass: '=?',
colorClassNamePrefix: '@?'
},
link: link
};
@@ -55,7 +55,8 @@ function confirmDirective() {
onConfirm: '=',
onCancel: '=',
caption: '@',
confirmButtonStyle: '@'
confirmButtonStyle: '@',
confirmLabelKey: '@'
},
link: function (scope, element, attr, ctrl) {
scope.showCancel = false;
@@ -0,0 +1,20 @@
/**
* @ngdoc filter
* @name umbraco.filters.filter:CMS_joinArray
* @namespace umbCmsJoinArray
*
* param {array} array of string or objects, if an object use the third argument to specify which prop to list.
* param {seperator} string containing the seperator to add between joined values.
* param {prop} string used if joining an array of objects, set the name of properties to join.
*
* @description
* Join an array of string or an array of objects, with a costum seperator.
*
*/
angular.module("umbraco.filters").filter('umbCmsJoinArray', function () {
return function join(array, separator, prop) {
return (!angular.isUndefined(prop) ? array.map(function (item) {
return item[prop];
}) : array).join(separator || '');
};
});
@@ -43,6 +43,30 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) {
"Failed to retrieve pre values for editor alias " + editorAlias);
},
/**
* @ngdoc method
* @name umbraco.resources.dataTypeResource#getReferences
* @methodOf umbraco.resources.dataTypeResource
*
* @description
* Retrieves references of a given data type.
*
* @param {Int} id id of datatype to retrieve references for
* @returns {Promise} resourcePromise object.
*
*/
getReferences: function (id) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"dataTypeApiBaseUrl",
"GetReferences",
{ id: id })),
"Failed to retrieve usages for data type of id " + id);
},
/**
* @ngdoc method
* @name umbraco.resources.dataTypeResource#getById
@@ -105,6 +105,20 @@ function macroResource($q, $http, umbRequestHelper) {
);
},
getGroupedParameterEditors: function () {
return umbRequestHelper.resourcePromise(
$http.get(umbRequestHelper.getApiUrl("macroApiBaseUrl", "GetGroupedParameterEditors"),
"Failed to get parameter editors")
);
},
getParameterEditorByAlias: function(alias) {
return umbRequestHelper.resourcePromise(
$http.get(umbRequestHelper.getApiUrl("macroApiBaseUrl", "GetParameterEditorByAlias", { "alias": alias }),
"Failed to get parameter editor")
);
},
getById: function(id) {
return umbRequestHelper.resourcePromise(
$http.get(umbRequestHelper.getApiUrl("macroApiBaseUrl", "GetById", { "id": id }), "Failed to get macro")
@@ -1143,6 +1143,14 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
prependToContext: true
});
// the editor frame catches Ctrl+S and handles it with the system save dialog
// - we want to handle it in the content controller, so we'll emit an event instead
editor.addShortcut('Ctrl+S', '', function () {
angularHelper.safeApply($rootScope, function() {
eventsService.emit("rte.shortcut.save");
});
});
},
insertLinkInEditor: function (editor, target, anchorElm) {
+2 -2
View File
@@ -138,14 +138,14 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService',
var toRetain = navigationService.retainQueryStrings(currentRouteParams, next.params);
//if toRetain is not null it means that there are missing query strings and we need to update the current params
//if toRetain is not null it means that there are missing query strings and we need to update the current params.
if (toRetain) {
$route.updateParams(toRetain);
}
//check if the location being changed is only due to global/state query strings which means the location change
//isn't actually going to cause a route change.
if (!toRetain && navigationService.isRouteChangingNavigation(currentRouteParams, next.params)) {
if (navigationService.isRouteChangingNavigation(currentRouteParams, next.params)) {
//The location change will cause a route change, continue the route if the query strings haven't been updated.
$route.reload();
@@ -6,6 +6,7 @@ LazyLoad.js([
'lib/angular-touch/angular-touch.js',
'lib/angular-sanitize/angular-sanitize.js',
'lib/angular-messages/angular-messages.js',
'lib/angular-aria/angular-aria.min.js',
'lib/underscore/underscore-min.js',
'lib/angular-ui-sortable/sortable.js',
'js/installer.app.js',
@@ -4,6 +4,7 @@
.umb-checkbox-list {
list-style: none;
margin-left: 0;
margin-top: 6px;
}
.umb-checkbox-list__item {
@@ -12,6 +13,10 @@
margin-bottom: 2px;
}
.umb-checkbox-list li:first-child {
font-weight: bold;
}
.umb-checkbox-list__item:last-child {
border-bottom: none;
}
@@ -6,6 +6,10 @@
span.flatpickr-day {
border-radius: @baseBorderRadius;
border: none;
&.today:not(.active) {
border: 1px solid;
}
}
span.flatpickr-day:hover {
@@ -36,6 +36,10 @@ a.umb-list-item:focus {
color: @gray-4;
}
.umb-list-item__description--checkbox{
margin: 0 0 0 26px;
}
.umb-list-checkbox {
position: absolute;
opacity: 0;
@@ -27,14 +27,13 @@
}
.umb-table__action,
.umb-table a {
background: transparent;
border: 0 none;
text-decoration: none;
cursor: pointer;
&:focus {
outline: none;
text-decoration: none;
}
padding: 0;
margin-left: 1px;
}
input.umb-table__input {
@@ -51,20 +50,26 @@ input.umb-table__input {
}
.umb-table-head__link {
background: transparent;
border: 0 none;
position: relative;
cursor: default;
text-decoration: none;
color: @gray-3;
font-size: inherit;
font-weight: inherit;
padding: 0 1px;
&:hover {
text-decoration: none;
cursor: default;
color: @gray-3;
}
}
.umb-table-head__link .sortable {
.umb-table-head__link.sortable {
cursor: pointer;
&:hover {
text-decoration: none;
cursor: pointer;
color: @black;
}
}
@@ -136,15 +141,15 @@ input.umb-table__input {
}
.umb-table-body__link {
color: @ui-option-type;
font-size: 14px;
font-weight: bold;
text-decoration: none;
&:hover, &:focus {
color: @ui-option-type-hover;
text-decoration: underline;
outline: none;
}
}
@@ -155,6 +160,7 @@ input.umb-table__input {
font-size: 20px;
line-height: 20px;
color: @ui-option-type;
vertical-align: bottom;
}
.umb-table-body__checkicon,
@@ -240,7 +246,7 @@ input.umb-table__input {
.umb-table-cell {
display: flex;
flex-flow: row nowrap;
flex: 1 1 1%; //NOTE 1% is a Internet Explore hack, so that cells don't collapse
flex: 1 1 5%;
position: relative;
margin: auto 14px;
padding: 6px 2px;
@@ -253,6 +259,11 @@ input.umb-table__input {
white-space: nowrap; //NOTE Disable/Enable this to keep textstring on one line
text-overflow: ellipsis;
}
.umb-table-cell.--noOverflow > * {
overflow: visible;
white-space: normal;
text-overflow: unset;
}
.umb-table-cell:first-of-type:not(.not-fixed) {
flex: 0 0 25px;
@@ -264,6 +275,9 @@ input.umb-table__input {
flex: 0 0 auto !important;
}
.umb-table-cell--nano {
flex: 0 0 50px;
}
.umb-table-cell--small {
flex: .5 .5 1%;
max-width: 12.5%;
@@ -280,8 +294,8 @@ input.umb-table__input {
// Increases the space for the name cell
.umb-table__name {
flex: 1 1 25%;
max-width: 25%;
flex: 1 1 20%;
max-width: 300px;
}
.umb-table__loading-overlay {
@@ -111,11 +111,10 @@ iframe, .content-column-body {
}
.pa-select-type label {
padding: 0 20px;
padding: 0 15px;
}
.pa-access-header {
font-weight: bold;
margin: 0 0 3px 0;
padding-bottom: 0;
}
@@ -48,6 +48,10 @@
flex-direction: row;
}
.date-wrapper-mini--checkbox{
margin: 0 0 0 26px;
}
.date-wrapper-mini__date {
display: flex;
+13 -1
View File
@@ -62,7 +62,7 @@ table {
}
.table tr > td:first-child {
.table:not(.table-bordered) tr > td:first-child {
border-left: 4px solid transparent;
}
.table tr.--selected > td:first-child {
@@ -263,3 +263,15 @@ table th[class*="span"],
.table-sortable tbody tr {
cursor: move;
}
.table__action-overlay{
background: transparent;
border: 0 none;
padding: 0;
font-style: italic;
&:focus,
&:hover{
text-decoration: underline;
}
}
@@ -62,7 +62,7 @@
<div ng-if="vm.availableGroups.length > 0">
<ul class="umb-checkbox-list" ng-repeat="group in vm.availableGroups | filter:searchTerm">
<li style="font-weight: bold" ng-show="vm.availableGroups.length > 1">
<li ng-show="vm.availableGroups.length > 1">
<i class="icon-folder umb-checkbox-list__item-icon"></i>
{{group.containerPath}}
</li>
@@ -77,7 +77,8 @@
checklist-model="model.compositeContentTypes"
checklist-value="compositeContentType.contentType.alias"
ng-change="model.selectCompositeContentType(compositeContentType.contentType)"
ng-disabled="compositeContentType.allowed===false || compositeContentType.inherited" />
ng-disabled="compositeContentType.allowed===false || compositeContentType.inherited" />
</div>
<label for="umb-overlay-comp-{{compositeContentType.contentType.key}}" class="umb-checkbox-list__item-text" ng-class="{'-faded': compositeContentType.allowed===false}">
@@ -10,7 +10,7 @@
(function() {
"use strict";
function DataTypePicker($scope, $filter, dataTypeResource, dataTypeHelper, contentTypeResource, localizationService, editorService) {
function DataTypePicker($scope, $filter, dataTypeResource, contentTypeResource, localizationService, editorService) {
var vm = this;
@@ -122,10 +122,18 @@
vm.showTabs = false;
var regex = new RegExp(vm.searchTerm, "i");
var userConfigured = filterCollection(vm.userConfigured, regex),
typesAndEditors = filterCollection(vm.typesAndEditors, regex);
var totalResults = _.reduce(_.pluck(_.union(userConfigured, typesAndEditors), 'count'), (m, n) => m + n, 0);
vm.filterResult = {
userConfigured: filterCollection(vm.userConfigured, regex),
typesAndEditors: filterCollection(vm.typesAndEditors, regex)
userConfigured: userConfigured,
typesAndEditors: typesAndEditors,
totalResults: totalResults
};
} else {
vm.filterResult = null;
vm.showTabs = true;
@@ -134,11 +142,15 @@
function filterCollection(collection, regex) {
return _.map(_.keys(collection), function (key) {
var filteredDataTypes = $filter('filter')(collection[key], function (dataType) {
return regex.test(dataType.name) || regex.test(dataType.alias);
});
return {
group: key,
dataTypes: $filter('filter')(collection[key], function (dataType) {
return regex.test(dataType.name) || regex.test(dataType.alias);
})
count: filteredDataTypes.length,
dataTypes: filteredDataTypes
}
});
}
@@ -150,7 +162,6 @@
propertyDetails.title = property.name;
$scope.model.itemDetails = propertyDetails;
}
function hideDetailsOverlay() {
@@ -177,7 +188,6 @@
};
editorService.open(dataTypeSettings);
}
function pickDataType(selectedDataType) {
@@ -205,7 +215,7 @@
}
function close() {
if($scope.model.close) {
if ($scope.model.close) {
$scope.model.close();
}
}
@@ -20,17 +20,19 @@
<div class="form-search">
<i class="icon-search"></i>
<input type="text"
style="width: 100%"
ng-change="vm.filterItems()"
ng-model="vm.searchTerm"
class="umb-search-field search-query search-input input-block-level"
localize="placeholder"
placeholder="@placeholders_filter"
umb-auto-focus
no-dirty-check />
style="width: 100%"
ng-change="vm.filterItems()"
ng-model="vm.searchTerm"
class="umb-search-field search-query search-input input-block-level"
localize="placeholder"
placeholder="@placeholders_filter"
umb-auto-focus
no-dirty-check />
</div>
</div>
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<!-- TABS -->
<div ng-if="vm.showTabs">
<umb-tabs-nav
@@ -80,47 +82,56 @@
</div>
</umb-tab-content>
</div>
<!-- FILTER RESULTS -->
<div ng-if="vm.filterResult">
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_reuse"></localize></h5>
<div ng-repeat="result in vm.filterResult.userConfigured">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="dataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
</span>
</a>
</li>
</ul>
<div ng-if="vm.filterResult.totalResults > 0">
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_reuse"></localize></h5>
<div ng-repeat="result in vm.filterResult.userConfigured">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="dataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{dataType.name}}">
<span>
<i class="{{dataType.icon}}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{dataType.name}}
</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_availableEditors"></localize></h5>
<div ng-repeat="result in vm.filterResult.typesAndEditors">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="systemDataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(systemDataType)"
ng-click="vm.pickEditor(systemDataType)">
<a class="umb-card-grid-item" href="" title="{{ systemDataType.name }}">
<span>
<i class="{{ systemDataType.icon }}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{ systemDataType.name }}
</span>
</a>
</li>
</ul>
<div ng-if="vm.filterResult.totalResults > 0">
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_availableEditors"></localize></h5>
<div ng-repeat="result in vm.filterResult.typesAndEditors">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="systemDataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(systemDataType)"
ng-click="vm.pickEditor(systemDataType)">
<a class="umb-card-grid-item" href="" title="{{systemDataType.name}}">
<span>
<i class="{{systemDataType.icon}}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{systemDataType.name}}
</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<umb-empty-state position="center" ng-if="vm.filterResult.totalResults === 0">
<localize key="general_searchNoResult"></localize>
</umb-empty-state>
</div>
</umb-box-content>
@@ -0,0 +1,127 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.MacroParameterPickerController
* @function
*
* @description
* The controller for the content type editor macro parameter dialog
*/
(function() {
"use strict";
function MacroParameterController($scope, $filter, macroResource, localizationService, editorService) {
var vm = this;
vm.searchTerm = "";
vm.parameterEditors = [];
vm.loading = false;
vm.labels = {};
vm.filterItems = filterItems;
vm.showDetailsOverlay = showDetailsOverlay;
vm.hideDetailsOverlay = hideDetailsOverlay;
vm.pickParameterEditor = pickParameterEditor;
vm.close = close;
function init() {
setTitle();
getGroupedParameterEditors();
}
function setTitle() {
if (!$scope.model.title) {
localizationService.localize("defaultdialogs_selectEditor")
.then(function(data){
$scope.model.title = data;
});
}
}
function getGroupedParameterEditors() {
vm.loading = true;
macroResource.getGroupedParameterEditors().then(function (data) {
vm.parameterEditors = data;
vm.loading = false;
}, function () {
vm.loading = false;
});
}
function filterItems() {
// clear item details
$scope.model.itemDetails = null;
if (vm.searchTerm) {
var regex = new RegExp(vm.searchTerm, "i");
var parameterEditors = filterCollection(vm.parameterEditors, regex);
var totalResults = _.reduce(_.pluck(parameterEditors, 'count'), (m, n) => m + n, 0);
vm.filterResult = {
parameterEditors: parameterEditors,
totalResults: totalResults
};
} else {
vm.filterResult = null;
}
}
function filterCollection(collection, regex) {
return _.map(_.keys(collection), function (key) {
var filteredEditors = $filter('filter')(collection[key], function (editor) {
return regex.test(editor.name) || regex.test(editor.alias);
});
return {
group: key,
count: filteredEditors.length,
parameterEditors: filteredEditors
}
});
}
function showDetailsOverlay(property) {
var propertyDetails = {};
propertyDetails.icon = property.icon;
propertyDetails.title = property.name;
$scope.model.itemDetails = propertyDetails;
}
function hideDetailsOverlay() {
$scope.model.itemDetails = null;
}
function pickParameterEditor(selectedParameterEditor) {
console.log("pickParameterEditor", selectedParameterEditor);
console.log("$scope.model", $scope.model);
$scope.model.parameter.editor = selectedParameterEditor.alias;
$scope.model.parameter.dataTypeName = selectedParameterEditor.name;
$scope.model.parameter.dataTypeIcon = selectedParameterEditor.icon;
$scope.model.submit($scope.model);
}
function close() {
if ($scope.model.close) {
$scope.model.close();
}
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.MacroParameterPickerController", MacroParameterController);
})();
@@ -0,0 +1,106 @@
<div ng-controller="Umbraco.Editors.MacroParameterPickerController as vm">
<umb-editor-view data-element="editor-macro-parameter">
<form novalidate name="MacroParameterForm" val-form-manager>
<umb-editor-header
name="model.title"
name-locked="true"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<umb-editor-container>
<umb-box>
<umb-box-content>
<!-- FILTER -->
<div class="umb-control-group -no-border">
<div class="form-search">
<i class="icon-search"></i>
<input type="text"
style="width: 100%"
ng-change="vm.filterItems()"
ng-model="vm.searchTerm"
class="umb-search-field search-query search-input input-block-level"
localize="placeholder"
placeholder="@placeholders_filter"
umb-auto-focus
no-dirty-check />
</div>
</div>
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<div ng-if="!vm.filterResult">
<div ng-repeat="(key,value) in vm.parameterEditors">
<h5>{{key}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="parameterEditor in value | orderBy:'name'"
data-element="editor-{{parameterEditor.name}}"
ng-mouseover="vm.showDetailsOverlay(parameterEditor)"
ng-click="vm.pickParameterEditor(parameterEditor)">
<a class="umb-card-grid-item" href="" title="{{parameterEditor.name}}">
<span>
<i class="{{parameterEditor.icon}}" ng-class="{'icon-autofill': parameterEditor.icon == null}"></i>
{{parameterEditor.name}}
</span>
</a>
</li>
</ul>
</div>
</div>
<!-- FILTER RESULTS -->
<div ng-if="vm.filterResult">
<div ng-if="vm.filterResult.totalResults > 0">
<div ng-repeat="result in vm.filterResult.parameterEditors">
<div ng-if="result.parameterEditors.length > 0">
<h5>{{result.group}}</h5>
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="parameterEditor in result.parameterEditors | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(parameterEditor)"
ng-click="vm.pickParameterEditor(parameterEditor)">
<div ng-if="parameterEditor.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{parameterEditor.name}}">
<span>
<i class="{{parameterEditor.icon}}" ng-class="{'icon-autofill': parameterEditor.icon == null}"></i>
{{parameterEditor.name}}
</span>
</a>
</li>
</ul>
</div>
</div>
</div>
<umb-empty-state position="center" ng-if="vm.filterResult.totalResults === 0">
<localize key="general_searchNoResult"></localize>
</umb-empty-state>
</div>
</umb-box-content>
</umb-box>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</form>
</umb-editor-view>
</div>
@@ -41,8 +41,6 @@ function MacroPickerController($scope, entityResource, macroResource, umbPropEdi
macroResource.getMacroParameters($scope.model.selectedMacro.id)
.then(function (data) {
//go to next page if there are params otherwise we can just exit
if (!angular.isArray(data) || data.length === 0) {
@@ -32,10 +32,10 @@
<ul class="umb-card-grid -three-in-row">
<li ng-repeat="availableItem in macros | orderBy:'name' | filter:searchTerm"
ng-click="selectMacro(availableItem)">
<a class="umb-card-grid-item" href="" title="{{ availableItem.name }}">
<a class="umb-card-grid-item" href="" title="{{availableItem.name}}">
<span>
<i class="icon-settings-alt"></i>
{{ availableItem.name }}
{{availableItem.name}}
</span>
</a>
</li>
@@ -10,9 +10,14 @@
<button type="button" class="umb-language-picker__toggle" ng-click="toggleLanguageSelector()" aria-haspopup="true" aria-expanded="{{page.languageSelectorIsOpen}}">
<span>
<span class="sr-only">
<<<<<<< HEAD
<localize key="visuallyHiddenTexts_currentLanguage">Current language</localize>
<span>: </span>
=======
<localize key="visuallyHiddenTexts_currentLanguage">Current language</localize>:
>>>>>>> v8/dev
</span>
<span>: {{selectedLanguage.name}}</span>
<span>{{selectedLanguage.name}}</span>
</span>
<i class="umb-language-picker__expand" ng-class="{'icon-navigation-down': !page.languageSelectorIsOpen, 'icon-navigation-up': page.languageSelectorIsOpen}" class="icon-navigation-right" aria-hidden="true"></i>
</button>
@@ -25,9 +30,14 @@
ng-repeat="language in languages"
>
<span class="sr-only">
<<<<<<< HEAD
<localize key="visuallyHiddenTexts_switchLanguage">Switch language to</localize>
<span>: </span>
=======
<localize key="visuallyHiddenTexts_switchLanguage">Switch language to</localize>:
>>>>>>> v8/dev
</span>
<span>: {{language.name}}</span>
<span>{{language.name}}</span>
</button>
</div>
</div>
@@ -15,5 +15,6 @@
<i class="umb-form-check__icon icon-check"></i>
</span>
</span>
<span class="umb-form-check__text">{{vm.text}}</span>
</label>
@@ -1,12 +1,13 @@
<label class="radio umb-form-check umb-form-check--radiobutton" ng-class="{ 'umb-form-check--disabled': vm.disabled }">
<input type="radio"
name="{{vm.name}}"
value="{{vm.value}}"
class="umb-form-check__input"
ng-model="vm.model"
ng-disabled="vm.disabled"
ng-required="vm.required"
ng-change="vm.change()" />
id="{{vm.inputId}}"
name="{{vm.name}}"
value="{{vm.value}}"
class="umb-form-check__input"
ng-model="vm.model"
ng-disabled="vm.disabled"
ng-required="vm.required"
ng-change="vm.change()" />
<span class="umb-form-check__state" aria-hidden="true">
<span class="umb-form-check__check"></span>
@@ -1,13 +1,11 @@
<div class="umb-box-header">
<div>
<div class="umb-box-header-title" ng-if="title || titleKey">
<localize ng-if="titleKey" key="{{titleKey}}"></localize>
<span ng-if="title">{{title}}</span>
<div class="umb-box-header-title" ng-if="titleLabel">
{{titleLabel}}
</div>
<div class="umb-box-header-description" ng-if="description || descriptionKey">
<localize ng-if="descriptionKey" key="{{descriptionKey}}"></localize>
<span ng-if="description">{{description}}</span>
<div class="umb-box-header-description" ng-if="descriptionLabel">
{{descriptionLabel}}
</div>
</div>
<ng-transclude></ng-transclude>
</div>
</div>
@@ -1,6 +1,6 @@
<div class="umb-color-swatches" ng-class="{ 'with-labels': useLabel }">
<button type="button" class="umb-color-box umb-color-box--{{size}} btn-{{color.value}}" ng-repeat="color in colors" title="{{useLabel || useColorClass ? (color.label || color.value) : ('#' + color.value)}}" hex-bg-inline="{{useColorClass === false}}" hex-bg-color="{{color.value}}" ng-class="{ 'active': isSelectedColor(color) }" ng-click="setColor(color, $index, $event)">
<button type="button" class="umb-color-box umb-color-box--{{size}} {{colorClassNamePrefix}}-{{color.value}}" ng-repeat="color in colors" title="{{useLabel || useColorClass ? (color.label || color.value) : ('#' + color.value)}}" hex-bg-inline="{{useColorClass === false}}" hex-bg-color="{{color.value}}" ng-class="{ 'active': isSelectedColor(color) }" ng-click="setColor(color, $index, $event)">
<div class="umb-color-box-inner">
<div class="check_circle">
<i class="icon icon-check small" ng-show="isSelectedColor(color)"></i>
@@ -14,7 +14,7 @@
action="confirm()"
button-style="{{confirmButtonStyle || 'primary'}}"
state="confirmButtonState"
label-key="general_ok">
label-key="{{confirmLabelKey || 'general_ok'}}">
</umb-button>
</div>
</div>
@@ -6,15 +6,13 @@
ng-class="{'-selected': item.selected}"
ng-click="clickItem(item, $event, $index)">
<!--<i ng-if="item.selected" class="icon-check umb-content-grid__checkmark"></i>-->
<div class="umb-content-grid__content">
<a class="umb-content-grid__item-name"
ng-href="{{'#' + item.editPath}}"
ng-click="clickItemName(item, $event, $index)"
ng-class="{'-light': !item.published && item.updater != null}">
<i class="umb-content-grid__icon {{ item.icon }}"></i>
<i class="umb-content-grid__icon {{ item.icon }}" aria-hidden="true"></i>
<span>{{ item.name }}</span>
</a>
@@ -5,29 +5,33 @@
<div class="umb-table-row">
<div class="umb-table-cell">
<a href="" style="text-decoration: none;" ng-show="vm.allowSelectAll" ng-click="vm.selectAll()">
<button type="button" class="umb-table__action" ng-show="vm.allowSelectAll" ng-click="vm.selectAll()">
<umb-checkmark checked="vm.isSelectedAll()" size="xs"></umb-checkmark>
</a>
</button>
</div>
<div class="umb-table-cell umb-table__name">
<a class="umb-table-head__link sortable" href="#"
<button
type="button"
class="umb-table-head__link sortable"
ng-click="vm.sort('Name', true, true)"
prevent-default>
<localize key="general_name">Name</localize>
<i class="umb-table-head__icon icon" ng-class="{'icon-navigation-up': vm.isSortDirection('Name', 'asc'), 'icon-navigation-down': vm.isSortDirection('Name', 'desc')}"></i>
</a>
<i class="umb-table-head__icon icon" aria-hidden="true" ng-class="{'icon-navigation-up': vm.isSortDirection('Name', 'asc'), 'icon-navigation-down': vm.isSortDirection('Name', 'desc')}"></i>
</button>
</div>
<div class="umb-table-cell" ng-show="vm.items[0].state">
<localize key="general_status">Status</localize>
</div>
<div class="umb-table-cell" ng-repeat="column in vm.itemProperties track by column.alias">
<a class="umb-table-head__link" href="#"
ng-click="vm.sort(column.alias, column.allowSorting, column.isSystem)"
ng-class="{'sortable':column.allowSorting}" prevent-default>
<button
type="button"
class="umb-table-head__link"
ng-click="vm.sort(column.alias, column.allowSorting, column.isSystem)"
ng-class="{'sortable':column.allowSorting}" prevent-default>
<span ng-bind="column.header"></span>
<i class="umb-table-head__icon icon" ng-class="{'icon-navigation-up': vm.isSortDirection(column.alias, 'asc'), 'icon-navigation-down': vm.isSortDirection(column.alias, 'desc')}"></i>
</a>
<i class="umb-table-head__icon icon" aria-hidden="true" ng-class="{'icon-navigation-up': vm.isSortDirection(column.alias, 'asc'), 'icon-navigation-down': vm.isSortDirection(column.alias, 'desc')}"></i>
</button>
</div>
</div>
</div>
@@ -39,8 +43,8 @@
ng-click="vm.selectItem(item, $index, $event)">
<div class="umb-table-cell">
<i class="umb-table-body__icon umb-table-body__fileicon {{item.icon}}" ng-class="vm.getIcon(item)"></i>
<i class="umb-table-body__icon umb-table-body__checkicon icon-check"></i>
<i class="umb-table-body__icon umb-table-body__fileicon {{item.icon}}" aria-hidden="true" ng-class="vm.getIcon(item)"></i>
<i class="umb-table-body__icon umb-table-body__checkicon icon-check" aria-hidden="true"></i>
</div>
<div class="umb-table-cell umb-table__name">
<a title="{{ item.name }}" class="umb-table-body__link"
@@ -22,7 +22,8 @@ function contentCreateController($scope,
function initialize() {
$scope.loading = true;
$scope.allowedTypes = null;
$scope.countTypes = contentTypeResource.getCount;
var getAllowedTypes = contentTypeResource.getAllowedTypes($scope.currentNode.id).then(function (data) {
$scope.allowedTypes = iconHelper.formatContentTypeIcons(data);
});
@@ -97,7 +98,7 @@ function contentCreateController($scope,
$scope.close = function() {
close();
}
};
$scope.closeDialog = function (showMenu) {
navigationService.hideDialog(showMenu);
@@ -106,12 +107,12 @@ function contentCreateController($scope,
$scope.createContentType = function () {
$location.path("/settings/documenttypes/edit/-1").search("create", "true");
close();
}
};
$scope.editContentType = function () {
$location.path("/settings/documenttypes/edit/" + $scope.contentTypeId).search("view", "permissions");
close();
}
};
$scope.createBlank = createBlank;
$scope.createOrSelectBlueprintIfAny = createOrSelectBlueprintIfAny;
@@ -80,20 +80,6 @@ function ContentDeleteController($scope, $timeout, contentResource, treeService,
$scope.close = function () {
navigationService.hideDialog();
};
languageResource.getAll().then(function (data) {
$scope.hasMoreThanOneLanguage = data.length > 1;
}, function (err) {
toggleDeleting(false);
//check if response is ysod
if (err.status && err.status >= 500) {
// TODO: All YSOD handling should be done with an interceptor
overlayService.ysod(err);
}
});
}
angular.module("umbraco").controller("Umbraco.Editors.Content.DeleteController", ContentDeleteController);
@@ -19,10 +19,16 @@
</button>
</div>
<div ng-if="hasSettingsAccess && currentNode.id < 0">
<p class="abstract">
<!-- There are existing document types, but none are allowed at root -->
<p class="abstract" ng-if="countTypes > 0">
<localize key="create_noDocumentTypesAllowedAtRoot" />
</p>
<!-- There's no document types, help people create one -->
<p class="abstract" ng-if="countTypes === 0">
<localize key="create_noDocumentTypesAtRoot" />
</p>
<button class="btn umb-outline" ng-click="createContentType()">
<button class="btn umb-outline" ng-click="createContentType()" ng-if="countTypes === 0">
<localize key="create_noDocumentTypesCreateNew" />
</button>
</div>
@@ -14,10 +14,14 @@
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong>?
</p>
<div class="umb-alert umb-alert--warning" ng-show="hasMoreThanOneLanguage">
<div class="umb-alert umb-alert--warning" ng-show="currentNode.metaData.variesByCulture && !currentNode.trashed">
<localize key="defaultdialogs_variantdeletewarning">This will delete the node and all its languages. If you only want to delete one language go and unpublish it instead.</localize>
</div>
<div class="umb-alert umb-alert--warning" ng-show="currentNode.trashed">
<localize key="defaultdialogs_recycleBinWarning">When items are deleted from the recycle bin, they will be gone forever</localize>.
</div>
<umb-confirm on-confirm="performDelete" confirm-button-style="danger" on-cancel="cancel"></umb-confirm>
</div>
@@ -4,12 +4,12 @@
<umb-loader ng-show="busy"></umb-loader>
<p class="abstract">
<localize key="defaultdialogs_recycleBinWarning">When items are deleted from the recycle bin, they will be gone forever</localize>.
<div class="umb-alert umb-alert--warning">
<p><localize key="defaultdialogs_recycleBinWarning">When items are deleted from the recycle bin, they will be gone forever</localize>.</p>
<localize key="general_areyousure">Are you sure?</localize>
</p>
</div>
<umb-confirm on-confirm="performDelete" on-cancel="cancel">
<umb-confirm on-confirm="performDelete" on-cancel="cancel" confirm-button-style="danger">
</umb-confirm>
</umb-pane>
</div>
@@ -13,32 +13,30 @@
<div class="umb-list-item"
ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter track by variant.compositeId">
<ng-form name="publishVariantSelectorForm">
<div class="flex" ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<div ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<umb-checkbox input-id="{{variant.htmlId}}"
name="publishVariantSelector"
model="variant.publish"
on-change="vm.changeSelection(variant)"
disabled="(variant.canPublish === false)"
server-validation-field="{{variant.htmlId}}" />
server-validation-field="{{variant.htmlId}}"
text="{{ variant.language.name }}"
/>
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<span class="db umb-list-item__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - </span>
<span ng-if="variant.language.isMandatory" ng-class="{'text-error': (variant.language.isMandatory && variant.willPublish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-list-item__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
<span class="db umb-list-item__description umb-list-item__description--checkbox text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</label>
</div>
</div>
@@ -6,12 +6,11 @@
</div>
<div class="flex mb3">
<umb-checkbox input-id="includeUnpublishedSelector" model="model.includeUnpublished" />
<label for="includeUnpublishedSelector">
<localize key="content_includeUnpublished"></localize>
</label>
<umb-checkbox
model="model.includeUnpublished"
text="Include drafts: also publish unpublished content items."
label-key="content_includeUnpublished"
/>
</div>
</div>
@@ -23,12 +22,11 @@
</div>
<div class="flex mb3">
<umb-checkbox input-id="includeUnpublishedSelector" model="model.includeUnpublished" />
<label for="includeUnpublishedSelector">
<localize key="content_includeUnpublished"></localize>
</label>
<umb-checkbox
model="model.includeUnpublished"
text="Include drafts: also publish unpublished content items."
label-key="content_includeUnpublished"
/>
</div>
<div class="bold mb1">
@@ -39,17 +37,17 @@
<div class="umb-list-item umb-list--condensed" ng-repeat="variant in vm.variants">
<ng-form name="publishVariantSelectorForm">
<div class="flex" ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<div ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<umb-checkbox
input-id="{{variant.htmlId}}"
name="publishVariantSelector"
model="variant.publish"
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}"/>
server-validation-field="{{variant.htmlId}}"
text="{{variant.language.name}}"/>
<div>
<label for="{{variant.htmlId}}" class="mb0">
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<span>{{variant.language.name}}</span>
<span class="db umb-list-item__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
@@ -58,12 +56,10 @@
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-list-item__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
<span class="db umb-list-item__description umb-list-item__description--checkbox text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</label>
</div>
</div>
@@ -17,31 +17,29 @@
<div class="umb-list-item"
ng-repeat="variant in vm.variants | filter:vm.dirtyVariantFilter track by variant.compositeId">
<ng-form name="saveVariantSelectorForm">
<div class="flex" ng-class="{'umb-list-item--error': saveVariantSelectorForm.saveVariantSelector.$invalid}">
<div ng-class="{'umb-list-item--error': saveVariantSelectorForm.saveVariantSelector.$invalid}">
<umb-checkbox
input-id="{{variant.htmlId}}"
name="saveVariantSelector"
model="variant.save"
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}"/>
server-validation-field="{{variant.htmlId}}"
text="{{variant.language.name}}"
/>
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<span class="db" ng-if="!saveVariantSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-list-item__description" variant="variant"></umb-variant-state>
<umb-variant-state class="umb-list-item__description umb-list-item__description--checkbox" variant="variant"></umb-variant-state>
</span>
<span class="db" ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
<span class="db umb-list-item__description text-error" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
<span class="db umb-list-item__description umb-list-item__description--checkbox text-error" ng-message="valServerField">{{saveVariantSelectorForm.saveVariantSelector.errorMsg}}</span>
</span>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</label>
</div>
</div>
@@ -86,27 +86,25 @@
<div class="umb-list-item" ng-repeat="variant in vm.variants">
<ng-form name="scheduleSelectorForm" style="width:100%;">
<div class="flex" ng-class="{'umb-list-item--error': scheduleSelectorForm.saveVariantReleaseDate.$invalid}">
<div ng-class="{'umb-list-item--error': scheduleSelectorForm.saveVariantReleaseDate.$invalid}">
<umb-checkbox
input-id="{{'saveVariantSelector_' + variant.language.culture}}"
name="saveVariantSelector"
model="variant.save"
on-change="vm.changeSelection(variant)"/>
on-change="vm.changeSelection(variant)"
text="{{variant.language.name}}"
/>
<div>
<label for="{{'saveVariantSelector_' + variant.language.culture}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<span class="db umb-list-item__description"
<span class="db umb-list-item__description umb-list-item__description--checkbox"
ng-if="!scheduleSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-show="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
</label>
<div class="date-wrapper-mini">
<div class="date-wrapper-mini date-wrapper-mini--checkbox">
<div class="date-wrapper-mini__date" ng-if="vm.dirtyVariantFilter(variant) && (variant.releaseDate || variant.save)">
<div style="font-size: 13px; margin-right: 5px;">Publish:<em ng-show="!variant.save">&nbsp;&nbsp;{{variant.releaseDateFormatted}}</em></div>
@@ -12,31 +12,29 @@
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.modifiedVariantFilter">
<ng-form name="publishVariantSelectorForm">
<div class="flex" ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<div ng-class="{'umb-list-item--error': publishVariantSelectorForm.publishVariantSelector.$invalid}">
<umb-checkbox
input-id="{{variant.htmlId}}"
name="publishVariantSelector"
model="variant.save"
on-change="vm.changeSelection(variant)"
server-validation-field="{{variant.htmlId}}"/>
server-validation-field="{{variant.htmlId}}"
text="{{ variant.language.name }}"
/>
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<span class="db" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-list-item__description" variant="variant"></umb-variant-state>
</span>
<span class="db" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-list-item__description umb-list-item__description--checkbox" variant="variant"></umb-variant-state>
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-list-item__description text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
<span class="db umb-list-item__description umb-list-item__description--checkbox text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
</span>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</label>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</div>
</div>
@@ -15,7 +15,7 @@
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.publishedVariantFilter">
<ng-form name="unpublishVariantSelectorForm">
<div class="flex">
<div>
<umb-checkbox
input-id="{{variant.htmlId}}"
@@ -23,17 +23,15 @@
model="variant.save"
on-change="vm.changeSelection(variant)"
disabled="variant.disabled"
server-validation-field="{{variant.htmlId}}"/>
server-validation-field="{{variant.htmlId}}"
text="{{ variant.language.name }}"
/>
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<span class="db umb-list-item__description">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
</label>
<span class="db umb-list-item__description umb-list-item__description--checkbox">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
</div>
</div>
@@ -18,31 +18,29 @@
<localize key="publicAccess_paHowWould" tokens="[currentNode.name]">Choose how to restrict access to this page</localize>
</p>
<umb-pane>
<div class="pa-select-type">
<input id="protectionTypeMember" type="radio" name="protectionType" value="member" ng-model="vm.type">
<div class="pa-select-type">
<umb-radiobutton input-id="protectionTypeMember" name="protectionType" value="member" model="vm.type" text="" />
<label for="protectionTypeMember">
<h5 class="pa-access-header"><localize key="publicAccess_paMembers">Specific members protection</localize></h5>
<p><localize key="publicAccess_paMembersHelp">If you want to grant access to specific members</localize></p>
</label>
</div>
<label for="protectionTypeMember" class="flex flex-column flex-auto">
<strong class="pa-access-header bold"><localize key="publicAccess_paMembers">Specific members protection</localize></strong>
<span class=""><localize key="publicAccess_paMembersHelp">If you want to grant access to specific members</localize></span>
</label>
</div>
<div class="pa-select-type">
<input id="protectionTypeGroup" type="radio" name="protectionType" value="group" ng-model="vm.type">
<div class="pa-select-type">
<umb-radiobutton input-id="protectionTypeGroup" name="protectionType" value="group" model="vm.type" />
<label for="protectionTypeGroup">
<h5 class="pa-access-header"><localize key="publicAccess_paGroups">Group based protection</localize></h5>
<p><localize key="publicAccess_paGroupsHelp">If you want to grant access to all members of specific member groups</localize></p>
</label>
</div>
</umb-pane>
<label for="protectionTypeGroup" class="flex flex-column flex-auto">
<strong class="pa-access-header bold"><localize key="publicAccess_paGroups">Group based protection</localize></strong>
<span><localize key="publicAccess_paGroupsHelp">If you want to grant access to all members of specific member groups</localize></span>
</label>
</div>
</div>
<div ng-show="vm.step && !vm.loading && !vm.removing">
<div ng-if="vm.step === 'member'">
<p><localize key="publicAccess_paSelectMembers" tokens="[currentNode.name]">Select the members that should have access to this page</localize></p>
<umb-pane>
<div>
<umb-node-preview ng-repeat="member in vm.members | orderBy:'name'"
icon="'icon-user'"
name="member.name"
@@ -52,7 +50,7 @@
<a href ng-click="vm.pickMember()" class="umb-node-preview-add" prevent-default>
<localize key="general_add">Add</localize>
</a>
</umb-pane>
</div>
</div>
<div ng-show="vm.step === 'group' && !vm.hasGroups">
@@ -61,7 +59,7 @@
<div ng-show="vm.step === 'group' && vm.hasGroups">
<p><localize key="publicAccess_paSelectGroups" tokens="[currentNode.name]">Select the groups that should have access to this page</localize></p>
<umb-pane>
<div>
<umb-node-preview ng-repeat="group in vm.groups | orderBy:'name'"
icon="'icon-users'"
name="group.name"
@@ -71,12 +69,12 @@
<a href ng-click="vm.pickGroup()" class="umb-node-preview-add" prevent-default>
<localize key="general_add">Add</localize>
</a>
</umb-pane>
</div>
</div>
<div ng-show="vm.step === 'member' || vm.hasGroups">
<p class="mt4"><localize key="publicAccess_paSelectPages">Select the pages that contain login form and error messages</localize></p>
<umb-pane>
<div>
<div class="control-group umb-control-group -no-border">
<div class="umb-el-wrap">
<label>
@@ -111,7 +109,7 @@
</umb-node-preview>
</div>
</div>
</umb-pane>
</div>
</div>
</div>
@@ -119,8 +117,6 @@
<p><localize key="publicAccess_paRemoveProtectionConfirm" tokens="[currentNode.name]">Are you sure you want to remove the protection from this page?</localize></p>
</div>
<!--<pre>{{vm | json}}</pre>-->
</umb-pane>
<umb-pane ng-show="vm.success">
@@ -31,15 +31,15 @@
<div class="umb-panel-group__details-status" ng-repeat="indexer in vm.indexerDetails">
<div class="umb-panel-group__details-status-icon-container">
<i class="umb-panel-status-icon"
<i class="umb-panel-status-icon" aria-hidden="true"
ng-class="{'icon-check color-green' : indexer.isHealthy, 'icon-delete color-red' : !indexer.isHealthy}"></i>
</div>
<div class="umb-panel-group__details-status-content">
<div class="umb-panel-group__details-status-text">
<a class="btn-link -underline" href="" ng-click="vm.showIndexInfo(indexer)">
<button type="button" class="btn-link -underline" ng-click="vm.showIndexInfo(indexer)">
{{indexer.name}}
</a>
</button>
</div>
</div>
@@ -67,14 +67,14 @@
<div class="umb-panel-group__details-status" ng-repeat="searcher in vm.searcherDetails">
<div class="umb-panel-group__details-status-icon-container">
<i class="umb-panel-status-icon icon-info"></i>
<i class="umb-panel-status-icon icon-info" aria-hidden="true"></i>
</div>
<div class="umb-panel-group__details-status-content">
<div class="umb-panel-group__details-status-text">
<a class="btn-link -underline" href="" ng-click="vm.showSearcherInfo(searcher)">
<button type="button" class="btn-link -underline" ng-click="vm.showSearcherInfo(searcher)">
{{searcher.name}}
</a>
</button>
</div>
</div>
</div>
@@ -159,10 +159,7 @@
<td>
<a ng-show="result.editUrl" ng-click="vm.goToResult(result, $event)" ng-href="#{{result.editUrl}}">{{result.values['nodeName']}}</a>
<span ng-hide="result.editUrl">{{result.values['nodeName']}}</span>
&nbsp;
<a class="color-green" href="" ng-click="vm.showSearchResultDialog(result.values)">
<em>({{result.fieldCount}} fields)</em>
</a>
<button type="button" class="table__action-overlay color-green" ng-click="vm.showSearchResultDialog(result.values)">({{result.fieldCount}} <localize key="examineManagement_fields">fields</localize>)</button>
</td>
</tr>
</tbody>
@@ -227,7 +224,7 @@
<div class="umb-panel-group__details-status">
<div class="umb-panel-group__details-status-icon-container">
<i class="umb-panel-status-icon"
<i class="umb-panel-status-icon" aria-hidden="true"
ng-class="{'icon-check color-green' : vm.selectedIndex.isHealthy, 'icon-delete color-red' : !vm.selectedIndex.isHealthy}"></i>
</div>
@@ -302,10 +299,7 @@
<td>
<a ng-show="result.editUrl" ng-click="vm.goToResult(result, $event)" ng-href="#{{result.editUrl}}">{{result.values['nodeName']}}</a>
<span ng-hide="result.editUrl">{{result.values['nodeName']}}</span>
&nbsp;
<a class="color-green" href="" ng-click="vm.showSearchResultDialog(result.values)">
<em>({{result.fieldCount}} fields)</em>
</a>
<button type="button" class="table__action-overlay color-green" ng-click="vm.showSearchResultDialog(result.values)">({{result.fieldCount}} <localize key="examineManagement_fields">fields</localize>)</button>
</td>
</tr>
</tbody>
@@ -8,7 +8,13 @@
*/
function DataTypeDeleteController($scope, dataTypeResource, treeService, navigationService, localizationService) {
$scope.performDelete = function() {
var vm = this;
vm.propertyJoinSeparator = ', <i class="icon-alert red"></i>';
vm.hasReferences = false;
vm.references = [];
vm.performDelete = function() {
//mark it for deletion (used in the UI)
$scope.currentNode.loading = true;
@@ -24,7 +30,7 @@ function DataTypeDeleteController($scope, dataTypeResource, treeService, navigat
});
};
$scope.performContainerDelete = function () {
vm.performContainerDelete = function () {
//mark it for deletion (used in the UI)
$scope.currentNode.loading = true;
@@ -41,16 +47,36 @@ function DataTypeDeleteController($scope, dataTypeResource, treeService, navigat
};
$scope.cancel = function() {
vm.cancel = function() {
navigationService.hideDialog();
};
$scope.labels = {};
vm.labels = {};
localizationService
.format(["editdatatype_yesDelete", "editdatatype_andAllRelated"], "%0% " + $scope.currentNode.name + " %1%")
.localize("editdatatype_acceptDeleteConsequence", [$scope.currentNode.name])
.then(function (data) {
$scope.labels.deleteConfirm = data;
vm.labels.deleteConfirm = data;
});
var init = function() {
if($scope.currentNode.nodeType === "dataTypes") {
vm.loading = true;
dataTypeResource.getReferences($scope.currentNode.id)
.then(function (data) {
vm.loading = false;
vm.references = data;
vm.hasReferences = data.documentTypes.length > 0 || data.mediaTypes.length > 0 || data.memberTypes.length > 0;
});
}
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.DataType.DeleteController", DataTypeDeleteController);
@@ -6,35 +6,20 @@
* @description
* The controller for the content editor
*/
function DataTypeEditController($scope, $routeParams, appState, navigationService, dataTypeResource, serverValidationManager, contentEditingHelper, formHelper, editorState, dataTypeHelper, eventsService) {
//setup scope vars
$scope.page = {};
$scope.page.loading = false;
$scope.page.nameLocked = false;
$scope.page.menu = {};
$scope.page.menu.currentSection = appState.getSectionState("currentSection");
$scope.page.menu.currentNode = null;
function DataTypeEditController($scope, $routeParams, appState, navigationService, dataTypeResource, serverValidationManager, contentEditingHelper, formHelper, editorState, dataTypeHelper, eventsService, localizationService) {
var evts = [];
//method used to configure the pre-values when we retrieve them from the server
function createPreValueProps(preVals) {
$scope.preValues = [];
for (var i = 0; i < preVals.length; i++) {
$scope.preValues.push({
hideLabel: preVals[i].hideLabel,
alias: preVals[i].key,
description: preVals[i].description,
label: preVals[i].label,
view: preVals[i].view,
value: preVals[i].value,
config: preVals[i].config
});
}
}
var vm = this;
//setup scope vars
vm.page = {};
vm.page.loading = false;
vm.page.menu = {};
vm.page.menu.currentSection = appState.getSectionState("currentSection");
vm.page.menu.currentNode = null;
//set up the standard data type props
$scope.properties = {
vm.properties = {
selectedEditor: {
alias: "selectedEditor",
description: "Select a property editor",
@@ -47,52 +32,51 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
};
//setup the pre-values as props
$scope.preValues = [];
if ($routeParams.create) {
$scope.page.loading = true;
$scope.showIdentifier = false;
//we are creating so get an empty data type item
dataTypeResource.getScaffold($routeParams.id)
.then(function(data) {
$scope.preValuesLoaded = true;
$scope.content = data;
setHeaderNameState($scope.content);
//set a shared state
editorState.set($scope.content);
$scope.page.loading = false;
vm.preValues = [];
//method used to configure the pre-values when we retrieve them from the server
function createPreValueProps(preVals) {
vm.preValues = [];
for (var i = 0; i < preVals.length; i++) {
vm.preValues.push({
hideLabel: preVals[i].hideLabel,
alias: preVals[i].key,
description: preVals[i].description,
label: preVals[i].label,
view: preVals[i].view,
value: preVals[i].value,
config: preVals[i].config
});
}
}
else {
loadDataType();
function setHeaderNameState(content) {
if(content.isSystem == 1) {
vm.page.nameLocked = true;
}
}
function loadDataType() {
$scope.page.loading = true;
$scope.showIdentifier = true;
vm.page.loading = true;
vm.showIdentifier = true;
//we are editing so get the content item from the server
dataTypeResource.getById($routeParams.id)
.then(function(data) {
$scope.preValuesLoaded = true;
$scope.content = data;
vm.preValuesLoaded = true;
vm.content = data;
createPreValueProps($scope.content.preValues);
createPreValueProps(vm.content.preValues);
setHeaderNameState($scope.content);
setHeaderNameState(vm.content);
//share state
editorState.set($scope.content);
editorState.set(vm.content);
//in one particular special case, after we've created a new item we redirect back to the edit
// route but there might be server validation errors in the collection which we need to display
@@ -101,49 +85,22 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
serverValidationManager.notifyAndClearAllSubscriptions();
navigationService.syncTree({ tree: "datatypes", path: data.path }).then(function (syncArgs) {
$scope.page.menu.currentNode = syncArgs.node;
vm.page.menu.currentNode = syncArgs.node;
});
$scope.page.loading = false;
vm.page.loading = false;
});
}
$scope.$watch("content.selectedEditor", function (newVal, oldVal) {
//when the value changes, we need to dynamically load in the new editor
if (newVal && (newVal != oldVal && (oldVal || $routeParams.create))) {
//we are editing so get the content item from the server
var currDataTypeId = $routeParams.create ? undefined : $routeParams.id;
dataTypeResource.getPreValues(newVal, currDataTypeId)
.then(function (data) {
$scope.preValuesLoaded = true;
$scope.content.preValues = data;
createPreValueProps($scope.content.preValues);
setHeaderNameState($scope.content);
//share state
editorState.set($scope.content);
});
}
});
function setHeaderNameState(content) {
if(content.isSystem == 1) {
$scope.page.nameLocked = true;
}
}
$scope.save = function() {
function saveDataType() {
if (formHelper.submitForm({ scope: $scope })) {
$scope.page.saveButtonState = "busy";
vm.page.saveButtonState = "busy";
dataTypeResource.save($scope.content, $scope.preValues, $routeParams.create)
dataTypeResource.save(vm.content, vm.preValues, $routeParams.create)
.then(function(data) {
formHelper.resetForm({ scope: $scope });
@@ -156,18 +113,18 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
}
});
setHeaderNameState($scope.content);
setHeaderNameState(vm.content);
//share state
editorState.set($scope.content);
editorState.set(vm.content);
navigationService.syncTree({ tree: "datatypes", path: data.path, forceReload: true }).then(function (syncArgs) {
$scope.page.menu.currentNode = syncArgs.node;
vm.page.menu.currentNode = syncArgs.node;
});
$scope.page.saveButtonState = "success";
vm.page.saveButtonState = "success";
dataTypeHelper.rebindChangedProperties($scope.content, data);
dataTypeHelper.rebindChangedProperties(vm.content, data);
}, function(err) {
@@ -177,19 +134,21 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
err: err
});
$scope.page.saveButtonState = "error";
vm.page.saveButtonState = "error";
//share state
editorState.set($scope.content);
editorState.set(vm.content);
});
}
};
vm.save = saveDataType;
evts.push(eventsService.on("app.refreshEditor", function(name, error) {
loadDataType();
}));
//ensure to unregister from all events!
$scope.$on('$destroy', function () {
for (var e in evts) {
@@ -197,6 +156,80 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
}
});
function init() {
$scope.$watch("vm.content.selectedEditor", function (newVal, oldVal) {
//when the value changes, we need to dynamically load in the new editor
if (newVal && (newVal != oldVal && (oldVal || $routeParams.create))) {
//we are editing so get the content item from the server
var currDataTypeId = $routeParams.create ? undefined : $routeParams.id;
dataTypeResource.getPreValues(newVal, currDataTypeId)
.then(function (data) {
vm.preValuesLoaded = true;
vm.content.preValues = data;
createPreValueProps(vm.content.preValues);
setHeaderNameState(vm.content);
//share state
editorState.set(vm.content);
});
}
});
if ($routeParams.create) {
vm.page.loading = true;
vm.showIdentifier = false;
//we are creating so get an empty data type item
dataTypeResource.getScaffold($routeParams.id)
.then(function(data) {
vm.preValuesLoaded = true;
vm.content = data;
setHeaderNameState(vm.content);
//set a shared state
editorState.set(vm.content);
vm.page.loading = false;
});
}
else {
loadDataType();
}
var labelKeys = [
"general_settings",
"references_tabName"
];
localizationService.localizeMany(labelKeys).then(function (values) {
vm.page.navigation = [
{
"name": values[0],
"alias": "settings",
"icon": "icon-settings",
"view": "views/datatypes/views/datatype.settings.html",
"active": true
},
{
"name": values[1],
"alias": "references",
"icon": "icon-molecular-network",
"view": "views/datatypes/views/datatype.references.html"
}
];
});
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.DataType.EditController", DataTypeEditController);
@@ -1,29 +1,122 @@
<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.DataType.DeleteController">
<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.DataType.DeleteController as vm">
<div class="umb-dialog-body">
<p class="abstract">
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize>&nbsp;<strong>{{currentNode.name}}</strong>?
</p>
<ng-switch on="currentNode.nodeType">
<div ng-switch-when="container">
<umb-confirm on-confirm="performContainerDelete"
on-cancel="cancel">
<p class="abstract">
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize>&nbsp;<strong>{{currentNode.name}}</strong>?
</p>
<umb-confirm on-confirm="vm.performContainerDelete"
on-cancel="vm.cancel"
confirm-button-style="danger"
confirm-label-key="general_delete">
</umb-confirm>
</div>
<div ng-switch-default>
<p>
<i class="icon-alert red"></i> <strong class="red"><localize key="editdatatype_allPropTypes">All property types & property data</localize></strong>
<localize key="editdatatype_willBeDeleted">using this data type will be deleted permanently, please confirm you want to delete these as well</localize>.
</p>
<hr />
<umb-checkbox model="confirmed" text="{{labels.deleteConfirm}}">
</umb-checkbox>
<umb-load-indicator ng-if="vm.loading === true"></umb-load-indicator>
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel">
<div ng-if="vm.loading === false && vm.hasReferences === false">
<p class="abstract">
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize>&nbsp;<strong>{{currentNode.name}}</strong>?
</p>
</div>
<div ng-if="vm.loading === false && vm.hasReferences === true">
<p class="abstract">
<localize key="editdatatype_hasReferencesDeleteConsequence" tokens="[currentNode.name]">Deleting <strong>{{currentNode.name}}</strong> will have the following consequence</localize>
</p>
<hr/>
<!-- Document Types -->
<div ng-if="vm.references.documentTypes.length > 0">
<h5 class="mt4" style="margin-bottom: 20px;">
<localize key="treeHeaders_documentTypes"></localize>
</h5>
<table class="table table-condensed table-bordered">
<thead>
<tr>
<th><localize key="general_name">Name</localize></th>
<th><localize key="general_properties">Properties</localize></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="relation in vm.references.documentTypes">
<td><span title="{{::relation.name}}({{::relation.alias}})"><i class="umb-table-body__icon {{relation.icon}}"></i>{{::relation.name}}</span></td>
<td><span><span class="red" ng-repeat="property in relation.properties"><i class="icon icon-alert red"></i>{{::property.name}}{{$last ? '' : ', '}}</span></span></td>
</tr>
</tbody>
</table>
</div>
<!-- Media Types -->
<div ng-if="vm.references.mediaTypes.length > 0">
<h5 class="mt4" style="margin-bottom: 20px;">
<localize key="treeHeaders_mediaTypes"></localize>
</h5>
<table class="table table-condensed table-bordered">
<thead>
<tr>
<th><localize key="general_name">Name</localize></th>
<th><localize key="general_properties">Properties</localize></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="relation in vm.references.mediaTypes">
<td><span title="{{::relation.name}}({{::relation.alias}})"><i class="umb-table-body__icon {{relation.icon}}"></i>{{::relation.name}}</span></td>
<td><span><span class="red" ng-repeat="property in relation.properties"><i class="icon icon-alert red"></i>{{::property.name}}{{$last ? '' : ', '}}</span></span></td>
</tr>
</tbody>
</table>
</div>
<!-- Member Types -->
<div ng-if="vm.references.memberTypes.length > 0">
<h5 class="mt4" style="margin-bottom: 20px;">
<localize key="treeHeaders_memberTypes"></localize>
</h5>
<table class="table table-condensed table-bordered">
<thead>
<tr>
<th><localize key="general_name">Name</localize></th>
<th><localize key="general_properties">Properties</localize></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="relation in vm.references.memberTypes">
<td><span title="{{::relation.name}}({{::relation.alias}})"><i class="umb-table-body__icon {{relation.icon}}"></i>{{::relation.name}}</span></td>
<td><span><span class="red" ng-repeat="property in relation.properties"><i class="icon icon-alert red"></i>{{::property.name}}{{$last ? '' : ', '}}</span></span></td>
</tr>
</tbody>
</table>
</div>
<umb-checkbox model="vm.confirmed" text="{{vm.labels.deleteConfirm}}">
</umb-checkbox>
</div>
<umb-confirm ng-if="vm.confirmed || vm.hasReferences === false"
on-confirm="vm.performDelete"
on-cancel="vm.cancel"
confirm-button-style="danger"
confirm-label-key="general_delete">
</umb-confirm>
</div>
</ng-switch>
@@ -1,81 +1,33 @@
<div data-element="editor-data-types" ng-controller="Umbraco.Editors.DataType.EditController">
<div data-element="editor-data-types" ng-controller="Umbraco.Editors.DataType.EditController as vm">
<umb-load-indicator ng-if="page.loading"></umb-load-indicator>
<form name="contentForm" novalidate val-form-manager ng-submit="vm.save()">
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-header name="vm.content.name"
name-locked="vm.page.nameLocked"
hide-icon="true"
hide-description="true"
hide-alias="true"
navigation="vm.page.navigation">
</umb-editor-header>
<form name="contentForm"
ng-submit="save()"
novalidate
val-form-manager>
<umb-editor-container class="form-horizontal">
<umb-editor-sub-views sub-views="vm.page.navigation" model="vm">
</umb-editor-sub-views>
</umb-editor-container>
<umb-editor-view ng-if="!page.loading">
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button type="submit"
button-style="success"
state="vm.page.saveButtonState"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
<umb-editor-header
name="content.name"
name-locked="page.nameLocked"
hide-icon="true"
hide-description="true"
hide-alias="true">
</umb-editor-header>
<umb-editor-container class="form-horizontal">
<umb-box>
<umb-box-content>
<umb-control-group label="Id" ng-if="showIdentifier">
<div>{{content.id}}</div>
<small>{{content.key}}</small>
</umb-control-group>
<umb-property property="properties.selectedEditor">
<div>
<select name="selectedEditor"
ng-model="content.selectedEditor"
required
ng-options="e.alias as e.name for e in content.availableEditors"></select>
<span ng-messages="contentForm.selectedEditor.$error" show-validation-on-submit>
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
</span>
</div>
</umb-property>
<umb-property property="properties.selectedEditorId">
<div>{{content.selectedEditor}}</div>
</umb-property>
<umb-property property="preValue"
ng-repeat="preValue in preValues">
<umb-property-editor model="preValue" is-pre-value="true"></umb-property-editor>
</umb-property>
</umb-box-content>
</umb-box>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
type="submit"
button-style="success"
state="page.saveButtonState"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
</div>
@@ -0,0 +1,55 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.DataType.ReferencesController
* @function
*
* @description
* The controller for the references view of the datatype editor
*/
function DataTypeReferencesController($scope, $routeParams, dataTypeResource, eventsService, $timeout) {
var vm = this;
var evts = [];
var referencesLoaded = false;
vm.references = {};
vm.hasReferences = false;
vm.view = {};
vm.view.loading = true;
/** Loads in the data type references one time */
function loadRelations() {
if (!referencesLoaded) {
referencesLoaded = true;
dataTypeResource.getReferences($routeParams.id)
.then(function (data) {
vm.view.loading = false;
vm.references = data;
vm.hasReferences = data.documentTypes.length > 0 || data.mediaTypes.length > 0 || data.memberTypes.length > 0;
});
}
}
// load data type references when the references tab is activated
evts.push(eventsService.on("app.tabChange", function (event, args) {
$timeout(function () {
if (args.alias === "references") {
loadRelations();
}
});
}));
//ensure to unregister from all events!
$scope.$on('$destroy', function () {
for (var e in evts) {
eventsService.unsubscribe(evts[e]);
}
});
}
angular.module("umbraco").controller("Umbraco.Editors.DataType.ReferencesController", DataTypeReferencesController);

Some files were not shown because too many files have changed in this diff Show More