Merge branch temp8 into temp8-di2690
This commit is contained in:
+5
-1
@@ -298,7 +298,11 @@
|
||||
|
||||
# copy libs
|
||||
Write-Host "Copy SqlCE libraries"
|
||||
$nugetPackages = [System.Environment]::ExpandEnvironmentVariables("%userprofile%\.nuget\packages")
|
||||
$nugetPackages = $env:NUGET_PACKAGES
|
||||
if (-not $nugetPackages)
|
||||
{
|
||||
$nugetPackages = [System.Environment]::ExpandEnvironmentVariables("%userprofile%\.nuget\packages")
|
||||
}
|
||||
$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\bin\x86")
|
||||
$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x64\native", "*.*", "$tmp\bin\amd64")
|
||||
$this.CopyFiles("$nugetPackages\umbraco.sqlserverce\4.0.0.1\runtimes\win-x86\native", "*.*", "$tmp\WebApp\bin\x86")
|
||||
|
||||
@@ -34,11 +34,6 @@ namespace Umbraco.Core
|
||||
public const string ListViewPrefix = "List View - ";
|
||||
}
|
||||
|
||||
public static class PropertyGroups
|
||||
{
|
||||
public const string ListViewGroupName = "umbContainerView";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constants for Umbraco Content property aliases.
|
||||
/// </summary>
|
||||
|
||||
@@ -17,11 +17,6 @@
|
||||
/// </summary>
|
||||
public const string File = "50899F9C-023A-4466-B623-ABA9049885FE";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Image PropertyTypeGroup object.
|
||||
/// </summary>
|
||||
public const string Contents = "79995FA2-63EE-453C-A29B-2E66F324CDBE";
|
||||
|
||||
/// <summary>
|
||||
/// Guid for a Image PropertyTypeGroup object.
|
||||
/// </summary>
|
||||
|
||||
@@ -14,10 +14,7 @@ namespace Umbraco.Core.Events
|
||||
_msgs.Add(msg);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get { return _msgs.Count; }
|
||||
}
|
||||
public int Count => _msgs.Count;
|
||||
|
||||
public IEnumerable<EventMessage> GetAll()
|
||||
{
|
||||
|
||||
@@ -205,7 +205,6 @@ namespace Umbraco.Core.Migrations.Install
|
||||
{
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 3, ContentTypeNodeId = 1032, Text = "Image", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Image) });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 4, ContentTypeNodeId = 1033, Text = "File", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.File) });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 5, ContentTypeNodeId = 1031, Text = "Contents", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Contents) });
|
||||
//membership property group
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyTypeGroup, "id", false, new PropertyTypeGroupDto { Id = 11, ContentTypeNodeId = 1044, Text = "Membership", SortOrder = 1, UniqueId = new Guid(Constants.PropertyTypeGroups.Membership) });
|
||||
}
|
||||
@@ -220,7 +219,6 @@ namespace Umbraco.Core.Migrations.Install
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 24, UniqueId = 24.ToGuid(), DataTypeId = -90, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.File, Name = "Upload file", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 25, UniqueId = 25.ToGuid(), DataTypeId = -92, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Extension, Name = "Type", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 26, UniqueId = 26.ToGuid(), DataTypeId = Constants.DataTypes.LabelBigint, ContentTypeId = 1033, PropertyTypeGroupId = 4, Alias = Constants.Conventions.Media.Bytes, Name = "Size", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 27, UniqueId = 27.ToGuid(), DataTypeId = Constants.DataTypes.DefaultMediaListView, ContentTypeId = 1031, PropertyTypeGroupId = 5, Alias = "contents", Name = "Contents:", SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
//membership property types
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 28, UniqueId = 28.ToGuid(), DataTypeId = -89, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.Comments, Name = Constants.Conventions.Member.CommentsLabel, SortOrder = 0, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.PropertyType, "id", false, new PropertyTypeDto { Id = 29, UniqueId = 29.ToGuid(), DataTypeId = Constants.DataTypes.LabelInt, ContentTypeId = 1044, PropertyTypeGroupId = 11, Alias = Constants.Conventions.Member.FailedPasswordAttempts, Name = Constants.Conventions.Member.FailedPasswordAttemptsLabel, SortOrder = 1, Mandatory = false, ValidationRegExp = null, Description = null, Variations = (byte) ContentVariation.Nothing });
|
||||
@@ -234,7 +232,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
private void CreateLanguageData()
|
||||
{
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "en-US" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)" });
|
||||
}
|
||||
|
||||
private void CreateContentChildTypeData()
|
||||
|
||||
@@ -141,13 +141,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
|
||||
//get the db index defs
|
||||
result.DbIndexDefinitions = SqlSyntax.GetDefinedIndexes(_database)
|
||||
.Select(x => new DbIndexDefinition
|
||||
{
|
||||
TableName = x.Item1,
|
||||
IndexName = x.Item2,
|
||||
ColumnName = x.Item3,
|
||||
IsUnique = x.Item4
|
||||
}).ToArray();
|
||||
.Select(x => new DbIndexDefinition(x)).ToArray();
|
||||
|
||||
result.TableDefinitions.AddRange(OrderedTables
|
||||
.Select(x => DefinitionFactory.GetTableDefinition(x, SqlSyntax)));
|
||||
@@ -160,6 +154,14 @@ namespace Umbraco.Core.Migrations.Install
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This validates the Primary/Foreign keys in the database
|
||||
/// </summary>
|
||||
/// <param name="result"></param>
|
||||
/// <remarks>
|
||||
/// This does not validate any database constraints that are not PKs or FKs because Umbraco does not create a database with non PK/FK contraints.
|
||||
/// Any unique "constraints" in the database are done with unique indexes.
|
||||
/// </remarks>
|
||||
private void ValidateDbConstraints(DatabaseSchemaResult result)
|
||||
{
|
||||
//MySql doesn't conform to the "normal" naming of constraints, so there is currently no point in doing these checks.
|
||||
@@ -172,8 +174,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
var constraintsInDatabase = SqlSyntax.GetConstraintsPerColumn(_database).DistinctBy(x => x.Item3).ToList();
|
||||
var foreignKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("FK_")).Select(x => x.Item3).ToList();
|
||||
var primaryKeysInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("PK_")).Select(x => x.Item3).ToList();
|
||||
var indexesInDatabase = constraintsInDatabase.Where(x => x.Item3.InvariantStartsWith("IX_")).Select(x => x.Item3).ToList();
|
||||
var indexesInSchema = result.TableDefinitions.SelectMany(x => x.Indexes.Select(y => y.Name)).ToList();
|
||||
|
||||
var unknownConstraintsInDatabase =
|
||||
constraintsInDatabase.Where(
|
||||
x =>
|
||||
@@ -188,7 +189,7 @@ namespace Umbraco.Core.Migrations.Install
|
||||
// In theory you could have: FK_ or fk_ ...or really any standard that your development department (or developer) chooses to use.
|
||||
foreach (var unknown in unknownConstraintsInDatabase)
|
||||
{
|
||||
if (foreignKeysInSchema.InvariantContains(unknown) || primaryKeysInSchema.InvariantContains(unknown) || indexesInSchema.InvariantContains(unknown))
|
||||
if (foreignKeysInSchema.InvariantContains(unknown) || primaryKeysInSchema.InvariantContains(unknown))
|
||||
{
|
||||
result.ValidConstraints.Add(unknown);
|
||||
}
|
||||
@@ -230,23 +231,6 @@ namespace Umbraco.Core.Migrations.Install
|
||||
result.Errors.Add(new Tuple<string, string>("Constraint", primaryKey));
|
||||
}
|
||||
|
||||
//Constaints:
|
||||
|
||||
//NOTE: SD: The colIndex checks above should really take care of this but I need to keep this here because it was here before
|
||||
// and some schema validation checks might rely on this data remaining here!
|
||||
//Add valid and invalid index differences to the result object
|
||||
var validIndexDifferences = indexesInDatabase.Intersect(indexesInSchema, StringComparer.InvariantCultureIgnoreCase);
|
||||
foreach (var index in validIndexDifferences)
|
||||
{
|
||||
result.ValidConstraints.Add(index);
|
||||
}
|
||||
var invalidIndexDifferences =
|
||||
indexesInDatabase.Except(indexesInSchema, StringComparer.InvariantCultureIgnoreCase)
|
||||
.Union(indexesInSchema.Except(indexesInDatabase, StringComparer.InvariantCultureIgnoreCase));
|
||||
foreach (var index in invalidIndexDifferences)
|
||||
{
|
||||
result.Errors.Add(new Tuple<string, string>("Constraint", index));
|
||||
}
|
||||
}
|
||||
|
||||
private void ValidateDbColumns(DatabaseSchemaResult result)
|
||||
|
||||
@@ -5,7 +5,6 @@ using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_7_12_0;
|
||||
using Umbraco.Core.Migrations.Upgrade.V_8_0_0;
|
||||
using Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwelveZero;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade
|
||||
{
|
||||
|
||||
+15
-12
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0
|
||||
{
|
||||
@@ -12,18 +11,22 @@ namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
var exists = Context.Database.FirstOrDefault<RelationTypeDto>("WHERE alias=@alias", new { alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias });
|
||||
if (exists == null)
|
||||
var relationTypeCount = Context.Database.ExecuteScalar<int>("SELECT COUNT(*) FROM umbracoRelationType WHERE alias=@alias",
|
||||
new { alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias });
|
||||
|
||||
if (relationTypeCount > 0)
|
||||
return;
|
||||
|
||||
var uniqueId = (Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias + "____" + Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName).ToGuid();
|
||||
Insert.IntoTable("umbracoRelationType").Row(new
|
||||
{
|
||||
Insert.IntoTable(Constants.DatabaseSchema.Tables.RelationType).Row(new
|
||||
{
|
||||
alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias,
|
||||
name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName,
|
||||
childObjectType = Constants.ObjectTypes.MediaType,
|
||||
parentObjectType = Constants.ObjectTypes.MediaType,
|
||||
dual = false
|
||||
typeUniqueId = uniqueId,
|
||||
alias = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias,
|
||||
name = Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteName,
|
||||
childObjectType = Constants.ObjectTypes.MediaType,
|
||||
parentObjectType = Constants.ObjectTypes.MediaType,
|
||||
dual = false
|
||||
}).Do();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+22
-8
@@ -1,18 +1,31 @@
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Migrations;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.SqlSyntax;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwelveZero
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0
|
||||
{
|
||||
public class IncreaseLanguageIsoCodeColumnLength : MigrationBase
|
||||
{
|
||||
public IncreaseLanguageIsoCodeColumnLength(IMigrationContext context) : base(context)
|
||||
{
|
||||
}
|
||||
public IncreaseLanguageIsoCodeColumnLength(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
Delete.Index("IX_umbracoLanguage_languageISOCode").OnTable("umbracoLanguage").Do();
|
||||
// Some people seem to have a constraint in their DB instead of an index, we'd need to drop that one
|
||||
// See: https://our.umbraco.com/forum/using-umbraco-and-getting-started/93282-upgrade-from-711-to-712-fails
|
||||
var constraints = SqlSyntax.GetConstraintsPerTable(Context.Database).Distinct().ToArray();
|
||||
if (constraints.Any(x => x.Item2.InvariantEquals("IX_umbracoLanguage_languageISOCode")))
|
||||
{
|
||||
Delete.UniqueConstraint("IX_umbracoLanguage_languageISOCode").FromTable("umbracoLanguage").Do();
|
||||
}
|
||||
|
||||
//Now check for indexes of that name and drop that if it exists
|
||||
var dbIndexes = SqlSyntax.GetDefinedIndexesDefinitions(Context.Database);
|
||||
if (dbIndexes.Any(x => x.IndexName.InvariantEquals("IX_umbracoLanguage_languageISOCode")))
|
||||
{
|
||||
Delete.Index("IX_umbracoLanguage_languageISOCode").OnTable("umbracoLanguage").Do();
|
||||
}
|
||||
|
||||
Alter.Table("umbracoLanguage")
|
||||
.AlterColumn("languageISOCode")
|
||||
@@ -23,10 +36,11 @@ namespace Umbraco.Core.Persistence.Migrations.Upgrades.TargetVersionSevenTwelveZ
|
||||
Create.Index("IX_umbracoLanguage_languageISOCode")
|
||||
.OnTable("umbracoLanguage")
|
||||
.OnColumn("languageISOCode")
|
||||
.Ascending()
|
||||
.WithOptions()
|
||||
.Unique()
|
||||
.Do();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0
|
||||
if (Context?.Database == null) return;
|
||||
|
||||
// We need to get all datatypes with an alias of "umbraco.tags" so we can loop over them and set the missing values if needed
|
||||
var datatypes = Context.Database.Fetch<DataTypeDto>("SELECT * FROM cmsDataType");
|
||||
var datatypes = Context.Database.Fetch<DataTypeDto>();
|
||||
var tagsDataTypes = datatypes.Where(x => string.Equals(x.EditorAlias, Constants.PropertyEditors.Aliases.Tags, StringComparison.InvariantCultureIgnoreCase));
|
||||
|
||||
foreach (var datatype in tagsDataTypes)
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Migrations.Upgrade.V_7_12_0
|
||||
public UpdateUmbracoConsent(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
{
Alter.Table("umbracoConsent").AlterColumn("comment").AsString().Nullable().Do();
}
|
||||
|
||||
public override void Migrate()
{
Alter.Table(Constants.DatabaseSchema.Tables.Consent).AlterColumn("comment").AsString().Nullable().Do();
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,17 +213,23 @@ namespace Umbraco.Core.Models
|
||||
[IgnoreDataMember]
|
||||
public IEnumerable<string> PublishedCultures => _publishInfos?.Keys ?? Enumerable.Empty<string>();
|
||||
|
||||
//fixme should this return false if ID == 0?
|
||||
//fixme should this return false if IsCultureAvailable(culture) is false?
|
||||
/// <inheritdoc />
|
||||
public bool IsCulturePublished(string culture)
|
||||
=> _publishInfos != null && _publishInfos.ContainsKey(culture);
|
||||
=> _publishInfos != null && _publishInfos.ContainsKey(culture);
|
||||
|
||||
//fixme should this return false if ID == 0?
|
||||
//fixme should this return false if IsCultureAvailable(culture) is false?
|
||||
/// <inheritdoc />
|
||||
public bool WasCulturePublished(string culture)
|
||||
=> _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture);
|
||||
=> _publishInfosOrig != null && _publishInfosOrig.ContainsKey(culture);
|
||||
|
||||
//fixme should this return false if ID == 0?
|
||||
//fixme should this return false if IsCultureAvailable(culture) is false?
|
||||
/// <inheritdoc />
|
||||
public bool IsCultureEdited(string culture)
|
||||
=> !IsCulturePublished(culture) || (_editedCultures != null && _editedCultures.Contains(culture));
|
||||
=> !IsCulturePublished(culture) || (_editedCultures != null && _editedCultures.Contains(culture));
|
||||
|
||||
/// <inheritdoc/>
|
||||
[IgnoreDataMember]
|
||||
|
||||
@@ -376,30 +376,7 @@ namespace Umbraco.Core.Models
|
||||
#endregion
|
||||
|
||||
#region Validation
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsValid(string culture = "*")
|
||||
{
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
|
||||
if (culture == null)
|
||||
{
|
||||
if (Name.IsNullOrWhiteSpace()) return false;
|
||||
return ValidateProperties(null).Length == 0;
|
||||
}
|
||||
|
||||
if (culture != "*")
|
||||
{
|
||||
var name = GetCultureName(culture);
|
||||
if (name.IsNullOrWhiteSpace()) return false;
|
||||
return ValidateProperties(culture).Length == 0;
|
||||
}
|
||||
|
||||
// 'all cultures'
|
||||
// those that have a name are ok, those without a name... we don't validate
|
||||
return AvailableCultures.All(c => ValidateProperties(c).Length == 0);
|
||||
}
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public virtual Property[] ValidateProperties(string culture = "*")
|
||||
{
|
||||
|
||||
@@ -10,6 +10,20 @@
|
||||
/// </summary>
|
||||
public string PropertyAlias { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When dealing with content variants, this is the culture for the variant
|
||||
/// </summary>
|
||||
public string Culture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An array of metadata that is parsed out from the file info posted to the server which is set on the client.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This can be used for property types like Nested Content that need to have special unique identifiers for each file since there might be multiple files
|
||||
/// per property.
|
||||
/// </remarks>
|
||||
public string[] Metadata { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the name of the file.
|
||||
/// </summary>
|
||||
|
||||
@@ -137,14 +137,7 @@ namespace Umbraco.Core.Models
|
||||
void CopyFrom(IContent other, string culture = "*");
|
||||
|
||||
// fixme validate published cultures?
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the content and property values are valid in order to be persisted.
|
||||
/// </summary>
|
||||
/// <para>If the content type is variant, then culture can be either '*' or an actual culture, but neither 'null' nor
|
||||
/// 'empty'. If the content type is invariant, then culture can be either '*' or null or empty.</para>
|
||||
bool IsValid(string culture = "*");
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Validates the content item's properties.
|
||||
/// </summary>
|
||||
|
||||
@@ -343,6 +343,8 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
internal bool IsValid(string culture = "*", string segment = "*")
|
||||
{
|
||||
//fixme - validating values shouldn't be done here, this calls in to IsValidValue
|
||||
|
||||
culture = culture.NullOrWhiteSpaceAsNull();
|
||||
segment = segment.NullOrWhiteSpaceAsNull();
|
||||
|
||||
@@ -382,6 +384,7 @@ namespace Umbraco.Core.Models
|
||||
/// <returns>True is property value is valid, otherwise false</returns>
|
||||
private bool IsValidValue(object value)
|
||||
{
|
||||
//fixme this shouldn't exist here, the model itself shouldn't be responsible for it's own validation and this requires singleton access
|
||||
return PropertyType.IsPropertyValueValid(value);
|
||||
}
|
||||
|
||||
|
||||
@@ -378,11 +378,11 @@ namespace Umbraco.Core.Models
|
||||
}
|
||||
|
||||
|
||||
//fixme - perhaps this and other validation methods should be a service level (not a model) thing?
|
||||
//fixme - this and other value validation methods should be a service level (not a model) thing. Changing this to internal for now
|
||||
/// <summary>
|
||||
/// Determines whether a value is valid for this property type.
|
||||
/// </summary>
|
||||
public bool IsPropertyValueValid(object value)
|
||||
internal bool IsPropertyValueValid(object value)
|
||||
{
|
||||
var editor = Current.PropertyEditors[_propertyEditorAlias]; // fixme inject?
|
||||
var configuration = Current.Services.DataTypeService.GetDataType(_dataTypeId).Configuration; // fixme inject?
|
||||
|
||||
@@ -6,13 +6,13 @@ namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
{
|
||||
public ConstraintDefinition(ConstraintType type)
|
||||
{
|
||||
constraintType = type;
|
||||
_constraintType = type;
|
||||
}
|
||||
|
||||
private ConstraintType constraintType;
|
||||
public bool IsPrimaryKeyConstraint { get { return ConstraintType.PrimaryKey == constraintType; } }
|
||||
public bool IsUniqueConstraint { get { return ConstraintType.Unique == constraintType; } }
|
||||
public bool IsNonUniqueConstraint { get { return ConstraintType.NonUnique == constraintType; } }
|
||||
private readonly ConstraintType _constraintType;
|
||||
public bool IsPrimaryKeyConstraint => ConstraintType.PrimaryKey == _constraintType;
|
||||
public bool IsUniqueConstraint => ConstraintType.Unique == _constraintType;
|
||||
public bool IsNonUniqueConstraint => ConstraintType.NonUnique == _constraintType;
|
||||
|
||||
public string SchemaName { get; set; }
|
||||
public string ConstraintName { get; set; }
|
||||
|
||||
@@ -1,13 +1,23 @@
|
||||
namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core.Persistence.DatabaseModelDefinitions
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a database index definition retreived by querying the database
|
||||
/// </summary>
|
||||
internal class DbIndexDefinition
|
||||
{
|
||||
public virtual string IndexName { get; set; }
|
||||
public virtual string TableName { get; set; }
|
||||
public virtual string ColumnName { get; set; }
|
||||
public virtual bool IsUnique { get; set; }
|
||||
public DbIndexDefinition(Tuple<string, string, string, bool> data)
|
||||
{
|
||||
TableName = data.Item1;
|
||||
IndexName = data.Item2;
|
||||
ColumnName = data.Item3;
|
||||
IsUnique = data.Item4;
|
||||
}
|
||||
|
||||
public string IndexName { get; }
|
||||
public string TableName { get; }
|
||||
public string ColumnName { get; }
|
||||
public bool IsUnique { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,11 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
if (xdtos.TryGetValue(propertyType.Id, out var propDtos))
|
||||
{
|
||||
foreach (var propDto in propDtos)
|
||||
{
|
||||
property.Id = propDto.Id;
|
||||
property.FactorySetValue(languageRepository.GetIsoCodeById(propDto.LanguageId), propDto.Segment, propDto.VersionId == publishedVersionId, propDto.Value);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
property.ResetDirtyProperties(false);
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
string TruncateTable { get; }
|
||||
string CreateConstraint { get; }
|
||||
string DeleteConstraint { get; }
|
||||
|
||||
[Obsolete("This is never used, use the Format(ForeignKeyDefinition) instead")]
|
||||
string CreateForeignKeyConstraint { get; }
|
||||
string DeleteDefaultConstraint { get; }
|
||||
string FormatDateTime(DateTime date, bool includeTime = true);
|
||||
@@ -80,8 +82,32 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
|
||||
IEnumerable<string> GetTablesInSchema(IDatabase db);
|
||||
IEnumerable<ColumnInfo> GetColumnsInSchema(IDatabase db);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all constraints defined in the database (Primary keys, foreign keys, unique constraints...) (does not include indexes)
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
/// <returns>
|
||||
/// A Tuple containing: TableName, ConstraintName
|
||||
/// </returns>
|
||||
IEnumerable<Tuple<string, string>> GetConstraintsPerTable(IDatabase db);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all constraints defined in the database (Primary keys, foreign keys, unique constraints...) (does not include indexes)
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
/// <returns>
|
||||
/// A Tuple containing: TableName, ColumnName, ConstraintName
|
||||
/// </returns>
|
||||
IEnumerable<Tuple<string, string, string>> GetConstraintsPerColumn(IDatabase db);
|
||||
|
||||
/// <summary>
|
||||
/// Returns all defined Indexes in the database excluding primary keys
|
||||
/// </summary>
|
||||
/// <param name="db"></param>
|
||||
/// <returns>
|
||||
/// A Tuple containing: TableName, IndexName, ColumnName, IsUnique
|
||||
/// </returns>
|
||||
IEnumerable<Tuple<string, string, string, bool>> GetDefinedIndexes(IDatabase db);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Tuple<string, string>> GetConstraintsPerTable(IDatabase db)
|
||||
{
|
||||
List<Tuple<string, string>> list;
|
||||
@@ -101,6 +102,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Tuple<string, string, string>> GetConstraintsPerColumn(IDatabase db)
|
||||
{
|
||||
List<Tuple<string, string, string>> list;
|
||||
@@ -127,6 +129,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Tuple<string, string, string, bool>> GetDefinedIndexes(IDatabase db)
|
||||
{
|
||||
List<Tuple<string, string, string, bool>> list;
|
||||
|
||||
@@ -107,40 +107,27 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
item.IS_NULLABLE, item.DATA_TYPE)).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Tuple<string, string>> GetConstraintsPerTable(IDatabase db)
|
||||
{
|
||||
var items = db.Fetch<dynamic>("SELECT TABLE_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS");
|
||||
var indexItems = db.Fetch<dynamic>("SELECT TABLE_NAME, INDEX_NAME FROM INFORMATION_SCHEMA.INDEXES");
|
||||
return
|
||||
items.Select(item => new Tuple<string, string>(item.TABLE_NAME, item.CONSTRAINT_NAME))
|
||||
.Union(
|
||||
indexItems.Select(
|
||||
indexItem => new Tuple<string, string>(indexItem.TABLE_NAME, indexItem.INDEX_NAME)))
|
||||
.ToList();
|
||||
return items.Select(item => new Tuple<string, string>(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Tuple<string, string, string>> GetConstraintsPerColumn(IDatabase db)
|
||||
{
|
||||
var items =
|
||||
db.Fetch<dynamic>(
|
||||
"SELECT CONSTRAINT_NAME, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE");
|
||||
var indexItems = db.Fetch<dynamic>("SELECT INDEX_NAME, TABLE_NAME, COLUMN_NAME FROM INFORMATION_SCHEMA.INDEXES");
|
||||
return
|
||||
items.Select(
|
||||
item => new Tuple<string, string, string>(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME))
|
||||
.Union(
|
||||
indexItems.Select(
|
||||
indexItem =>
|
||||
new Tuple<string, string, string>(indexItem.TABLE_NAME, indexItem.COLUMN_NAME,
|
||||
indexItem.INDEX_NAME))).ToList();
|
||||
var items = db.Fetch<dynamic>("SELECT TABLE_NAME, COLUMN_NAME, CONSTRAINT_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE");
|
||||
return items.Select(item => new Tuple<string, string, string>(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Tuple<string, string, string, bool>> GetDefinedIndexes(IDatabase db)
|
||||
{
|
||||
var items =
|
||||
db.Fetch<dynamic>(
|
||||
@"SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME, [UNIQUE] FROM INFORMATION_SCHEMA.INDEXES
|
||||
WHERE INDEX_NAME NOT LIKE 'PK_%'
|
||||
WHERE PRIMARY_KEY=0
|
||||
ORDER BY TABLE_NAME, INDEX_NAME");
|
||||
return
|
||||
items.Select(
|
||||
|
||||
@@ -163,6 +163,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
item.IS_NULLABLE, item.DATA_TYPE)).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Tuple<string, string>> GetConstraintsPerTable(IDatabase db)
|
||||
{
|
||||
var items =
|
||||
@@ -171,6 +172,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
return items.Select(item => new Tuple<string, string>(item.TABLE_NAME, item.CONSTRAINT_NAME)).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Tuple<string, string, string>> GetConstraintsPerColumn(IDatabase db)
|
||||
{
|
||||
var items =
|
||||
@@ -179,6 +181,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
return items.Select(item => new Tuple<string, string, string>(item.TABLE_NAME, item.COLUMN_NAME, item.CONSTRAINT_NAME)).ToList();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<Tuple<string, string, string, bool>> GetDefinedIndexes(IDatabase db)
|
||||
{
|
||||
var items =
|
||||
@@ -188,7 +191,7 @@ CASE WHEN I.is_unique_constraint = 1 OR I.is_unique = 1 THEN 1 ELSE 0 END AS [U
|
||||
from sys.tables as T inner join sys.indexes as I on T.[object_id] = I.[object_id]
|
||||
inner join sys.index_columns as IC on IC.[object_id] = I.[object_id] and IC.[index_id] = I.[index_id]
|
||||
inner join sys.all_columns as AC on IC.[object_id] = AC.[object_id] and IC.[column_id] = AC.[column_id]
|
||||
WHERE I.name NOT LIKE 'PK_%'
|
||||
WHERE I.is_primary_key = 0
|
||||
order by T.name, I.name");
|
||||
return items.Select(item => new Tuple<string, string, string, bool>(item.TABLE_NAME, item.INDEX_NAME, item.COLUMN_NAME,
|
||||
item.UNIQUE == 1)).ToList();
|
||||
|
||||
@@ -10,13 +10,7 @@ namespace Umbraco.Core.Persistence.SqlSyntax
|
||||
public static IEnumerable<DbIndexDefinition> GetDefinedIndexesDefinitions(this ISqlSyntaxProvider sql, IDatabase db)
|
||||
{
|
||||
return sql.GetDefinedIndexes(db)
|
||||
.Select(x => new DbIndexDefinition
|
||||
{
|
||||
TableName = x.Item1,
|
||||
IndexName = x.Item2,
|
||||
ColumnName = x.Item3,
|
||||
IsUnique = x.Item4
|
||||
}).ToArray();
|
||||
.Select(x => new DbIndexDefinition(x)).ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
+5
-7
@@ -1,6 +1,4 @@
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the configuration for the slider value editor.
|
||||
@@ -8,7 +6,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
public class SliderConfiguration
|
||||
{
|
||||
[ConfigurationField("enableRange", "Enable range", "boolean")]
|
||||
public string Range { get; set; }
|
||||
public bool EnableRange { get; set; }
|
||||
|
||||
[ConfigurationField("orientation", "Orientation", "views/propertyeditors/slider/orientation.prevalues.html")]
|
||||
public string Orientation { get; set; }
|
||||
@@ -38,7 +36,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
public string Tooltip { get; set; }
|
||||
|
||||
[ConfigurationField("tooltipSplit", "Tooltip split", "boolean", Description = "If false show one tootip if true show two tooltips one for each handler")]
|
||||
public string TooltipSplit { get; set; } // fixme bool?
|
||||
public bool TooltipSplit { get; set; } // fixme bool?
|
||||
|
||||
[ConfigurationField("tooltipFormat", "Tooltip format", "textstring", Description = "The value wanted to be displayed in the tooltip. Use {0} and {1} for current values - {1} is only for range slider and if not using tooltip split.")]
|
||||
public string TooltipFormat { get; set; }
|
||||
@@ -47,7 +45,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
public string TooltipPosition { get; set; }
|
||||
|
||||
[ConfigurationField("reversed", "Reversed", "boolean", Description = "Whether or not the slider should be reversed")]
|
||||
public string Reversed { get; set; } // fixme bool?
|
||||
public bool Reversed { get; set; } // fixme bool?
|
||||
|
||||
[ConfigurationField("ticks", "Ticks", "textstring", Description = "Comma-separated values. Used to define the values of ticks. Tick marks are indicators to denote special values in the range. This option overwrites min and max options.")]
|
||||
public string Ticks { get; set; }
|
||||
@@ -61,4 +59,4 @@ namespace Umbraco.Web.PropertyEditors
|
||||
[ConfigurationField("ticksSnapBounds", "Ticks snap bounds", "number", Description = "Used to define the snap bounds of a tick. Snaps to the tick if value is within these bounds.")]
|
||||
public int TicksSnapBounds { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors
|
||||
{
|
||||
public class SliderPropertyEditorConfiguration
|
||||
{
|
||||
[JsonProperty("enableRange")]
|
||||
public bool EnableRange { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Text.RegularExpressions;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors.Validators
|
||||
{
|
||||
@@ -11,6 +13,7 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// </summary>
|
||||
internal sealed class RegexValidator : IValueFormatValidator, IManifestValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _textService;
|
||||
private string _regex;
|
||||
|
||||
/// <inheritdoc cref="IManifestValueValidator.ValidationName"/>
|
||||
@@ -22,8 +25,8 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// <remarks>Use this constructor when the validator is used as an <see cref="IValueFormatValidator"/>,
|
||||
/// and the regular expression is supplied at validation time. This constructor is also used when
|
||||
/// the validator is used as an <see cref="IManifestValueValidator"/> and the regular expression
|
||||
/// is supplied via the <see cref="Configure"/> method.</remarks>
|
||||
public RegexValidator()
|
||||
/// is supplied via the <see cref="Configuration"/> method.</remarks>
|
||||
public RegexValidator() : this(Current.Services.TextService, null)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
@@ -31,10 +34,9 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// </summary>
|
||||
/// <remarks>Use this constructor when the validator is used as an <see cref="IValueValidator"/>,
|
||||
/// and the regular expression must be supplied when the validator is created.</remarks>
|
||||
public RegexValidator(string regex)
|
||||
public RegexValidator(ILocalizedTextService textService, string regex)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(regex))
|
||||
throw new ArgumentNullOrEmptyException(nameof(regex));
|
||||
_textService = textService;
|
||||
_regex = regex;
|
||||
}
|
||||
|
||||
@@ -66,7 +68,7 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(format)) throw new ArgumentNullOrEmptyException(nameof(format));
|
||||
if (value == null || !new Regex(format).IsMatch(value.ToString()))
|
||||
yield return new ValidationResult("Value is invalid, it does not match the correct pattern", new[] { "value" });
|
||||
yield return new ValidationResult(_textService.Localize("validation", "invalidPattern"), new[] { "value" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Core.PropertyEditors.Validators
|
||||
{
|
||||
@@ -8,6 +10,17 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
/// </summary>
|
||||
internal sealed class RequiredValidator : IValueRequiredValidator, IManifestValueValidator
|
||||
{
|
||||
private readonly ILocalizedTextService _textService;
|
||||
|
||||
public RequiredValidator() : this(Current.Services.TextService)
|
||||
{
|
||||
}
|
||||
|
||||
public RequiredValidator(ILocalizedTextService textService)
|
||||
{
|
||||
_textService = textService;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IManifestValueValidator.ValidationName"/>
|
||||
public string ValidationName => "Required";
|
||||
|
||||
@@ -22,20 +35,20 @@ namespace Umbraco.Core.PropertyEditors.Validators
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
yield return new ValidationResult("Value cannot be null", new[] {"value"});
|
||||
yield return new ValidationResult(_textService.Localize("validation", "invalidNull"), new[] {"value"});
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (valueType.InvariantEquals(ValueTypes.Json))
|
||||
{
|
||||
if (value.ToString().DetectIsEmptyJson())
|
||||
yield return new ValidationResult("Value cannot be empty", new[] { "value" });
|
||||
yield return new ValidationResult(_textService.Localize("validation", "invalidEmpty"), new[] { "value" });
|
||||
yield break;
|
||||
}
|
||||
|
||||
if (value.ToString().IsNullOrWhiteSpace())
|
||||
{
|
||||
yield return new ValidationResult("Value cannot be empty", new[] { "value" });
|
||||
yield return new ValidationResult(_textService.Localize("validation", "invalidEmpty"), new[] { "value" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Umbraco.Core.PropertyEditors.ValueConverters
|
||||
return Storages.GetOrAdd(dataTypeId, id =>
|
||||
{
|
||||
var dataType = _dataTypeService.GetDataType(id);
|
||||
var configuration = dataType.ConfigurationAs<SliderPropertyEditorConfiguration>();
|
||||
var configuration = dataType.ConfigurationAs<SliderConfiguration>();
|
||||
return configuration.EnableRange;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1010,7 +1010,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var originalPath = media.Path;
|
||||
|
||||
if (scope.Events.DispatchCancelable(Trashing, this, new MoveEventArgs<IMedia>(new MoveEventInfo<IMedia>(media, originalPath, Constants.System.RecycleBinMedia))))
|
||||
if (scope.Events.DispatchCancelable(Trashing, this, new MoveEventArgs<IMedia>(new MoveEventInfo<IMedia>(media, originalPath, Constants.System.RecycleBinMedia)), nameof(Trashing)))
|
||||
{
|
||||
scope.Complete();
|
||||
return OperationResult.Attempt.Cancel(evtMsgs);
|
||||
@@ -1022,7 +1022,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
var moveInfo = moves.Select(x => new MoveEventInfo<IMedia>(x.Item1, x.Item2, x.Item1.ParentId))
|
||||
.ToArray();
|
||||
|
||||
scope.Events.Dispatch(Trashed, this, new MoveEventArgs<IMedia>(false, evtMsgs, moveInfo));
|
||||
scope.Events.Dispatch(Trashed, this, new MoveEventArgs<IMedia>(false, evtMsgs, moveInfo), nameof(Trashed));
|
||||
Audit(AuditType.Move, "Move Media to Recycle Bin performed by user", userId, media.Id);
|
||||
|
||||
scope.Complete();
|
||||
@@ -1058,7 +1058,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var moveEventInfo = new MoveEventInfo<IMedia>(media, media.Path, parentId);
|
||||
var moveEventArgs = new MoveEventArgs<IMedia>(moveEventInfo);
|
||||
if (scope.Events.DispatchCancelable(Moving, this, moveEventArgs))
|
||||
if (scope.Events.DispatchCancelable(Moving, this, moveEventArgs, nameof(Moving)))
|
||||
{
|
||||
scope.Complete();
|
||||
return;
|
||||
@@ -1082,7 +1082,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
.ToArray();
|
||||
moveEventArgs.MoveInfoCollection = moveInfo;
|
||||
moveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Moved, this, moveEventArgs);
|
||||
scope.Events.Dispatch(Moved, this, moveEventArgs, nameof(Moved));
|
||||
Audit(AuditType.Move, "Move Media performed by user", userId, media.Id);
|
||||
scope.Complete();
|
||||
}
|
||||
@@ -1382,7 +1382,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <para>Deletes media items of the specified type, and only that type. Does *not* handle content types
|
||||
/// inheritance and compositions, which need to be managed outside of this method.</para>
|
||||
/// </remarks>
|
||||
/// <param name="mediaTypeId">Id of the <see cref="IMediaType"/></param>
|
||||
/// <param name="mediaTypeIds">Id of the <see cref="IMediaType"/></param>
|
||||
/// <param name="userId">Optional id of the user deleting the media</param>
|
||||
public void DeleteMediaOfTypes(IEnumerable<int> mediaTypeIds, int userId = 0)
|
||||
{
|
||||
@@ -1434,7 +1434,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
var moveInfos = moves.Select(x => new MoveEventInfo<IMedia>(x.Item1, x.Item2, x.Item1.ParentId))
|
||||
.ToArray();
|
||||
if (moveInfos.Length > 0)
|
||||
scope.Events.Dispatch(Trashed, this, new MoveEventArgs<IMedia>(false, moveInfos), "Trashed");
|
||||
scope.Events.Dispatch(Trashed, this, new MoveEventArgs<IMedia>(false, moveInfos), nameof(Trashed));
|
||||
scope.Events.Dispatch(TreeChanged, this, changes.ToEventArgs());
|
||||
|
||||
Audit(AuditType.Delete, $"Delete Media of types {string.Join(",", mediaTypeIdsA)} performed by user", userId, Constants.System.Root);
|
||||
|
||||
@@ -411,7 +411,7 @@
|
||||
<Compile Include="PropertyEditors\PropertyEditorCollection.cs" />
|
||||
<Compile Include="PropertyEditors\PropertyEditorTagsExtensions.cs" />
|
||||
<Compile Include="PropertyEditors\PropertyValueLevel.cs" />
|
||||
<Compile Include="PropertyEditors\SliderPropertyEditorConfiguration.cs" />
|
||||
<Compile Include="PropertyEditors\SliderConfiguration.cs" />
|
||||
<Compile Include="PropertyEditors\TagConfiguration.cs" />
|
||||
<Compile Include="PropertyEditors\ValueConverters\ImageCropperValue.cs" />
|
||||
<Compile Include="PropertyEditors\ValueConverters\ImageCropperValueTypeConverter.cs" />
|
||||
|
||||
@@ -389,6 +389,8 @@ namespace Umbraco.Examine
|
||||
//the value of the field 'as-is'.
|
||||
foreach (var value in e.IndexItem.ValueSet.Values.ToList()) //ToList here to make a diff collection else we'll get collection modified errors
|
||||
{
|
||||
if (value.Value == null) continue;
|
||||
|
||||
if (value.Value.Count > 0)
|
||||
{
|
||||
if (value.Value.First() is string str)
|
||||
|
||||
@@ -272,7 +272,7 @@ AnotherContentFinder
|
||||
public void Resolves_Actions()
|
||||
{
|
||||
var actions = _typeLoader.GetActions();
|
||||
Assert.AreEqual(35, actions.Count());
|
||||
Assert.AreEqual(34, actions.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -67,9 +67,6 @@ namespace Umbraco.Tests.Configurations
|
||||
Assert.IsTrue(globalSettings.IsReservedPathOrUrl(url));
|
||||
}
|
||||
|
||||
[TestCase("/umbraco_client/Tree/treeIcons.css")]
|
||||
[TestCase("/umbraco_client/Tree/Themes/umbraco/style.css")]
|
||||
[TestCase("/umbraco_client/scrollingmenu/style.css")]
|
||||
[TestCase("/base/somebasehandler")]
|
||||
[TestCase("/")]
|
||||
[TestCase("/home.aspx")]
|
||||
|
||||
@@ -417,7 +417,6 @@ namespace Umbraco.Tests.CoreXml
|
||||
{
|
||||
var source = new TestSource5();
|
||||
var nav = new NavigableNavigator(source);
|
||||
TestContent content;
|
||||
|
||||
Assert.AreEqual(NavigableNavigator.StatePosition.Root, nav.InternalState.Position);
|
||||
Assert.IsTrue(nav.MoveToFirstChild());
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using System.Text;
|
||||
using LightInject;
|
||||
using NUnit.Framework;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Cache;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Manifest;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.PropertyEditors.Validators;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Tests.Manifest
|
||||
{
|
||||
@@ -22,10 +25,25 @@ namespace Umbraco.Tests.Manifest
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
Current.Reset();
|
||||
var container = Mock.Of<IServiceContainer>();
|
||||
Current.Container = container;
|
||||
|
||||
var serviceContext = new ServiceContext(
|
||||
localizedTextService: Mock.Of<ILocalizedTextService>());
|
||||
|
||||
Mock.Get(container)
|
||||
.Setup(x => x.GetInstance(It.IsAny<Type>()))
|
||||
.Returns<Type>(x =>
|
||||
{
|
||||
if (x == typeof(ServiceContext)) return serviceContext;
|
||||
throw new Exception("oops");
|
||||
});
|
||||
|
||||
var validators = new IManifestValueValidator[]
|
||||
{
|
||||
new RequiredValidator(),
|
||||
new RegexValidator()
|
||||
new RequiredValidator(Mock.Of<ILocalizedTextService>()),
|
||||
new RegexValidator(Mock.Of<ILocalizedTextService>(), null)
|
||||
};
|
||||
_parser = new ManifestParser(NullCacheProvider.Instance, new ManifestValueValidatorCollection(validators), Mock.Of<ILogger>());
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ namespace Umbraco.Tests.Models.Collections
|
||||
public abstract class Item : IEntity, ICanBeDirty
|
||||
{
|
||||
private bool _hasIdentity;
|
||||
private int? _hash;
|
||||
private int _id;
|
||||
private Guid _key;
|
||||
|
||||
@@ -184,12 +183,6 @@ namespace Umbraco.Tests.Models.Collections
|
||||
|
||||
public static bool operator ==(Item left, Item right)
|
||||
{
|
||||
/*if (ReferenceEquals(null, left))
|
||||
return false;
|
||||
|
||||
if(ReferenceEquals(null, right))
|
||||
return false;*/
|
||||
|
||||
return ReferenceEquals(left, right);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
var content = MockedMedia.CreateMediaImage(contentType, -1);
|
||||
FixUsers(content);
|
||||
|
||||
var result = Mapper.Map<IMedia, ContentItemBasic<ContentPropertyBasic, IMedia>>(content);
|
||||
var result = Mapper.Map<IMedia, ContentItemBasic<ContentPropertyBasic>>(content);
|
||||
|
||||
AssertBasics(result, content);
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
var content = MockedContent.CreateSimpleContent(contentType);
|
||||
FixUsers(content);
|
||||
|
||||
var result = Mapper.Map<IContent, ContentItemBasic<ContentPropertyBasic, IContent>>(content);
|
||||
var result = Mapper.Map<IContent, ContentItemBasic<ContentPropertyBasic>>(content);
|
||||
|
||||
AssertBasics(result, content);
|
||||
|
||||
@@ -90,9 +90,12 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
var content = MockedContent.CreateSimpleContent(contentType);
|
||||
FixUsers(content);
|
||||
|
||||
var result = Mapper.Map<IContent, ContentItemDto<IContent>>(content);
|
||||
var result = Mapper.Map<IContent, ContentPropertyCollectionDto>(content);
|
||||
|
||||
AssertContentItem(result, content);
|
||||
foreach (var p in content.Properties)
|
||||
{
|
||||
AssertProperty(result, p);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -102,9 +105,12 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
var content = MockedMedia.CreateMediaImage(contentType, -1);
|
||||
FixUsers(content);
|
||||
|
||||
var result = Mapper.Map<IMedia, ContentItemDto<IMedia>>(content);
|
||||
var result = Mapper.Map<IMedia, ContentPropertyCollectionDto>(content);
|
||||
|
||||
AssertContentItem(result, content);
|
||||
foreach (var p in content.Properties)
|
||||
{
|
||||
AssertProperty(result, p);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -123,12 +129,16 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
|
||||
AssertBasics(result, content);
|
||||
|
||||
var invariantContent = result.Variants.First();
|
||||
foreach (var p in content.Properties)
|
||||
AssertDisplayProperty(result, p);
|
||||
{
|
||||
AssertBasicProperty(invariantContent, p);
|
||||
AssertDisplayProperty(invariantContent, p);
|
||||
}
|
||||
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count());
|
||||
Assert.IsTrue(result.Tabs.First().IsActive);
|
||||
Assert.IsTrue(result.Tabs.Except(new[] {result.Tabs.First()}).All(x => x.IsActive == false));
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), invariantContent.Tabs.Count());
|
||||
Assert.IsTrue(invariantContent.Tabs.First().IsActive);
|
||||
Assert.IsTrue(invariantContent.Tabs.Except(new[] { invariantContent.Tabs.First() }).All(x => x.IsActive == false));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -141,11 +151,15 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
var result = Mapper.Map<IContent, ContentItemDisplay>(content);
|
||||
|
||||
AssertBasics(result, content);
|
||||
|
||||
var invariantContent = result.Variants.First();
|
||||
foreach (var p in content.Properties)
|
||||
{
|
||||
AssertDisplayProperty(result, p);
|
||||
AssertBasicProperty(invariantContent, p);
|
||||
AssertDisplayProperty(invariantContent, p);
|
||||
}
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count());
|
||||
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), invariantContent.Tabs.Count());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -186,23 +200,24 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
var result = Mapper.Map<IContent, ContentItemDisplay>(content);
|
||||
|
||||
AssertBasics(result, content);
|
||||
|
||||
var invariantContent = result.Variants.First();
|
||||
foreach (var p in content.Properties)
|
||||
{
|
||||
AssertDisplayProperty(result, p);
|
||||
AssertBasicProperty(invariantContent, p);
|
||||
AssertDisplayProperty(invariantContent, p);
|
||||
}
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), result.Tabs.Count() - 1);
|
||||
Assert.IsTrue(result.Tabs.Any(x => x.Label == Current.Services.TextService.Localize("general/properties")));
|
||||
Assert.AreEqual(2, result.Tabs.Where(x => x.Label == Current.Services.TextService.Localize("general/properties")).SelectMany(x => x.Properties.Where(p => p.Alias.StartsWith("_umb_") == false)).Count());
|
||||
|
||||
Assert.AreEqual(content.PropertyGroups.Count(), invariantContent.Tabs.Count() - 1);
|
||||
Assert.IsTrue(invariantContent.Tabs.Any(x => x.Label == Current.Services.TextService.Localize("general/properties")));
|
||||
Assert.AreEqual(2, invariantContent.Tabs.Where(x => x.Label == Current.Services.TextService.Localize("general/properties")).SelectMany(x => x.Properties.Where(p => p.Alias.StartsWith("_umb_") == false)).Count());
|
||||
}
|
||||
|
||||
#region Assertions
|
||||
|
||||
private void AssertDisplayProperty<T, TPersisted>(ContentItemBasic<T, TPersisted> result, Property p)
|
||||
where T : ContentPropertyDisplay
|
||||
where TPersisted : IContentBase
|
||||
private void AssertDisplayProperty<T>(IContentProperties<T> result, Property p)
|
||||
where T : ContentPropertyBasic
|
||||
{
|
||||
AssertBasicProperty(result, p);
|
||||
|
||||
var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias);
|
||||
Assert.IsNotNull(pDto);
|
||||
|
||||
@@ -213,7 +228,33 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
|
||||
}
|
||||
|
||||
private void AssertBasics<T, TPersisted>(ContentItemBasic<T, TPersisted> result, TPersisted content)
|
||||
private void AssertBasics(ContentItemDisplay result, IContent content)
|
||||
{
|
||||
Assert.AreEqual(content.Id, result.Id);
|
||||
|
||||
var ownerId = content.CreatorId;
|
||||
if (ownerId != 0)
|
||||
{
|
||||
Assert.IsNotNull(result.Owner);
|
||||
Assert.AreEqual(Constants.Security.SuperUserId, result.Owner.UserId);
|
||||
Assert.AreEqual("Administrator", result.Owner.Name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Assert.IsNull(result.Owner); // because, 0 is no user
|
||||
}
|
||||
|
||||
var invariantContent = result.Variants.First();
|
||||
|
||||
Assert.AreEqual(content.ParentId, result.ParentId);
|
||||
Assert.AreEqual(content.UpdateDate, invariantContent.UpdateDate);
|
||||
Assert.AreEqual(content.CreateDate, invariantContent.CreateDate);
|
||||
Assert.AreEqual(content.Name, invariantContent.Name);
|
||||
Assert.AreEqual(content.Properties.Count(),
|
||||
((IContentProperties<ContentPropertyDisplay>)invariantContent).Properties.Count(x => x.Alias.StartsWith("_umb_") == false));
|
||||
}
|
||||
|
||||
private void AssertBasics<T, TPersisted>(ContentItemBasic<T> result, TPersisted content)
|
||||
where T : ContentPropertyBasic
|
||||
where TPersisted : IContentBase
|
||||
{
|
||||
@@ -238,9 +279,8 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
Assert.AreEqual(content.Properties.Count(), result.Properties.Count(x => x.Alias.StartsWith("_umb_") == false));
|
||||
}
|
||||
|
||||
private void AssertBasicProperty<T, TPersisted>(ContentItemBasic<T, TPersisted> result, Property p)
|
||||
private void AssertBasicProperty<T>(IContentProperties<T> result, Property p)
|
||||
where T : ContentPropertyBasic
|
||||
where TPersisted : IContentBase
|
||||
{
|
||||
var pDto = result.Properties.SingleOrDefault(x => x.Alias == p.Alias);
|
||||
Assert.IsNotNull(pDto);
|
||||
@@ -255,8 +295,7 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
Assert.AreEqual(pDto.Value, p.GetValue().ToString());
|
||||
}
|
||||
|
||||
private void AssertProperty<TPersisted>(ContentItemBasic<ContentPropertyDto, TPersisted> result, Property p)
|
||||
where TPersisted : IContentBase
|
||||
private void AssertProperty(IContentProperties<ContentPropertyDto> result, Property p)
|
||||
{
|
||||
AssertBasicProperty(result, p);
|
||||
|
||||
@@ -270,7 +309,7 @@ namespace Umbraco.Tests.Models.Mapping
|
||||
Assert.AreEqual(Current.PropertyEditors[p.PropertyType.PropertyEditorAlias], pDto.PropertyEditor);
|
||||
}
|
||||
|
||||
private void AssertContentItem<T>(ContentItemBasic<ContentPropertyDto, T> result, T content)
|
||||
private void AssertContentItem<T>(ContentItemBasic<ContentPropertyDto> result, T content)
|
||||
where T : IContentBase
|
||||
{
|
||||
AssertBasics(result, content);
|
||||
|
||||
@@ -45,7 +45,9 @@ namespace Umbraco.Tests.Models
|
||||
.Setup(x => x.GetDataType(It.IsAny<int>()))
|
||||
.Returns<int>(x => dataType);
|
||||
|
||||
var serviceContext = new ServiceContext(dataTypeService: dataTypeService);
|
||||
var serviceContext = new ServiceContext(
|
||||
dataTypeService: dataTypeService,
|
||||
localizedTextService: Mock.Of<ILocalizedTextService>());
|
||||
|
||||
Mock.Get(container)
|
||||
.Setup(x => x.GetInstance(It.IsAny<Type>()))
|
||||
@@ -53,8 +55,10 @@ namespace Umbraco.Tests.Models
|
||||
{
|
||||
if (x == typeof(PropertyEditorCollection)) return propertyEditors;
|
||||
if (x == typeof(ServiceContext)) return serviceContext;
|
||||
if (x == typeof(ILocalizedTextService)) return serviceContext.LocalizationService;
|
||||
throw new Exception("oops");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -77,7 +77,6 @@ namespace Umbraco.Tests.Routing
|
||||
// do not test for /base here as it's handled before EnsureUmbracoRoutablePage is called
|
||||
[TestCase("/umbraco_client/Tree/treeIcons.css", false)]
|
||||
[TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", false)]
|
||||
[TestCase("/umbraco_client/scrollingmenu/style.css?cdv=37", false)]
|
||||
[TestCase("/umbraco/umbraco.aspx", false)]
|
||||
[TestCase("/umbraco/editContent.aspx", false)]
|
||||
[TestCase("/install/default.aspx", false)]
|
||||
@@ -103,7 +102,6 @@ namespace Umbraco.Tests.Routing
|
||||
[TestCase("/favicon.ico", true)]
|
||||
[TestCase("/umbraco_client/Tree/treeIcons.css", true)]
|
||||
[TestCase("/umbraco_client/Tree/Themes/umbraco/style.css?cdv=37", true)]
|
||||
[TestCase("/umbraco_client/scrollingmenu/style.css?cdv=37", true)]
|
||||
[TestCase("/base/somebasehandler", false)]
|
||||
[TestCase("/", false)]
|
||||
[TestCase("/home.aspx", false)]
|
||||
|
||||
@@ -946,8 +946,6 @@ namespace Umbraco.Tests.Scheduling
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private int i;
|
||||
|
||||
public async Task RunAsync(CancellationToken token)
|
||||
{
|
||||
Console.WriteLine("boom");
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace Umbraco.Tests.Services
|
||||
/// <summary>
|
||||
/// Used to list out all ambiguous events that will require dispatching with a name
|
||||
/// </summary>
|
||||
[Test]
|
||||
[Test, Explicit]
|
||||
public void List_Ambiguous_Events()
|
||||
{
|
||||
var events = ServiceContext.ContentService.GetType().GetEvents(BindingFlags.Static | BindingFlags.Public);
|
||||
|
||||
@@ -1,16 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Services.Implement;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
@@ -22,6 +25,32 @@ namespace Umbraco.Tests.Services
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, PublishedRepositoryEvents = true)]
|
||||
public class MediaServiceTests : TestWithSomeContentBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to list out all ambiguous events that will require dispatching with a name
|
||||
/// </summary>
|
||||
[Test, Explicit]
|
||||
public void List_Ambiguous_Events()
|
||||
{
|
||||
var events = ServiceContext.MediaService.GetType().GetEvents(BindingFlags.Static | BindingFlags.Public);
|
||||
var typedEventHandler = typeof(TypedEventHandler<,>);
|
||||
foreach (var e in events)
|
||||
{
|
||||
//only continue if this is a TypedEventHandler
|
||||
if (!e.EventHandlerType.IsGenericType) continue;
|
||||
var typeDef = e.EventHandlerType.GetGenericTypeDefinition();
|
||||
if (typedEventHandler != typeDef) continue;
|
||||
|
||||
//get the event arg type
|
||||
var eventArgType = e.EventHandlerType.GenericTypeArguments[1];
|
||||
|
||||
var found = EventNameExtractor.FindEvent(typeof(MediaService), eventArgType, EventNameExtractor.MatchIngNames);
|
||||
if (!found.Success && found.Result.Error == EventNameExtractorError.Ambiguous)
|
||||
{
|
||||
Console.WriteLine($"Ambiguous event, source: {typeof(MediaService)}, args: {eventArgType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Children_With_Media_Type_Filter()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,9 @@ using Umbraco.Core.Security;
|
||||
|
||||
namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures there's an admin user assigned to the request
|
||||
/// </summary>
|
||||
public class AuthenticateEverythingMiddleware : AuthenticationMiddleware<AuthenticationOptions>
|
||||
{
|
||||
public AuthenticateEverythingMiddleware(OwinMiddleware next, IAppBuilder app, AuthenticationOptions options)
|
||||
@@ -26,7 +29,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
{
|
||||
var sessionId = Guid.NewGuid().ToString();
|
||||
var identity = new UmbracoBackOfficeIdentity(
|
||||
-1, "admin", "Admin", null, null, "en-US", sessionId, sessionId, new[] { "content", "media", "members" }, new[] { "admin" });
|
||||
-1, "admin", "Admin", new []{-1}, new[] { -1 }, "en-US", sessionId, sessionId, new[] { "content", "media", "members" }, new[] { "admin" });
|
||||
|
||||
return Task.FromResult(new AuthenticationTicket(identity,
|
||||
new AuthenticationProperties()
|
||||
|
||||
@@ -22,8 +22,16 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
_controllerFactory = controllerFactory;
|
||||
}
|
||||
|
||||
public async Task<Tuple<HttpResponseMessage, string>> Execute(string controllerName, string actionName, HttpMethod method, HttpContent content = null)
|
||||
public async Task<Tuple<HttpResponseMessage, string>> Execute(string controllerName, string actionName, HttpMethod method,
|
||||
HttpContent content = null,
|
||||
MediaTypeWithQualityHeaderValue mediaTypeHeader = null,
|
||||
bool assertOkResponse = true)
|
||||
{
|
||||
if (mediaTypeHeader == null)
|
||||
{
|
||||
mediaTypeHeader = new MediaTypeWithQualityHeaderValue("application/json");
|
||||
}
|
||||
|
||||
var startup = new TestStartup(
|
||||
configuration =>
|
||||
{
|
||||
@@ -44,7 +52,7 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
if (content != null)
|
||||
request.Content = content;
|
||||
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
request.Headers.Accept.Add(mediaTypeHeader);
|
||||
|
||||
Console.WriteLine(request);
|
||||
var response = await server.HttpClient.SendAsync(request);
|
||||
@@ -62,7 +70,11 @@ namespace Umbraco.Tests.TestHelpers.ControllerTesting
|
||||
Console.Write(JsonConvert.SerializeObject(deserialized, Formatting.Indented));
|
||||
}
|
||||
|
||||
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
|
||||
if (assertOkResponse)
|
||||
{
|
||||
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
|
||||
}
|
||||
|
||||
return Tuple.Create(response, json);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,6 +213,7 @@
|
||||
<Compile Include="Collections\DeepCloneableListTests.cs" />
|
||||
<Compile Include="Web\Controllers\BackOfficeControllerUnitTests.cs" />
|
||||
<Compile Include="CoreThings\DelegateExtensionsTests.cs" />
|
||||
<Compile Include="Web\Controllers\ContentControllerTests.cs" />
|
||||
<Compile Include="Web\Controllers\UserEditorAuthorizationHelperTests.cs" />
|
||||
<Compile Include="Web\Controllers\UsersControllerTests.cs" />
|
||||
<Compile Include="Web\HealthChecks\HealthCheckResultsTests.cs" />
|
||||
|
||||
@@ -16,59 +16,66 @@ namespace Umbraco.Tests.Web.AngularIntegration
|
||||
{
|
||||
//create 3 tabs with 3 properties each
|
||||
var tabs = new List<Tab<ContentPropertyDisplay>>();
|
||||
for (var tabIndex = 0; tabIndex < 3; tabIndex ++)
|
||||
for (var tabIndex = 0; tabIndex < 3; tabIndex++)
|
||||
{
|
||||
var props = new List<ContentPropertyDisplay>();
|
||||
for (var propertyIndex = 0; propertyIndex < 3; propertyIndex ++)
|
||||
for (var propertyIndex = 0; propertyIndex < 3; propertyIndex++)
|
||||
{
|
||||
props.Add(new ContentPropertyDisplay
|
||||
{
|
||||
Alias = "property" + propertyIndex,
|
||||
Label = "Property " + propertyIndex,
|
||||
Id = propertyIndex,
|
||||
Value = "value" + propertyIndex,
|
||||
Config = new Dictionary<string, object> {{ propertyIndex.ToInvariantString(), "value" }},
|
||||
Description = "Description " + propertyIndex,
|
||||
View = "~/Views/View" + propertyIndex,
|
||||
HideLabel = false
|
||||
});
|
||||
{
|
||||
Alias = "property" + propertyIndex,
|
||||
Label = "Property " + propertyIndex,
|
||||
Id = propertyIndex,
|
||||
Value = "value" + propertyIndex,
|
||||
Config = new Dictionary<string, object> { { propertyIndex.ToInvariantString(), "value" } },
|
||||
Description = "Description " + propertyIndex,
|
||||
View = "~/Views/View" + propertyIndex,
|
||||
HideLabel = false
|
||||
});
|
||||
}
|
||||
tabs.Add(new Tab<ContentPropertyDisplay>()
|
||||
{
|
||||
Alias = "Tab" + tabIndex,
|
||||
Label = "Tab" + tabIndex,
|
||||
Properties = props
|
||||
});
|
||||
{
|
||||
Alias = "Tab" + tabIndex,
|
||||
Label = "Tab" + tabIndex,
|
||||
Properties = props
|
||||
});
|
||||
}
|
||||
|
||||
var displayModel = new ContentItemDisplay
|
||||
{
|
||||
Id = 1234,
|
||||
Variants = new List<ContentVariantDisplay>
|
||||
{
|
||||
Id = 1234,
|
||||
Name = "Test",
|
||||
Tabs = tabs
|
||||
};
|
||||
new ContentVariantDisplay
|
||||
{
|
||||
Name = "Test",
|
||||
Tabs = tabs
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var json = JsonConvert.SerializeObject(displayModel);
|
||||
|
||||
var jObject = JObject.Parse(json);
|
||||
|
||||
Assert.AreEqual("1234", jObject["id"].ToString());
|
||||
Assert.AreEqual("Test", jObject["name"].ToString());
|
||||
Assert.AreEqual(3, jObject["tabs"].Count());
|
||||
for (var tab = 0; tab < jObject["tabs"].Count(); tab++)
|
||||
Assert.AreEqual("Test", jObject["variants"][0]["name"].ToString());
|
||||
var jsonTabs = jObject["variants"][0]["tabs"];
|
||||
Assert.AreEqual(3, jsonTabs.Count());
|
||||
for (var tab = 0; tab < jsonTabs.Count(); tab++)
|
||||
{
|
||||
Assert.AreEqual("Tab" + tab, jObject["tabs"][tab]["alias"].ToString());
|
||||
Assert.AreEqual("Tab" + tab, jObject["tabs"][tab]["label"].ToString());
|
||||
Assert.AreEqual(3, jObject["tabs"][tab]["properties"].Count());
|
||||
for (var prop = 0; prop < jObject["tabs"][tab]["properties"].Count(); prop++)
|
||||
Assert.AreEqual("Tab" + tab, jsonTabs[tab]["alias"].ToString());
|
||||
Assert.AreEqual("Tab" + tab, jsonTabs[tab]["label"].ToString());
|
||||
Assert.AreEqual(3, jsonTabs[tab]["properties"].Count());
|
||||
for (var prop = 0; prop < jsonTabs[tab]["properties"].Count(); prop++)
|
||||
{
|
||||
Assert.AreEqual("property" + prop, jObject["tabs"][tab]["properties"][prop]["alias"].ToString());
|
||||
Assert.AreEqual("Property " + prop, jObject["tabs"][tab]["properties"][prop]["label"].ToString());
|
||||
Assert.AreEqual(prop, jObject["tabs"][tab]["properties"][prop]["id"].Value<int>());
|
||||
Assert.AreEqual("value" + prop, jObject["tabs"][tab]["properties"][prop]["value"].ToString());
|
||||
Assert.AreEqual("{\"" + prop + "\":\"value\"}", jObject["tabs"][tab]["properties"][prop]["config"].ToString(Formatting.None));
|
||||
Assert.AreEqual("Description " + prop, jObject["tabs"][tab]["properties"][prop]["description"].ToString());
|
||||
Assert.AreEqual(false, jObject["tabs"][tab]["properties"][prop]["hideLabel"].Value<bool>());
|
||||
Assert.AreEqual("property" + prop, jsonTabs[tab]["properties"][prop]["alias"].ToString());
|
||||
Assert.AreEqual("Property " + prop, jsonTabs[tab]["properties"][prop]["label"].ToString());
|
||||
Assert.AreEqual(prop, jsonTabs[tab]["properties"][prop]["id"].Value<int>());
|
||||
Assert.AreEqual("value" + prop, jsonTabs[tab]["properties"][prop]["value"].ToString());
|
||||
Assert.AreEqual("{\"" + prop + "\":\"value\"}", jsonTabs[tab]["properties"][prop]["config"].ToString(Formatting.None));
|
||||
Assert.AreEqual("Description " + prop, jsonTabs[tab]["properties"][prop]["description"].ToString());
|
||||
Assert.AreEqual(false, jsonTabs[tab]["properties"][prop]["hideLabel"].Value<bool>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Web.Http;
|
||||
using Moq;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.ControllerTesting;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Web;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web._Legacy.Actions;
|
||||
using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Umbraco.Tests.Web.Controllers
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.None)]
|
||||
public class ContentControllerTests : TestWithDatabaseBase
|
||||
{
|
||||
protected override void ComposeApplication(bool withApplication)
|
||||
{
|
||||
base.ComposeApplication(withApplication);
|
||||
|
||||
//Replace with mockable services:
|
||||
|
||||
var userServiceMock = new Mock<IUserService>();
|
||||
userServiceMock.Setup(service => service.GetUserById(It.IsAny<int>()))
|
||||
.Returns((int id) => id == 1234 ? new User(1234, "Test", "test@test.com", "test@test.com", "", new List<IReadOnlyUserGroup>(), new int[0], new int[0]) : null);
|
||||
userServiceMock.Setup(service => service.GetPermissionsForPath(It.IsAny<IUser>(), It.IsAny<string>()))
|
||||
.Returns(new EntityPermissionSet(123, new EntityPermissionCollection(new[]
|
||||
{
|
||||
new EntityPermission(0, 123, new[]
|
||||
{
|
||||
ActionBrowse.Instance.Letter.ToString(),
|
||||
ActionUpdate.Instance.Letter.ToString(),
|
||||
ActionPublish.Instance.Letter.ToString(),
|
||||
ActionNew.Instance.Letter.ToString()
|
||||
}),
|
||||
})));
|
||||
|
||||
var entityService = new Mock<IEntityService>();
|
||||
entityService.Setup(x => x.GetAllPaths(UmbracoObjectTypes.Document, It.IsAny<int[]>()))
|
||||
.Returns((UmbracoObjectTypes objType, int[] ids) => ids.Select(x => new TreeEntityPath {Path = $"-1,{x}", Id = x}).ToList());
|
||||
|
||||
var dataTypeService = new Mock<IDataTypeService>();
|
||||
dataTypeService.Setup(service => service.GetDataType(It.IsAny<int>()))
|
||||
.Returns(MockedDataType());
|
||||
|
||||
Container.RegisterSingleton(f => Mock.Of<IContentService>());
|
||||
Container.RegisterSingleton(f => userServiceMock.Object);
|
||||
Container.RegisterSingleton(f => entityService.Object);
|
||||
Container.RegisterSingleton(f => dataTypeService.Object);
|
||||
}
|
||||
|
||||
private IDataType MockedDataType()
|
||||
{
|
||||
return Mock.Of<IDataType>(type => type.Id == 9876 && type.Name == "text");
|
||||
}
|
||||
|
||||
private MultipartFormDataContent GetMultiPartRequestContent(string json)
|
||||
{
|
||||
var multiPartBoundary = "----WebKitFormBoundary123456789";
|
||||
return new MultipartFormDataContent(multiPartBoundary)
|
||||
{
|
||||
new StringContent(json)
|
||||
{
|
||||
Headers =
|
||||
{
|
||||
ContentDisposition = new ContentDispositionHeaderValue("form-data")
|
||||
{
|
||||
Name = "contentItem"
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private const string PublishJson1 = @"{
|
||||
""id"": 123,
|
||||
""contentTypeAlias"": ""page"",
|
||||
""parentId"": -1,
|
||||
""action"": ""save"",
|
||||
""variants"": [
|
||||
{
|
||||
""name"": null,
|
||||
""properties"": [
|
||||
{
|
||||
""id"": 1,
|
||||
""alias"": ""title"",
|
||||
""value"": ""asdf""
|
||||
}
|
||||
],
|
||||
""culture"": ""en-US""
|
||||
},
|
||||
{
|
||||
""name"": null,
|
||||
""properties"": [
|
||||
{
|
||||
""id"": 1,
|
||||
""alias"": ""title"",
|
||||
""value"": ""asdf""
|
||||
}
|
||||
],
|
||||
""culture"": ""fr-FR""
|
||||
},
|
||||
{
|
||||
""name"": ""asdf"",
|
||||
""properties"": [
|
||||
{
|
||||
""id"": 1,
|
||||
""alias"": ""title"",
|
||||
""value"": ""asdf""
|
||||
}
|
||||
],
|
||||
""culture"": ""es-ES"",
|
||||
""save"": true,
|
||||
""publish"": true
|
||||
}
|
||||
]
|
||||
}";
|
||||
|
||||
/// <summary>
|
||||
/// Returns 404 if the content wasn't found based on the ID specified
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Test]
|
||||
public async Task PostSave_Validate_Existing_Content()
|
||||
{
|
||||
ApiController Factory(HttpRequestMessage message, UmbracoHelper helper)
|
||||
{
|
||||
//var content = MockedContent.CreateSimpleContent(MockedContentTypes.CreateSimpleContentType());
|
||||
//content.Id = 999999999; //this will not be found
|
||||
//content.Path = "-1,999999999";
|
||||
|
||||
var contentServiceMock = Mock.Get(Current.Services.ContentService);
|
||||
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null);
|
||||
|
||||
var publishedSnapshot = Mock.Of<IPublishedSnapshotService>();
|
||||
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()));
|
||||
var usersController = new ContentController(publishedSnapshot, propertyEditorCollection);
|
||||
Container.InjectProperties(usersController);
|
||||
return usersController;
|
||||
}
|
||||
|
||||
var runner = new TestRunner(Factory);
|
||||
var response = await runner.Execute("Content", "PostSave", HttpMethod.Post,
|
||||
content: GetMultiPartRequestContent(PublishJson1),
|
||||
mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"),
|
||||
assertOkResponse: false);
|
||||
|
||||
Assert.AreEqual(HttpStatusCode.NotFound, response.Item1.StatusCode);
|
||||
Assert.AreEqual(")]}',\n{\"Message\":\"content was not found\"}", response.Item1.Content.ReadAsStringAsync().Result);
|
||||
|
||||
//var obj = JsonConvert.DeserializeObject<PagedResult<UserDisplay>>(response.Item2);
|
||||
//Assert.AreEqual(0, obj.TotalItems);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task PostSave_Validate_At_Least_One_Variant_Flagged_For_Saving()
|
||||
{
|
||||
ApiController Factory(HttpRequestMessage message, UmbracoHelper helper)
|
||||
{
|
||||
var contentServiceMock = Mock.Get(Current.Services.ContentService);
|
||||
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null);
|
||||
|
||||
var publishedSnapshot = Mock.Of<IPublishedSnapshotService>();
|
||||
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()));
|
||||
var usersController = new ContentController(publishedSnapshot, propertyEditorCollection);
|
||||
Container.InjectProperties(usersController);
|
||||
return usersController;
|
||||
}
|
||||
|
||||
var json = JsonConvert.DeserializeObject<JObject>(PublishJson1);
|
||||
//remove all save flaggs
|
||||
((JArray)json["variants"])[2]["save"] = false;
|
||||
|
||||
var runner = new TestRunner(Factory);
|
||||
var response = await runner.Execute("Content", "PostSave", HttpMethod.Post,
|
||||
content: GetMultiPartRequestContent(JsonConvert.SerializeObject(json)),
|
||||
mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"),
|
||||
assertOkResponse: false);
|
||||
|
||||
Assert.AreEqual(HttpStatusCode.NotFound, response.Item1.StatusCode);
|
||||
Assert.AreEqual(")]}',\n{\"Message\":\"No variants flagged for saving\"}", response.Item1.Content.ReadAsStringAsync().Result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns 404 if any of the posted properties dont actually exist
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[Test, Ignore("Not implemented yet")]
|
||||
public async Task PostSave_Validate_Properties_Exist()
|
||||
{
|
||||
//TODO: Make this work! to finish it, we need to include a property in the POST data that doesn't exist on the content type
|
||||
// or change the content type below to not include one of the posted ones
|
||||
|
||||
ApiController Factory(HttpRequestMessage message, UmbracoHelper helper)
|
||||
{
|
||||
var content = MockedContent.CreateSimpleContent(MockedContentTypes.CreateSimpleContentType());
|
||||
content.Id = 123;
|
||||
content.Path = "-1,123";
|
||||
|
||||
var contentServiceMock = Mock.Get(Current.Services.ContentService);
|
||||
contentServiceMock.Setup(x => x.GetById(123)).Returns(() => null);
|
||||
|
||||
var publishedSnapshot = Mock.Of<IPublishedSnapshotService>();
|
||||
var propertyEditorCollection = new PropertyEditorCollection(new DataEditorCollection(Enumerable.Empty<DataEditor>()));
|
||||
var usersController = new ContentController(publishedSnapshot, propertyEditorCollection);
|
||||
Container.InjectProperties(usersController);
|
||||
return usersController;
|
||||
}
|
||||
|
||||
var runner = new TestRunner(Factory);
|
||||
var response = await runner.Execute("Content", "PostSave", HttpMethod.Post,
|
||||
content: GetMultiPartRequestContent(PublishJson1),
|
||||
mediaTypeHeader: new MediaTypeWithQualityHeaderValue("multipart/form-data"),
|
||||
assertOkResponse: false);
|
||||
|
||||
Assert.AreEqual(HttpStatusCode.NotFound, response.Item1.StatusCode);
|
||||
|
||||
//var obj = JsonConvert.DeserializeObject<PagedResult<UserDisplay>>(response.Item2);
|
||||
//Assert.AreEqual(0, obj.TotalItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,37 +3,8 @@
|
||||
"browser": true
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
"angular"
|
||||
],
|
||||
|
||||
"rules": {
|
||||
"eqeqeq": 2,
|
||||
"curly": 2,
|
||||
|
||||
"no-unused-vars": 1,
|
||||
"no-eval": 1,
|
||||
"no-delete-var": 1,
|
||||
"quotes": 1,
|
||||
"dot-notation": 1,
|
||||
|
||||
"no-use-before-define": 0,
|
||||
"angular/ng_controller_as": 1,
|
||||
"angular/ng_controller_as_vm": 1,
|
||||
|
||||
|
||||
"strict": 0,
|
||||
"no-irregular-whitespace": 0,
|
||||
"no-mixed-spaces-and-tabs": 0,
|
||||
"no-multi-spaces": 0,
|
||||
"key-spacing": 0,
|
||||
"semi-spacing": 0,
|
||||
"space-infix-ops": 0,
|
||||
"comma-spacing": 0,
|
||||
"no-trailing-spaces": 0,
|
||||
"eol-last": 0,
|
||||
"no-underscore-dangle": 0,
|
||||
"camelcase": 0
|
||||
"comma-dangle": ["error", "never"]
|
||||
},
|
||||
|
||||
"globals": {
|
||||
|
||||
@@ -25,12 +25,14 @@
|
||||
"angular-i18n": "~1.7.2",
|
||||
"signalr": "^2.2.1",
|
||||
"typeahead.js": "~0.10.5",
|
||||
"underscore": "~1.7.0",
|
||||
"underscore": "~1.9.1",
|
||||
"rgrove-lazyload": "*",
|
||||
"bootstrap-social": "~4.8.0",
|
||||
"jquery": "2.2.4",
|
||||
"jquery-ui": "~1.12.0",
|
||||
"jquery-migrate": "1.4.0",
|
||||
"jquery-validate": "~1.17.0",
|
||||
"jquery-validation-unobtrusive": "3.2.10",
|
||||
"angular-dynamic-locale": "~0.1.36",
|
||||
"ng-file-upload": "~12.2.13",
|
||||
"tinymce": "~4.7.1",
|
||||
@@ -41,7 +43,7 @@
|
||||
"clipboard": "~2.0.0",
|
||||
"font-awesome": "~4.2",
|
||||
"animejs": "^2.2.0",
|
||||
"angular-ui-sortable": "0.14.3",
|
||||
"angular-ui-sortable": "0.14.4",
|
||||
"angular-messages": "^1.7.2"
|
||||
},
|
||||
"install": {
|
||||
@@ -53,39 +55,41 @@
|
||||
"ace-builds"
|
||||
],
|
||||
"sources": {
|
||||
"moment": [
|
||||
"bower_components/moment/min/moment.min.js",
|
||||
"bower_components/moment/min/moment-with-locales.js",
|
||||
"bower_components/moment/min/moment-with-locales.min.js",
|
||||
"bower_components/moment/locale/*.js"
|
||||
],
|
||||
"underscore": [
|
||||
"bower_components/underscore/underscore-min.js",
|
||||
"bower_components/underscore/underscore-min.map"
|
||||
],
|
||||
"jquery": [
|
||||
"bower_components/jquery/dist/jquery.min.js",
|
||||
"bower_components/jquery/dist/jquery.min.map"
|
||||
],
|
||||
"angular-dynamic-locale": [
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js",
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map"
|
||||
],
|
||||
"angular-local-storage": [
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js",
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js.map"
|
||||
],
|
||||
"tinymce": [
|
||||
"bower_components/tinymce/tinymce.min.js"
|
||||
],
|
||||
"angular-i18n": "bower_components/angular-i18n/angular-locale_*.js",
|
||||
"typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js",
|
||||
"rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js",
|
||||
"ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js",
|
||||
"jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js",
|
||||
"jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js",
|
||||
"clipboard": "bower_components/clipboard/dist/clipboard.min.js",
|
||||
"animejs": "bower_components/animejs/anime.min.js"
|
||||
"moment": [
|
||||
"bower_components/moment/min/moment.min.js",
|
||||
"bower_components/moment/min/moment-with-locales.js",
|
||||
"bower_components/moment/min/moment-with-locales.min.js",
|
||||
"bower_components/moment/locale/*.js"
|
||||
],
|
||||
"underscore": [
|
||||
"bower_components/underscore/underscore-min.js",
|
||||
"bower_components/underscore/underscore-min.map"
|
||||
],
|
||||
"jquery": [
|
||||
"bower_components/jquery/dist/jquery.min.js",
|
||||
"bower_components/jquery/dist/jquery.min.map"
|
||||
],
|
||||
"angular-dynamic-locale": [
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js",
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map"
|
||||
],
|
||||
"angular-local-storage": [
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js",
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js.map"
|
||||
],
|
||||
"tinymce": [
|
||||
"bower_components/tinymce/tinymce.min.js"
|
||||
],
|
||||
"angular-i18n": "bower_components/angular-i18n/angular-locale_*.js",
|
||||
"typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js",
|
||||
"rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js",
|
||||
"ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js",
|
||||
"jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js",
|
||||
"jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js",
|
||||
"clipboard": "bower_components/clipboard/dist/clipboard.min.js",
|
||||
"animejs": "bower_components/animejs/anime.min.js",
|
||||
"jquery-validate": "bower_components/jquery-validate/dist/jquery.validate.min.js",
|
||||
"jquery-validation-unobtrusive": "bower_components/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.min.js"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -12,6 +12,9 @@ const imagemin = require('gulp-imagemin');
|
||||
var _ = require('lodash');
|
||||
var MergeStream = require('merge-stream');
|
||||
|
||||
// js
|
||||
const eslint = require('gulp-eslint');
|
||||
|
||||
//Less + css
|
||||
var postcss = require('gulp-postcss');
|
||||
var less = require('gulp-less');
|
||||
@@ -30,6 +33,11 @@ Helper functions
|
||||
function processJs(files, out) {
|
||||
|
||||
return gulp.src(files)
|
||||
// check for js errors
|
||||
.pipe(eslint())
|
||||
// outputs the lint results to the console
|
||||
.pipe(eslint.format())
|
||||
// sort files in stream by path or any custom sort comparator
|
||||
.pipe(sort())
|
||||
.pipe(concat(out))
|
||||
.pipe(wrap('(function(){\n%= body %\n})();'))
|
||||
|
||||
@@ -329,10 +329,15 @@
|
||||
|
||||
/** Converts a string/integer/bool to true/false */
|
||||
Object.toBoolean = function (obj) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((typeof obj) === "boolean") {
|
||||
return obj;
|
||||
}
|
||||
if (obj === "1" || obj === 1 || obj === "true") {
|
||||
|
||||
if (obj === "1" || obj === 1 || obj.toString().toLowerCase() === "true") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
+1376
-553
File diff suppressed because it is too large
Load Diff
@@ -27,6 +27,7 @@
|
||||
"gulp": "^3.9.1",
|
||||
"gulp-concat": "^2.6.0",
|
||||
"gulp-connect": "5.0.0",
|
||||
"gulp-eslint": "^5.0.0",
|
||||
"gulp-imagemin": "^4.1.0",
|
||||
"gulp-less": "^3.5.0",
|
||||
"gulp-ngdocs": "^0.3.0",
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#Obsolete directives
|
||||
|
||||
Folder contains directives we plan to remove in the next major version of umbraco (8.0) these are not recommended to use.
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:autoScale
|
||||
* @element div
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
* @function
|
||||
* @description
|
||||
* Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set
|
||||
* So if you only want to scale the div to 70 pixels from the bottom you pass "70"
|
||||
|
||||
* @example
|
||||
* <example module="umbraco.directives">
|
||||
* <file name="index.html">
|
||||
* <div auto-scale="70" class="input-block-level"></div>
|
||||
* </file>
|
||||
* </example>
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.directive('autoScale', function ($window) {
|
||||
return function (scope, el, attrs) {
|
||||
|
||||
var totalOffset = 0;
|
||||
var offsety = parseInt(attrs.autoScale, 10);
|
||||
var window = angular.element($window);
|
||||
if (offsety !== undefined){
|
||||
totalOffset += offsety;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
el.height(window.height() - (el.offset().top + totalOffset));
|
||||
}, 500);
|
||||
|
||||
window.bind("resize", function () {
|
||||
el.height(window.height() - (el.offset().top + totalOffset));
|
||||
});
|
||||
|
||||
};
|
||||
});
|
||||
@@ -1,83 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:detectFold
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
* @description This is used for the editor buttons to ensure they are displayed correctly if the horizontal overflow of the editor
|
||||
* exceeds the height of the window
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives.html")
|
||||
.directive('detectFold', function ($timeout, $log, windowResizeListener) {
|
||||
return {
|
||||
require: "^?umbTabs",
|
||||
restrict: 'A',
|
||||
link: function (scope, el, attrs, tabsCtrl) {
|
||||
|
||||
var firstRun = false;
|
||||
var parent = $(".umb-panel-body");
|
||||
var winHeight = $(window).height();
|
||||
var calculate = function () {
|
||||
if (el && el.is(":visible") && !el.hasClass("umb-bottom-bar")) {
|
||||
|
||||
//now that the element is visible, set the flag in a couple of seconds,
|
||||
// this will ensure that loading time of a current tab get's completed and that
|
||||
// we eventually stop watching to save on CPU time
|
||||
$timeout(function() {
|
||||
firstRun = true;
|
||||
}, 4000);
|
||||
|
||||
//var parent = el.parent();
|
||||
var hasOverflow = parent.innerHeight() < parent[0].scrollHeight;
|
||||
//var belowFold = (el.offset().top + el.height()) > winHeight;
|
||||
if (hasOverflow) {
|
||||
el.addClass("umb-bottom-bar");
|
||||
|
||||
//I wish we didn't have to put this logic here but unfortunately we
|
||||
// do. This needs to calculate the left offest to place the bottom bar
|
||||
// depending on if the left column splitter has been moved by the user
|
||||
// (based on the nav-resize directive)
|
||||
var wrapper = $("#mainwrapper");
|
||||
var contentPanel = $("#leftcolumn").next();
|
||||
var contentPanelLeftPx = contentPanel.css("left");
|
||||
|
||||
el.css({ left: contentPanelLeftPx });
|
||||
}
|
||||
}
|
||||
return firstRun;
|
||||
};
|
||||
|
||||
var resizeCallback = function(size) {
|
||||
winHeight = size.height;
|
||||
el.removeClass("umb-bottom-bar");
|
||||
calculate();
|
||||
};
|
||||
|
||||
windowResizeListener.register(resizeCallback);
|
||||
|
||||
//Only execute the watcher if this tab is the active (first) tab on load, otherwise there's no reason to execute
|
||||
// the watcher since it will be recalculated when the tab changes!
|
||||
if (el.closest(".umb-tab-pane").index() === 0) {
|
||||
//run a watcher to ensure that the calculation occurs until it's firstRun but ensure
|
||||
// the calculations are throttled to save a bit of CPU
|
||||
var listener = scope.$watch(_.throttle(calculate, 1000), function (newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
listener();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//listen for tab changes
|
||||
if (tabsCtrl != null) {
|
||||
tabsCtrl.onTabShown(function (args) {
|
||||
calculate();
|
||||
});
|
||||
}
|
||||
|
||||
//ensure to unregister
|
||||
scope.$on('$destroy', function() {
|
||||
windowResizeListener.unregister(resizeCallback);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,69 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbItemSorter
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
* @function
|
||||
* @element ANY
|
||||
* @restrict E
|
||||
* @description A re-usable directive for sorting items
|
||||
**/
|
||||
|
||||
function umbItemSorter(angularHelper) {
|
||||
return {
|
||||
scope: {
|
||||
model: "="
|
||||
},
|
||||
restrict: "E", // restrict to an element
|
||||
replace: true, // replace the html element with the template
|
||||
templateUrl: 'views/directives/_obsolete/umb-item-sorter.html',
|
||||
link: function(scope, element, attrs, ctrl) {
|
||||
var defaultModel = {
|
||||
okButton: "Ok",
|
||||
successMsg: "Sorting successful",
|
||||
complete: false
|
||||
};
|
||||
//assign user vals to default
|
||||
angular.extend(defaultModel, scope.model);
|
||||
//re-assign merged to user
|
||||
scope.model = defaultModel;
|
||||
|
||||
scope.performSort = function() {
|
||||
scope.$emit("umbItemSorter.sorting", {
|
||||
sortedItems: scope.model.itemsToSort
|
||||
});
|
||||
};
|
||||
|
||||
scope.handleCancel = function () {
|
||||
scope.$emit("umbItemSorter.cancel");
|
||||
};
|
||||
|
||||
scope.handleOk = function() {
|
||||
scope.$emit("umbItemSorter.ok");
|
||||
};
|
||||
|
||||
//defines the options for the jquery sortable
|
||||
scope.sortableOptions = {
|
||||
axis: 'y',
|
||||
cursor: "move",
|
||||
placeholder: "ui-sortable-placeholder",
|
||||
update: function (ev, ui) {
|
||||
//highlight the item when the position is changed
|
||||
$(ui.item).effect("highlight", { color: "#049cdb" }, 500);
|
||||
},
|
||||
stop: function (ev, ui) {
|
||||
//the ui-sortable directive already ensures that our list is re-sorted, so now we just
|
||||
// need to update the sortOrder to the index of each item
|
||||
angularHelper.safeApply(scope, function () {
|
||||
angular.forEach(scope.itemsToSort, function (val, index) {
|
||||
val.sortOrder = index + 1;
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive("umbItemSorter", umbItemSorter);
|
||||
@@ -1,92 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbContentName
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
* @restrict E
|
||||
* @function
|
||||
* @description
|
||||
* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form.
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbContentName', function ($timeout, localizationService) {
|
||||
return {
|
||||
require: "ngModel",
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/directives/_obsolete/umb-content-name.html',
|
||||
|
||||
scope: {
|
||||
placeholder: '@placeholder',
|
||||
model: '=ngModel',
|
||||
ngDisabled: '='
|
||||
},
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
|
||||
var inputElement = element.find("input");
|
||||
if(scope.placeholder && scope.placeholder[0] === "@"){
|
||||
localizationService.localize(scope.placeholder.substring(1))
|
||||
.then(function(value){
|
||||
scope.placeholder = value;
|
||||
});
|
||||
}
|
||||
|
||||
var mX, mY, distance;
|
||||
|
||||
function calculateDistance(elem, mouseX, mouseY) {
|
||||
|
||||
var cx = Math.max(Math.min(mouseX, elem.offset().left + elem.width()), elem.offset().left);
|
||||
var cy = Math.max(Math.min(mouseY, elem.offset().top + elem.height()), elem.offset().top);
|
||||
return Math.sqrt((mouseX - cx) * (mouseX - cx) + (mouseY - cy) * (mouseY - cy));
|
||||
}
|
||||
|
||||
var mouseMoveDebounce = _.throttle(function (e) {
|
||||
mX = e.pageX;
|
||||
mY = e.pageY;
|
||||
// not focused and not over element
|
||||
if (!inputElement.is(":focus") && !inputElement.hasClass("ng-invalid")) {
|
||||
// on page
|
||||
if (mX >= inputElement.offset().left) {
|
||||
distance = calculateDistance(inputElement, mX, mY);
|
||||
if (distance <= 155) {
|
||||
|
||||
distance = 1 - (100 / 150 * distance / 100);
|
||||
inputElement.css("border", "1px solid rgba(175,175,175, " + distance + ")");
|
||||
inputElement.css("background-color", "rgba(255,255,255, " + distance + ")");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}, 15);
|
||||
|
||||
$(document).bind("mousemove", mouseMoveDebounce);
|
||||
|
||||
$timeout(function(){
|
||||
if(!scope.model){
|
||||
scope.goEdit();
|
||||
}
|
||||
}, 100, false);
|
||||
|
||||
scope.goEdit = function(){
|
||||
scope.editMode = true;
|
||||
|
||||
$timeout(function () {
|
||||
inputElement.focus();
|
||||
}, 100, false);
|
||||
};
|
||||
|
||||
scope.exitEdit = function(){
|
||||
if(scope.model && scope.model !== ""){
|
||||
scope.editMode = false;
|
||||
}
|
||||
};
|
||||
|
||||
//unbind doc event!
|
||||
scope.$on('$destroy', function () {
|
||||
$(document).unbind("mousemove", mouseMoveDebounce);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbHeader
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
* @restrict E
|
||||
* @function
|
||||
* @description
|
||||
* The header on an editor that contains tabs using bootstrap tabs - THIS IS OBSOLETE, use umbTabHeader instead
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbHeader', function ($parse, $timeout) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: 'true',
|
||||
templateUrl: 'views/directives/_obsolete/umb-header.html',
|
||||
//create a new isolated scope assigning a tabs property from the attribute 'tabs'
|
||||
//which is bound to the parent scope property passed in
|
||||
scope: {
|
||||
tabs: "="
|
||||
},
|
||||
link: function (scope, iElement, iAttrs) {
|
||||
|
||||
scope.showTabs = iAttrs.tabs ? true : false;
|
||||
scope.visibleTabs = [];
|
||||
|
||||
//since tabs are loaded async, we need to put a watch on them to determine
|
||||
// when they are loaded, then we can close the watch
|
||||
var tabWatch = scope.$watch("tabs", function (newValue, oldValue) {
|
||||
|
||||
angular.forEach(newValue, function(val, index){
|
||||
var tab = {id: val.id, label: val.label};
|
||||
scope.visibleTabs.push(tab);
|
||||
});
|
||||
|
||||
//don't process if we cannot or have already done so
|
||||
if (!newValue) {return;}
|
||||
if (!newValue.length || newValue.length === 0){return;}
|
||||
|
||||
//we need to do a timeout here so that the current sync operation can complete
|
||||
// and update the UI, then this will fire and the UI elements will be available.
|
||||
$timeout(function () {
|
||||
|
||||
//use bootstrap tabs API to show the first one
|
||||
iElement.find(".nav-tabs a:first").tab('show');
|
||||
|
||||
//enable the tab drop
|
||||
iElement.find('.nav-pills, .nav-tabs').tabdrop();
|
||||
|
||||
//ensure to destroy tabdrop (unbinds window resize listeners)
|
||||
scope.$on('$destroy', function () {
|
||||
iElement.find('.nav-pills, .nav-tabs').tabdrop("destroy");
|
||||
});
|
||||
|
||||
//stop watching now
|
||||
tabWatch();
|
||||
}, 200);
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbLogin
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
* @function
|
||||
* @element ANY
|
||||
* @restrict E
|
||||
**/
|
||||
|
||||
function loginDirective() {
|
||||
return {
|
||||
restrict: "E", // restrict to an element
|
||||
replace: true, // replace the html element with the template
|
||||
templateUrl: 'views/directives/_obsolete/umb-login.html'
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive("umbLogin", loginDirective);
|
||||
@@ -1,49 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbOptionsMenu
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
* @function
|
||||
* @element ANY
|
||||
* @restrict E
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbOptionsMenu', function ($injector, treeService, navigationService, umbModelMapper, appState) {
|
||||
return {
|
||||
scope: {
|
||||
currentSection: "@",
|
||||
currentNode: "="
|
||||
},
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/directives/_obsolete/umb-optionsmenu.html',
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
|
||||
//adds a handler to the context menu item click, we need to handle this differently
|
||||
//depending on what the menu item is supposed to do.
|
||||
scope.executeMenuItem = function (action) {
|
||||
navigationService.executeMenuAction(action, scope.currentNode, scope.currentSection);
|
||||
};
|
||||
|
||||
//callback method to go and get the options async
|
||||
scope.getOptions = function () {
|
||||
|
||||
if (!scope.currentNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
//when the options item is selected, we need to set the current menu item in appState (since this is synonymous with a menu)
|
||||
appState.setMenuState("currentNode", scope.currentNode);
|
||||
|
||||
if (!scope.actions) {
|
||||
treeService.getMenu({ treeNode: scope.currentNode })
|
||||
.then(function (data) {
|
||||
scope.actions = data.menuItems;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbPhotoFolder
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
* @restrict E
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives.html")
|
||||
.directive('umbPhotoFolder', function($compile, $log, $timeout, $filter, umbPhotoFolderHelper) {
|
||||
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
require: '?ngModel',
|
||||
terminate: true,
|
||||
templateUrl: 'views/directives/_obsolete/umb-photo-folder.html',
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
|
||||
var lastWatch = null;
|
||||
|
||||
ngModel.$render = function() {
|
||||
if (ngModel.$modelValue) {
|
||||
|
||||
$timeout(function() {
|
||||
var photos = ngModel.$modelValue;
|
||||
|
||||
scope.clickHandler = scope.$eval(element.attr('on-click'));
|
||||
|
||||
|
||||
var imagesOnly = element.attr('images-only') === "true";
|
||||
|
||||
|
||||
var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5;
|
||||
var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0;
|
||||
var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420;
|
||||
var minHeight = element.attr('min-height') ? parseInt(element.attr('min-height'), 10) : 100;
|
||||
var maxHeight = element.attr('max-height') ? parseInt(element.attr('max-height'), 10) : 300;
|
||||
var idealImgPerRow = element.attr('ideal-items-per-row') ? parseInt(element.attr('ideal-items-per-row'), 10) : 5;
|
||||
var fixedRowWidth = Math.max(element.width(), minWidth);
|
||||
|
||||
scope.containerStyle = { width: fixedRowWidth + "px" };
|
||||
scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly);
|
||||
|
||||
if (attrs.filterBy) {
|
||||
|
||||
//we track the watches that we create, we don't want to create multiple, so clear it
|
||||
// if it already exists before creating another.
|
||||
if (lastWatch) {
|
||||
lastWatch();
|
||||
}
|
||||
|
||||
//TODO: Need to debounce this so it doesn't filter too often!
|
||||
lastWatch = scope.$watch(attrs.filterBy, function (newVal, oldVal) {
|
||||
if (newVal && newVal !== oldVal) {
|
||||
var p = $filter('filter')(photos, newVal, false);
|
||||
scope.baseline = 0;
|
||||
var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly);
|
||||
scope.rows = m;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}, 500); //end timeout
|
||||
} //end if modelValue
|
||||
|
||||
}; //end $render
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1,172 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbSort
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
*
|
||||
* @element div
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set
|
||||
* So if you only want to scale the div to 70 pixels from the bottom you pass "70"
|
||||
*
|
||||
* @example
|
||||
* <example module="umbraco.directives">
|
||||
* <file name="index.html">
|
||||
* <div umb-sort="70" class="input-block-level"></div>
|
||||
* </file>
|
||||
* </example>
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.value('umbSortContextInternal',{})
|
||||
.directive('umbSort', function($log,umbSortContextInternal) {
|
||||
return {
|
||||
require: '?ngModel',
|
||||
link: function(scope, element, attrs, ngModel) {
|
||||
var adjustment;
|
||||
|
||||
var cfg = scope.$eval(element.attr('umb-sort')) || {};
|
||||
|
||||
scope.model = ngModel;
|
||||
|
||||
scope.opts = cfg;
|
||||
scope.opts.containerSelector= cfg.containerSelector || ".umb-" + cfg.group + "-container",
|
||||
scope.opts.nested= cfg.nested || true,
|
||||
scope.opts.drop= cfg.drop || true,
|
||||
scope.opts.drag= cfg.drag || true,
|
||||
scope.opts.clone = cfg.clone || "<li/>";
|
||||
scope.opts.mode = cfg.mode || "list";
|
||||
|
||||
scope.opts.itemSelectorFull = $.trim(scope.opts.itemPath + " " + scope.opts.itemSelector);
|
||||
|
||||
/*
|
||||
scope.opts.isValidTarget = function(item, container) {
|
||||
if(container.el.is(".umb-" + scope.opts.group + "-container")){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
*/
|
||||
|
||||
element.addClass("umb-sort");
|
||||
element.addClass("umb-" + cfg.group + "-container");
|
||||
|
||||
scope.opts.onDrag = function (item, position) {
|
||||
if(scope.opts.mode === "list"){
|
||||
item.css({
|
||||
left: position.left - adjustment.left,
|
||||
top: position.top - adjustment.top
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
scope.opts.onDrop = function (item, targetContainer, _super) {
|
||||
|
||||
if(scope.opts.mode === "list"){
|
||||
//list mode
|
||||
var clonedItem = $(scope.opts.clone).css({height: 0});
|
||||
item.after(clonedItem);
|
||||
clonedItem.animate({'height': item.height()});
|
||||
|
||||
item.animate(clonedItem.position(), function () {
|
||||
clonedItem.detach();
|
||||
_super(item);
|
||||
});
|
||||
}
|
||||
|
||||
var children = $(scope.opts.itemSelectorFull, targetContainer.el);
|
||||
var targetIndex = children.index(item);
|
||||
var targetScope = $(targetContainer.el[0]).scope();
|
||||
|
||||
|
||||
if(targetScope === umbSortContextInternal.sourceScope){
|
||||
if(umbSortContextInternal.sourceScope.opts.onSortHandler){
|
||||
var _largs = {
|
||||
oldIndex: umbSortContextInternal.sourceIndex,
|
||||
newIndex: targetIndex,
|
||||
scope: umbSortContextInternal.sourceScope
|
||||
};
|
||||
|
||||
umbSortContextInternal.sourceScope.opts.onSortHandler.call(this, item, _largs);
|
||||
}
|
||||
}else{
|
||||
|
||||
|
||||
if(targetScope.opts.onDropHandler){
|
||||
var args = {
|
||||
sourceScope: umbSortContextInternal.sourceScope,
|
||||
sourceIndex: umbSortContextInternal.sourceIndex,
|
||||
sourceContainer: umbSortContextInternal.sourceContainer,
|
||||
|
||||
targetScope: targetScope,
|
||||
targetIndex: targetIndex,
|
||||
targetContainer: targetContainer
|
||||
};
|
||||
|
||||
targetScope.opts.onDropHandler.call(this, item, args);
|
||||
}
|
||||
|
||||
if(umbSortContextInternal.sourceScope.opts.onReleaseHandler){
|
||||
var _args = {
|
||||
sourceScope: umbSortContextInternal.sourceScope,
|
||||
sourceIndex: umbSortContextInternal.sourceIndex,
|
||||
sourceContainer: umbSortContextInternal.sourceContainer,
|
||||
|
||||
targetScope: targetScope,
|
||||
targetIndex: targetIndex,
|
||||
targetContainer: targetContainer
|
||||
};
|
||||
|
||||
umbSortContextInternal.sourceScope.opts.onReleaseHandler.call(this, item, _args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scope.changeIndex = function(from, to){
|
||||
scope.$apply(function(){
|
||||
var i = ngModel.$modelValue.splice(from, 1)[0];
|
||||
ngModel.$modelValue.splice(to, 0, i);
|
||||
});
|
||||
};
|
||||
|
||||
scope.move = function(args){
|
||||
var from = args.sourceIndex;
|
||||
var to = args.targetIndex;
|
||||
|
||||
if(args.sourceContainer === args.targetContainer){
|
||||
scope.changeIndex(from, to);
|
||||
}else{
|
||||
scope.$apply(function(){
|
||||
var i = args.sourceScope.model.$modelValue.splice(from, 1)[0];
|
||||
args.targetScope.model.$modelvalue.splice(to,0, i);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
scope.opts.onDragStart = function (item, container, _super) {
|
||||
var children = $(scope.opts.itemSelectorFull, container.el);
|
||||
var offset = item.offset();
|
||||
|
||||
umbSortContextInternal.sourceIndex = children.index(item);
|
||||
umbSortContextInternal.sourceScope = $(container.el[0]).scope();
|
||||
umbSortContextInternal.sourceContainer = container;
|
||||
|
||||
//current.item = ngModel.$modelValue.splice(current.index, 1)[0];
|
||||
|
||||
var pointer = container.rootGroup.pointer;
|
||||
adjustment = {
|
||||
left: pointer.left - offset.left,
|
||||
top: pointer.top - offset.top
|
||||
};
|
||||
|
||||
_super(item, container);
|
||||
};
|
||||
|
||||
element.sortable( scope.opts );
|
||||
}
|
||||
};
|
||||
|
||||
});
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbTabView
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
*
|
||||
* @restrict E
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbTabView', function($timeout, $log){
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: 'true',
|
||||
templateUrl: 'views/directives/_obsolete/umb-tab-view.html'
|
||||
};
|
||||
});
|
||||
-17
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbUploadDropzone
|
||||
* @deprecated
|
||||
* We plan to remove this directive in the next major version of umbraco (8.0). The directive is not recommended to use.
|
||||
*
|
||||
* @restrict E
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives.html")
|
||||
.directive('umbUploadDropzone', function(){
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/directives/_obsolete/umb-upload-dropzone.html'
|
||||
};
|
||||
});
|
||||
+1
-1
@@ -103,7 +103,7 @@
|
||||
backdropOpacity: "=?",
|
||||
highlightElement: "=?",
|
||||
highlightPreventClick: "=?",
|
||||
disableEventsOnClick: "=?",
|
||||
disableEventsOnClick: "=?"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+79
-79
@@ -10,62 +10,62 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
|
||||
templateUrl: 'views/components/application/umb-sections.html',
|
||||
link: function (scope, element, attr, ctrl) {
|
||||
|
||||
var sectionItemsWidth = [];
|
||||
var evts = [];
|
||||
var maxSections = 7;
|
||||
var sectionItemsWidth = [];
|
||||
var evts = [];
|
||||
var maxSections = 7;
|
||||
|
||||
//setup scope vars
|
||||
scope.maxSections = maxSections;
|
||||
scope.overflowingSections = 0;
|
||||
scope.maxSections = maxSections;
|
||||
scope.overflowingSections = 0;
|
||||
scope.sections = [];
|
||||
scope.currentSection = appState.getSectionState("currentSection");
|
||||
scope.showTray = false; //appState.getGlobalState("showTray");
|
||||
scope.stickyNavigation = appState.getGlobalState("stickyNavigation");
|
||||
scope.needTray = false;
|
||||
|
||||
function loadSections(){
|
||||
sectionService.getSectionsForUser()
|
||||
.then(function (result) {
|
||||
scope.sections = result;
|
||||
// store the width of each section so we can hide/show them based on browser width
|
||||
// we store them because the sections get removed from the dom and then we
|
||||
// can't tell when to show them gain
|
||||
$timeout(function(){
|
||||
$("#applications .sections li").each(function(index) {
|
||||
sectionItemsWidth.push($(this).outerWidth());
|
||||
});
|
||||
});
|
||||
calculateWidth();
|
||||
});
|
||||
}
|
||||
|
||||
function calculateWidth(){
|
||||
$timeout(function(){
|
||||
//total width minus room for avatar, search, and help icon
|
||||
var windowWidth = $(window).width()-200;
|
||||
var sectionsWidth = 0;
|
||||
scope.totalSections = scope.sections.length;
|
||||
scope.maxSections = maxSections;
|
||||
scope.overflowingSections = 0;
|
||||
scope.needTray = false;
|
||||
|
||||
// detect how many sections we can show on the screen
|
||||
for (var i = 0; i < sectionItemsWidth.length; i++) {
|
||||
var sectionItemWidth = sectionItemsWidth[i];
|
||||
sectionsWidth += sectionItemWidth;
|
||||
function loadSections() {
|
||||
sectionService.getSectionsForUser()
|
||||
.then(function (result) {
|
||||
scope.sections = result;
|
||||
// store the width of each section so we can hide/show them based on browser width
|
||||
// we store them because the sections get removed from the dom and then we
|
||||
// can't tell when to show them gain
|
||||
$timeout(function () {
|
||||
$("#applications .sections li").each(function (index) {
|
||||
sectionItemsWidth.push($(this).outerWidth());
|
||||
});
|
||||
});
|
||||
calculateWidth();
|
||||
});
|
||||
}
|
||||
|
||||
if(sectionsWidth > windowWidth) {
|
||||
scope.needTray = true;
|
||||
scope.maxSections = i - 1;
|
||||
scope.overflowingSections = scope.maxSections - scope.totalSections;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function calculateWidth() {
|
||||
$timeout(function () {
|
||||
//total width minus room for avatar, search, and help icon
|
||||
var windowWidth = $(window).width() - 200;
|
||||
var sectionsWidth = 0;
|
||||
scope.totalSections = scope.sections.length;
|
||||
scope.maxSections = maxSections;
|
||||
scope.overflowingSections = 0;
|
||||
scope.needTray = false;
|
||||
|
||||
// detect how many sections we can show on the screen
|
||||
for (var i = 0; i < sectionItemsWidth.length; i++) {
|
||||
var sectionItemWidth = sectionItemsWidth[i];
|
||||
sectionsWidth += sectionItemWidth;
|
||||
|
||||
if (sectionsWidth > windowWidth) {
|
||||
scope.needTray = true;
|
||||
scope.maxSections = i - 1;
|
||||
scope.overflowingSections = scope.maxSections - scope.totalSections;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//Listen for global state changes
|
||||
evts.push(eventsService.on("appState.globalState.changed", function(e, args) {
|
||||
evts.push(eventsService.on("appState.globalState.changed", function (e, args) {
|
||||
if (args.key === "showTray") {
|
||||
scope.showTray = args.value;
|
||||
}
|
||||
@@ -74,44 +74,44 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
|
||||
}
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("appState.sectionState.changed", function(e, args) {
|
||||
evts.push(eventsService.on("appState.sectionState.changed", function (e, args) {
|
||||
if (args.key === "currentSection") {
|
||||
scope.currentSection = args.value;
|
||||
}
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("app.reInitialize", function(e, args) {
|
||||
evts.push(eventsService.on("app.reInitialize", function (e, args) {
|
||||
//re-load the sections if we're re-initializing (i.e. package installed)
|
||||
loadSections();
|
||||
}));
|
||||
|
||||
//ensure to unregister from all events!
|
||||
scope.$on('$destroy', function () {
|
||||
for (var e in evts) {
|
||||
eventsService.unsubscribe(evts[e]);
|
||||
}
|
||||
});
|
||||
scope.$on('$destroy', function () {
|
||||
for (var e in evts) {
|
||||
eventsService.unsubscribe(evts[e]);
|
||||
}
|
||||
});
|
||||
|
||||
//on page resize
|
||||
window.onresize = calculateWidth;
|
||||
//on page resize
|
||||
window.onresize = calculateWidth;
|
||||
|
||||
scope.sectionClick = function (event, section) {
|
||||
scope.sectionClick = function (event, section) {
|
||||
|
||||
if (event.ctrlKey ||
|
||||
event.shiftKey ||
|
||||
event.metaKey || // apple
|
||||
(event.button && event.button === 1) // middle click, >IE9 + everyone else
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (event.ctrlKey ||
|
||||
event.shiftKey ||
|
||||
event.metaKey || // apple
|
||||
(event.button && event.button === 1) // middle click, >IE9 + everyone else
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (scope.userDialog) {
|
||||
closeUserDialog();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
navigationService.hideSearch();
|
||||
navigationService.showTree(section.alias);
|
||||
|
||||
navigationService.hideSearch();
|
||||
navigationService.showTree(section.alias);
|
||||
|
||||
//in some cases the section will have a custom route path specified, if there is one we'll use it
|
||||
if (section.routePath) {
|
||||
@@ -123,22 +123,22 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
|
||||
$location.path(path);
|
||||
}
|
||||
navigationService.clearSearch();
|
||||
|
||||
};
|
||||
|
||||
scope.sectionDblClick = function(section){
|
||||
navigationService.reloadSection(section.alias);
|
||||
};
|
||||
};
|
||||
|
||||
scope.trayClick = function () {
|
||||
if (appState.getGlobalState("showTray") === true) {
|
||||
navigationService.hideTray();
|
||||
} else {
|
||||
navigationService.showTray();
|
||||
}
|
||||
};
|
||||
scope.sectionDblClick = function (section) {
|
||||
navigationService.reloadSection(section.alias);
|
||||
};
|
||||
|
||||
loadSections();
|
||||
scope.trayClick = function () {
|
||||
if (appState.getGlobalState("showTray") === true) {
|
||||
navigationService.hideTray();
|
||||
} else {
|
||||
navigationService.showTray();
|
||||
}
|
||||
};
|
||||
|
||||
loadSections();
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
+1
@@ -94,6 +94,7 @@ Use this directive to render an umbraco button. The directive can be used to gen
|
||||
}
|
||||
});
|
||||
|
||||
//TODO: This doesn't seem necessary?
|
||||
UmbButtonController.$inject = ['$timeout'];
|
||||
|
||||
function UmbButtonController($timeout) {
|
||||
|
||||
+269
-182
@@ -21,21 +21,18 @@
|
||||
$scope.page.listViewPath = null;
|
||||
$scope.page.isNew = $scope.isNew ? true : false;
|
||||
$scope.page.buttonGroupState = "init";
|
||||
$scope.page.culture = $scope.culture;
|
||||
$scope.page.hideActionsMenu = infiniteMode ? true : false;
|
||||
$scope.page.hideChangeVariant = infiniteMode ? true : false;
|
||||
$scope.allowOpen = true;
|
||||
|
||||
// add all editors to an editors array to support split view
|
||||
$scope.editors = [];
|
||||
$scope.splitView = {
|
||||
"leftIsOpen": true,
|
||||
"rightIsOpen": false
|
||||
};
|
||||
$scope.initVariant = initVariant;
|
||||
$scope.splitViewChanged = splitViewChanged;
|
||||
|
||||
function init(content) {
|
||||
|
||||
if(infiniteMode) {
|
||||
|
||||
if (infiniteMode) {
|
||||
createInfiniteModeButtons(content);
|
||||
} else {
|
||||
createButtons(content);
|
||||
@@ -53,7 +50,166 @@
|
||||
}
|
||||
}
|
||||
|
||||
//init can be called more than once and we don't want to have multiple bound events
|
||||
bindEvents();
|
||||
|
||||
// set first app to active
|
||||
// We need to track active
|
||||
$scope.content.apps[0].active = true;
|
||||
|
||||
setActiveCulture();
|
||||
|
||||
resetVariantFlags();
|
||||
}
|
||||
|
||||
/** This is called when the split view changes based on the umb-variant-content */
|
||||
function splitViewChanged() {
|
||||
//send an event downwards
|
||||
$scope.$broadcast("editors.content.splitViewChanged", { editors: $scope.editors });
|
||||
}
|
||||
|
||||
/**
|
||||
* This will reset isDirty flags if save is true.
|
||||
* When working with multiple variants, this will set the save/publish flags of each one to false.
|
||||
* When working with a single variant, this will set the publish flag to false and the save flag to true.
|
||||
*/
|
||||
function resetVariantFlags() {
|
||||
if ($scope.content.variants.length > 1) {
|
||||
for (var i = 0; i < $scope.content.variants.length; i++) {
|
||||
var v = $scope.content.variants[i];
|
||||
if (v.save) {
|
||||
v.isDirty = false;
|
||||
}
|
||||
v.save = false;
|
||||
v.publish = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if ($scope.content.variants[0].save) {
|
||||
$scope.content.variants[0].isDirty = false;
|
||||
}
|
||||
$scope.content.variants[0].save = true;
|
||||
$scope.content.variants[0].publish = false;
|
||||
}
|
||||
}
|
||||
|
||||
function countDirtyVariants() {
|
||||
var count = 0;
|
||||
for (var i = 0; i < $scope.content.variants.length; i++) {
|
||||
var v = $scope.content.variants[i];
|
||||
if (v.isDirty) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/** Returns true if the save/publish dialog should be shown when pressing the button */
|
||||
function showSaveOrPublishDialog() {
|
||||
return $scope.content.variants.length > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* The content item(s) are loaded into an array and this will set the active content item based on the current culture (query string).
|
||||
* If the content item is invariant, then only one item exists in the array.
|
||||
*/
|
||||
function setActiveCulture() {
|
||||
// set the active variant
|
||||
var activeVariant = null;
|
||||
_.each($scope.content.variants, function (v) {
|
||||
if (v.language && v.language.culture === $scope.culture) {
|
||||
v.active = true;
|
||||
activeVariant = v;
|
||||
}
|
||||
else {
|
||||
v.active = false;
|
||||
}
|
||||
});
|
||||
if (!activeVariant) {
|
||||
// set the first variant to active
|
||||
$scope.content.variants[0].active = true;
|
||||
activeVariant = $scope.content.variants[0];
|
||||
}
|
||||
|
||||
initVariant(activeVariant);
|
||||
|
||||
//If there are no editors yet then create one with the current content.
|
||||
//if there's already a main editor then update it with the current content.
|
||||
if ($scope.editors.length === 0) {
|
||||
var editor = {
|
||||
content: activeVariant
|
||||
};
|
||||
$scope.editors.push(editor);
|
||||
}
|
||||
else {
|
||||
//this will mean there is only one
|
||||
$scope.editors[0].content = activeVariant;
|
||||
|
||||
if ($scope.editors.length > 1) {
|
||||
//now re-sync any other editor content (i.e. if split view is open)
|
||||
for (var s = 1; s < $scope.editors.length; s++) {
|
||||
//get the variant from the scope model
|
||||
var variant = _.find($scope.content.variants, function (v) {
|
||||
return v.language.culture === $scope.editors[s].content.language.culture;
|
||||
});
|
||||
$scope.editors[s].content = initVariant(variant);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function initVariant(variant) {
|
||||
//The model that is assigned to the editor contains the current content variant along
|
||||
//with a copy of the contentApps. This is required because each editor renders it's own
|
||||
//header and content apps section and the content apps contains the view for editing content itself
|
||||
//and we need to assign a view model to the subView so that it is scoped to the current
|
||||
//editor so that split views work.
|
||||
|
||||
//copy the apps from the main model if not assigned yet to the variant
|
||||
if (!variant.apps) {
|
||||
variant.apps = angular.copy($scope.content.apps);
|
||||
}
|
||||
|
||||
//if this is a variant has a culture/language than we need to assign the language drop down info
|
||||
if (variant.language) {
|
||||
//if the variant list that defines the header drop down isn't assigned to the variant then assign it now
|
||||
if (!variant.variants) {
|
||||
variant.variants = _.map($scope.content.variants,
|
||||
function (v) {
|
||||
return _.pick(v, "active", "language", "state");
|
||||
});
|
||||
}
|
||||
else {
|
||||
//merge the scope variants on top of the header variants collection (handy when needing to refresh)
|
||||
angular.extend(variant.variants,
|
||||
_.map($scope.content.variants,
|
||||
function (v) {
|
||||
return _.pick(v, "active", "language", "state");
|
||||
}));
|
||||
}
|
||||
|
||||
//ensure the current culture is set as the active one
|
||||
for (var i = 0; i < variant.variants.length; i++) {
|
||||
if (variant.variants[i].language.culture === variant.language.culture) {
|
||||
variant.variants[i].active = true;
|
||||
}
|
||||
else {
|
||||
variant.variants[i].active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//then assign the variant to a view model to the content app
|
||||
var contentApp = _.find(variant.apps, function (a) {
|
||||
return a.alias === "content";
|
||||
});
|
||||
contentApp.viewModel = variant;
|
||||
|
||||
return variant;
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
//bindEvents can be called more than once and we don't want to have multiple bound events
|
||||
for (var e in evts) {
|
||||
eventsService.unsubscribe(evts[e]);
|
||||
}
|
||||
@@ -68,84 +224,19 @@
|
||||
|
||||
evts.push(eventsService.on("editors.documentType.saved", function (name, args) {
|
||||
// if this content item uses the updated doc type we need to reload the content item
|
||||
if(args && args.documentType && args.documentType.key === content.documentType.key) {
|
||||
if ($scope.page.culture) {
|
||||
loadContent($scope.page.culture);
|
||||
}
|
||||
else {
|
||||
loadContent();
|
||||
}
|
||||
if (args && args.documentType && args.documentType.key === content.documentType.key) {
|
||||
loadContent();
|
||||
}
|
||||
}));
|
||||
|
||||
// We don't get the info tab from the server from version 7.8 so we need to manually add it
|
||||
//contentEditingHelper.addInfoTab($scope.content.tabs);
|
||||
|
||||
// prototype content and info apps
|
||||
var contentApp = {
|
||||
"name": "Content",
|
||||
"alias": "content",
|
||||
"icon": "icon-document",
|
||||
"view": "views/content/apps/content/content.html"
|
||||
};
|
||||
|
||||
var infoApp = {
|
||||
"name": "Info",
|
||||
"alias": "info",
|
||||
"icon": "icon-info",
|
||||
"view": "views/content/apps/info/info.html"
|
||||
};
|
||||
|
||||
var listview = {
|
||||
"name": "Child items",
|
||||
"alias": "childItems",
|
||||
"icon": "icon-list",
|
||||
"view": "views/content/apps/listview/listview.html"
|
||||
};
|
||||
|
||||
$scope.content.apps = [];
|
||||
|
||||
if ($scope.content.isContainer) {
|
||||
// add list view app
|
||||
$scope.content.apps.push(listview);
|
||||
|
||||
// remove the list view tab
|
||||
angular.forEach($scope.content.tabs, function (tab, index) {
|
||||
if (tab.alias === "umbContainerView") {
|
||||
tab.hide = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$scope.content.apps.push(contentApp);
|
||||
$scope.content.apps.push(infoApp);
|
||||
|
||||
// set first app to active
|
||||
$scope.content.apps[0].active = true;
|
||||
|
||||
// create new editor for split view
|
||||
if ($scope.editors.length === 0) {
|
||||
var editor = {
|
||||
content: $scope.content
|
||||
};
|
||||
$scope.editors.push(editor);
|
||||
}
|
||||
else if ($scope.editors.length === 1) {
|
||||
$scope.editors[0].content = $scope.content;
|
||||
}
|
||||
else {
|
||||
//fixme - need to fix something here if we are re-loading a content item that is in a split view
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This does the content loading and initializes everything, called on load and changing variants
|
||||
* @param {any} culture
|
||||
* This does the content loading and initializes everything, called on first load
|
||||
*/
|
||||
function loadContent(culture) {
|
||||
function loadContent() {
|
||||
|
||||
//we are editing so get the content item from the server
|
||||
return $scope.getMethod()($scope.contentId, culture)
|
||||
return $scope.getMethod()($scope.contentId)
|
||||
.then(function (data) {
|
||||
|
||||
$scope.content = data;
|
||||
@@ -158,14 +249,8 @@
|
||||
|
||||
init($scope.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
|
||||
// after the redirect, so we will bind all subscriptions which will show the server validation errors
|
||||
// if there are any and then clear them so the collection no longer persists them.
|
||||
serverValidationManager.executeAndClearAllSubscriptions();
|
||||
|
||||
if(!infiniteMode) {
|
||||
syncTreeNode($scope.content, data.path, true);
|
||||
if (!infiniteMode) {
|
||||
syncTreeNode($scope.content, true);
|
||||
}
|
||||
|
||||
resetLastListPageNumber($scope.content);
|
||||
@@ -204,18 +289,20 @@
|
||||
$scope.page.allowInfiniteSaveAndClose = false;
|
||||
|
||||
// check for publish rights
|
||||
if(_.contains(content.allowedActions, "U")) {
|
||||
if (_.contains(content.allowedActions, "U")) {
|
||||
$scope.page.allowInfinitePublishAndClose = true;
|
||||
|
||||
// check for save rights
|
||||
} else if( _.contains(content.allowedActions, "A")) {
|
||||
// check for save rights
|
||||
} else if (_.contains(content.allowedActions, "A")) {
|
||||
$scope.page.allowInfiniteSaveAndClose = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** 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, initialLoad) {
|
||||
|
||||
var path = content.path;
|
||||
|
||||
if (!$scope.content.isChildOfListView) {
|
||||
navigationService.syncTree({ tree: $scope.treeAlias, path: path.split(","), forceReload: initialLoad !== true }).then(function (syncArgs) {
|
||||
@@ -248,13 +335,14 @@
|
||||
saveMethod: args.saveMethod,
|
||||
scope: $scope,
|
||||
content: $scope.content,
|
||||
action: args.action
|
||||
action: args.action,
|
||||
showNotifications: args.showNotifications
|
||||
}).then(function (data) {
|
||||
//success
|
||||
init($scope.content);
|
||||
|
||||
if(!infiniteMode) {
|
||||
syncTreeNode($scope.content, data.path);
|
||||
if (!infiniteMode) {
|
||||
syncTreeNode($scope.content);
|
||||
}
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
@@ -264,12 +352,17 @@
|
||||
return $q.when(data);
|
||||
},
|
||||
function (err) {
|
||||
|
||||
setActiveCulture();
|
||||
syncTreeNode($scope.content);
|
||||
|
||||
//error
|
||||
if (err) {
|
||||
editorState.set($scope.content);
|
||||
}
|
||||
|
||||
$scope.page.buttonGroupState = "error";
|
||||
|
||||
return $q.reject(err);
|
||||
});
|
||||
}
|
||||
@@ -305,18 +398,11 @@
|
||||
}
|
||||
else {
|
||||
|
||||
//Browse content nodes based on the selected tree language variant
|
||||
$scope.page.loading = true;
|
||||
if ($scope.page.culture) {
|
||||
loadContent($scope.page.culture).then(function(){
|
||||
$scope.page.loading = false;
|
||||
});
|
||||
}
|
||||
else {
|
||||
loadContent().then(function(){
|
||||
$scope.page.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
loadContent().then(function () {
|
||||
$scope.page.loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
$scope.unPublish = function () {
|
||||
@@ -326,15 +412,15 @@
|
||||
if ($scope.content.variants.length > 0) {
|
||||
_.each($scope.content.variants,
|
||||
function (d) {
|
||||
//set the culture if this is current
|
||||
if (d.current === true) {
|
||||
//set the culture if this is active
|
||||
if (d.active === true) {
|
||||
culture = d.language.culture;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (formHelper.submitForm({ scope: $scope, skipValidation: true })) {
|
||||
|
||||
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
|
||||
eventsService.emit("content.unpublishing", { content: $scope.content });
|
||||
@@ -352,8 +438,8 @@
|
||||
|
||||
init($scope.content);
|
||||
|
||||
if(!infiniteMode) {
|
||||
syncTreeNode($scope.content, data.path);
|
||||
if (!infiniteMode) {
|
||||
syncTreeNode($scope.content);
|
||||
}
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
@@ -374,12 +460,12 @@
|
||||
$scope.saveAndPublish = function () {
|
||||
|
||||
// TODO: Add "..." to publish button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
|
||||
if ($scope.content.variants.length > 1) {
|
||||
if (showSaveOrPublishDialog()) {
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "publish"})) {
|
||||
if (formHelper.submitForm({ scope: $scope, action: "publish" })) {
|
||||
|
||||
var dialog = {
|
||||
view: "publish",
|
||||
view: "views/content/overlays/publish.html",
|
||||
variants: $scope.content.variants, //set a model property for the dialog
|
||||
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
|
||||
submitButtonLabel: "Publish",
|
||||
@@ -389,7 +475,8 @@
|
||||
//we need to return this promise so that the dialog can handle the result and wire up the validation response
|
||||
return performSave({
|
||||
saveMethod: contentResource.publish,
|
||||
action: "publish"
|
||||
action: "publish",
|
||||
showNotifications: false
|
||||
}).then(function (data) {
|
||||
overlayService.close();
|
||||
return $q.when(data);
|
||||
@@ -399,21 +486,6 @@
|
||||
//re-map the dialog model since we've re-bound the properties
|
||||
dialog.variants = $scope.content.variants;
|
||||
|
||||
//check the error list for specific variant errors, if none exist that means that only server side validation
|
||||
//for the current variant's properties failed, in this case we want to close the publish dialog since the user
|
||||
//will need to fix validation errors on the properties
|
||||
if (err.data && err.data.ModelState) {
|
||||
var keys = _.keys(err.data.ModelState);
|
||||
var foundVariantError = _.find(keys,
|
||||
function (k) {
|
||||
return k.startsWith("publish_variant_");
|
||||
});
|
||||
if (!foundVariantError) {
|
||||
//no variant errors, close the dialog
|
||||
overlayService.close();
|
||||
}
|
||||
}
|
||||
|
||||
return $q.reject(err);
|
||||
});
|
||||
},
|
||||
@@ -426,12 +498,56 @@
|
||||
}
|
||||
}
|
||||
else {
|
||||
return performSave({ saveMethod: contentResource.publish, action: "publish" }).catch(angular.noop);;
|
||||
//ensure the publish flag is set
|
||||
$scope.content.variants[0].publish = true;
|
||||
return performSave({ saveMethod: contentResource.publish, action: "publish" });
|
||||
}
|
||||
};
|
||||
|
||||
$scope.save = function () {
|
||||
return performSave({ saveMethod: $scope.saveMethod(), action: "save" }).catch(angular.noop);
|
||||
|
||||
// TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
|
||||
if (showSaveOrPublishDialog()) {
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "save" })) {
|
||||
|
||||
var dialog = {
|
||||
view: "views/content/overlays/save.html",
|
||||
variants: $scope.content.variants, //set a model property for the dialog
|
||||
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
|
||||
submitButtonLabel: "Save",
|
||||
submit: function (model) {
|
||||
model.submitButtonState = "busy";
|
||||
|
||||
//we need to return this promise so that the dialog can handle the result and wire up the validation response
|
||||
return performSave({
|
||||
saveMethod: $scope.saveMethod(),
|
||||
action: "save",
|
||||
showNotifications: false
|
||||
}).then(function (data) {
|
||||
overlayService.close();
|
||||
return $q.when(data);
|
||||
},
|
||||
function (err) {
|
||||
model.submitButtonState = "error";
|
||||
//re-map the dialog model since we've re-bound the properties
|
||||
dialog.variants = $scope.content.variants;
|
||||
|
||||
return $q.reject(err);
|
||||
});
|
||||
},
|
||||
close: function (oldModel) {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
|
||||
overlayService.open(dialog);
|
||||
}
|
||||
}
|
||||
else {
|
||||
return performSave({ saveMethod: $scope.saveMethod(), action: "save" });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
$scope.preview = function (content) {
|
||||
@@ -455,7 +571,7 @@
|
||||
else {
|
||||
$scope.save().then(function (data) {
|
||||
previewWindow.location.href = redirect;
|
||||
}).catch(angular.noop);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -509,69 +625,29 @@
|
||||
notificationsService.error(error.headline, error.content);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.closeSplitView = function (index, editor) {
|
||||
// hacky animation stuff - it will be much better when angular is upgraded
|
||||
editor.loading = true;
|
||||
editor.collapsed = true;
|
||||
$timeout(function () {
|
||||
$scope.editors.splice(index, 1);
|
||||
}, 400);
|
||||
};
|
||||
|
||||
$scope.openInSplitView = function (selectedVariant) {
|
||||
|
||||
console.log(selectedVariant);
|
||||
|
||||
var editor = {};
|
||||
// hacking animation states - these should hopefully be easier to do when we upgrade angular
|
||||
editor.collapsed = true;
|
||||
editor.loading = true;
|
||||
$scope.editors.push(editor);
|
||||
var editorIndex = $scope.editors.length - 1;
|
||||
$timeout(function () {
|
||||
$scope.editors[editorIndex].collapsed = false;
|
||||
}, 100);
|
||||
|
||||
// fake loading of content
|
||||
// TODO: Make this real, but how do we deal with saving since currently we only save one variant at a time?!
|
||||
$timeout(function () {
|
||||
$scope.editors[editorIndex].content = angular.copy($scope.content);
|
||||
$scope.editors[editorIndex].content.name = "What a variant";
|
||||
// set selected variant on split view content
|
||||
angular.forEach($scope.editors[editorIndex].content.variants, function (variant) {
|
||||
if (variant.culture === selectedVariant.culture) {
|
||||
variant.current = true;
|
||||
} else {
|
||||
variant.current = false;
|
||||
}
|
||||
});
|
||||
$scope.editors[editorIndex].loading = false;
|
||||
}, 500);
|
||||
};
|
||||
|
||||
/* publish method used in infinite editing */
|
||||
$scope.publishAndClose = function(content) {
|
||||
$scope.publishAndClose = function (content) {
|
||||
$scope.publishAndCloseButtonState = "busy";
|
||||
performSave({ saveMethod: contentResource.publish, action: "publish" }).then(function(){
|
||||
if($scope.infiniteModel.submit) {
|
||||
performSave({ saveMethod: contentResource.publish, action: "publish" }).then(function () {
|
||||
if ($scope.infiniteModel.submit) {
|
||||
$scope.infiniteModel.contentNode = content;
|
||||
$scope.infiniteModel.submit($scope.infiniteModel);
|
||||
}
|
||||
$scope.publishAndCloseButtonState = "success";
|
||||
}).catch(angular.noop);;
|
||||
});
|
||||
};
|
||||
|
||||
/* save method used in infinite editing */
|
||||
$scope.saveAndClose = function(content) {
|
||||
$scope.saveAndClose = function (content) {
|
||||
$scope.saveAndCloseButtonState = "busy";
|
||||
performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function(){
|
||||
if($scope.infiniteModel.submit) {
|
||||
performSave({ saveMethod: $scope.saveMethod(), action: "save" }).then(function () {
|
||||
if ($scope.infiniteModel.submit) {
|
||||
$scope.infiniteModel.contentNode = content;
|
||||
$scope.infiniteModel.submit($scope.infiniteModel);
|
||||
}
|
||||
$scope.saveAndCloseButtonState = "success";
|
||||
}).catch(angular.noop);;
|
||||
});
|
||||
};
|
||||
|
||||
function moveNode(node, target) {
|
||||
@@ -585,7 +661,7 @@
|
||||
}
|
||||
|
||||
// sync the destination node
|
||||
if(!infiniteMode) {
|
||||
if (!infiniteMode) {
|
||||
navigationService.syncTree({ tree: "content", path: path, forceReload: true, activate: false });
|
||||
}
|
||||
|
||||
@@ -603,17 +679,28 @@
|
||||
}
|
||||
|
||||
// methods for infinite editing
|
||||
$scope.close = function() {
|
||||
if($scope.infiniteModel.close) {
|
||||
$scope.close = function () {
|
||||
if ($scope.infiniteModel.close) {
|
||||
$scope.infiniteModel.close($scope.infiniteModel);
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$watch('culture', function (newVal, oldVal) {
|
||||
if (newVal !== oldVal) {
|
||||
setActiveCulture();
|
||||
}
|
||||
});
|
||||
|
||||
//ensure to unregister from all events!
|
||||
$scope.$on('$destroy', function () {
|
||||
for (var e in evts) {
|
||||
eventsService.unsubscribe(evts[e]);
|
||||
}
|
||||
//since we are not notifying and clearing server validation messages when they are received due to how the variant
|
||||
//switching works, we need to ensure they are cleared when this editor is destroyed
|
||||
if (!$scope.page.isNew) {
|
||||
serverValidationManager.clear();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
+56
-27
@@ -8,10 +8,11 @@
|
||||
var evts = [];
|
||||
var isInfoTab = false;
|
||||
var labels = {};
|
||||
scope.publishStatus = {};
|
||||
scope.publishStatus = [];
|
||||
|
||||
scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates;
|
||||
scope.allowChangeDocumentType = false;
|
||||
scope.allowChangeTemplate = false;
|
||||
|
||||
function onInit() {
|
||||
|
||||
@@ -20,6 +21,7 @@
|
||||
angular.forEach(user.sections, function(section){
|
||||
if(section.alias === "settings") {
|
||||
scope.allowChangeDocumentType = true;
|
||||
scope.allowChangeTemplate = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -28,15 +30,17 @@
|
||||
"general_deleted",
|
||||
"content_unpublished",
|
||||
"content_published",
|
||||
"content_publishedPendingChanges"
|
||||
"content_publishedPendingChanges",
|
||||
"content_notCreated"
|
||||
];
|
||||
|
||||
localizationService.localizeMany(keys)
|
||||
.then(function(data){
|
||||
labels.deleted = data[0];
|
||||
labels.unpublished = data[1];
|
||||
labels.unpublished = data[1]; //aka draft
|
||||
labels.published = data[2];
|
||||
labels.publishedPendingChanges = data[3];
|
||||
labels.notCreated = data[4];
|
||||
|
||||
setNodePublishStatus(scope.node);
|
||||
|
||||
@@ -92,15 +96,21 @@
|
||||
};
|
||||
|
||||
scope.openTemplate = function () {
|
||||
var url = "/settings/templates/edit/" + scope.node.templateId;
|
||||
$location.url(url);
|
||||
var templateEditor = {
|
||||
id: scope.node.templateId,
|
||||
submit: function(model) {
|
||||
editorService.close();
|
||||
},
|
||||
close: function() {
|
||||
editorService.close();
|
||||
}
|
||||
};
|
||||
editorService.templateEditor(templateEditor);
|
||||
}
|
||||
|
||||
scope.updateTemplate = function (templateAlias) {
|
||||
|
||||
// update template value
|
||||
scope.node.template = templateAlias;
|
||||
|
||||
};
|
||||
|
||||
scope.datePickerChange = function (event, type) {
|
||||
@@ -163,31 +173,50 @@
|
||||
}
|
||||
|
||||
function setNodePublishStatus(node) {
|
||||
|
||||
|
||||
scope.publishStatus = [];
|
||||
|
||||
// deleted node
|
||||
if(node.trashed === true) {
|
||||
scope.publishStatus.label = labels.deleted;
|
||||
scope.publishStatus.color = "danger";
|
||||
if (node.trashed === true) {
|
||||
scope.publishStatus.push({
|
||||
label: labels.deleted,
|
||||
color: "danger"
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// unpublished node
|
||||
if(node.published === false && node.trashed === false) {
|
||||
scope.publishStatus.label = labels.unpublished;
|
||||
scope.publishStatus.color = "gray";
|
||||
}
|
||||
if (node.variants) {
|
||||
for (var i = 0; i < node.variants.length; i++) {
|
||||
|
||||
// published node
|
||||
if(node.publishDate && node.published === true) {
|
||||
scope.publishStatus.label = labels.published;
|
||||
scope.publishStatus.color = "success";
|
||||
}
|
||||
var variant = node.variants[i];
|
||||
|
||||
// published node with pending changes
|
||||
if (node.edited === true && node.publishDate) {
|
||||
scope.publishStatus.label = labels.publishedPendingChanges;
|
||||
scope.publishStatus.color = "success"
|
||||
}
|
||||
var status = {
|
||||
culture: variant.language ? variant.language.culture : null
|
||||
};
|
||||
|
||||
if (variant.state === "NotCreated") {
|
||||
status.label = labels.notCreated;
|
||||
status.color = "gray";
|
||||
}
|
||||
else if (variant.state === "Draft") {
|
||||
// draft node
|
||||
status.label = labels.unpublished;
|
||||
status.color = "gray";
|
||||
}
|
||||
else if (variant.state === "Published") {
|
||||
// published node
|
||||
status.label = labels.published;
|
||||
status.color = "success";
|
||||
}
|
||||
else if (variant.state === "PublishedPendingChanges") {
|
||||
// published node with pending changes
|
||||
status.label = labels.publishedPendingChanges;
|
||||
status.color = "success";
|
||||
}
|
||||
|
||||
scope.publishStatus.push(status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setPublishDate(date) {
|
||||
@@ -293,7 +322,7 @@
|
||||
});
|
||||
}));
|
||||
|
||||
// watch for content updates - reload content when node is saved, published etc.
|
||||
// watch for content state updates
|
||||
scope.$watch('node.updateDate', function(newValue, oldValue){
|
||||
|
||||
if(!newValue) { return; }
|
||||
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/** This directive is used to render out the current variant tabs and properties and exposes an API for other directives to consume */
|
||||
function tabbedContentDirective() {
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/content/umb-tabbed-content.html',
|
||||
controller: function ($scope) {
|
||||
|
||||
//expose the property/methods for other directives to use
|
||||
this.content = $scope.content;
|
||||
|
||||
$scope.$watch("tabbedContentForm.$dirty",
|
||||
function (newValue, oldValue) {
|
||||
if (newValue === true) {
|
||||
$scope.content.isDirty = true;
|
||||
}
|
||||
});
|
||||
},
|
||||
link: function(scope) {
|
||||
|
||||
function onInit() {
|
||||
angular.forEach(scope.content.tabs, function (group) {
|
||||
group.open = true;
|
||||
});
|
||||
}
|
||||
|
||||
onInit();
|
||||
|
||||
},
|
||||
scope: {
|
||||
content: "="
|
||||
}
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbTabbedContent', tabbedContentDirective);
|
||||
|
||||
})();
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A directive to encapsulate each variant editor which includes the name header and all content apps for a given variant
|
||||
* @param {any} $timeout
|
||||
* @param {any} $location
|
||||
*/
|
||||
function variantContentDirective($timeout, $location) {
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/content/umb-variant-content.html',
|
||||
link: function (scope) {
|
||||
|
||||
/**
|
||||
* Adds a new editor to the editors array to show content in a split view
|
||||
* @param {any} selectedVariant
|
||||
*/
|
||||
scope.openInSplitView = function (selectedVariant) {
|
||||
|
||||
var selectedCulture = selectedVariant.language.culture;
|
||||
|
||||
//only the content app can be selected since no other apps are shown, and because we copy all of these apps
|
||||
//to the "editors" we need to update this across all editors
|
||||
for (var e = 0; e < scope.editors.length; e++) {
|
||||
var editor = scope.editors[e];
|
||||
for (var i = 0; i < editor.content.apps.length; i++) {
|
||||
var app = editor.content.apps[i];
|
||||
if (app.alias === "content") {
|
||||
app.active = true;
|
||||
}
|
||||
else {
|
||||
app.active = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Find the whole variant model based on the culture that was chosen
|
||||
var variant = _.find(scope.content.variants, function (v) {
|
||||
return v.language.culture === selectedCulture;
|
||||
});
|
||||
|
||||
var editor = {
|
||||
content: scope.initVariant({ variant: variant})
|
||||
};
|
||||
scope.editors.push(editor);
|
||||
|
||||
//TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
|
||||
editor.collapsed = true;
|
||||
editor.loading = true;
|
||||
$timeout(function () {
|
||||
editor.collapsed = false;
|
||||
editor.loading = false;
|
||||
scope.onSplitViewChanged();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the currently selected variant
|
||||
* @param {any} variantDropDownItem
|
||||
*/
|
||||
scope.selectVariant = function (variantDropDownItem) {
|
||||
|
||||
var editorIndex = _.findIndex(scope.editors, function (e) {
|
||||
return e === scope.editor;
|
||||
});
|
||||
|
||||
//if the editor index is zero, then update the query string to track the lang selection, otherwise if it's part
|
||||
//of a 2nd split view editor then update the model directly.
|
||||
if (editorIndex === 0) {
|
||||
//if we've made it this far, then update the query string
|
||||
$location.search("cculture", variantDropDownItem.language.culture);
|
||||
}
|
||||
else {
|
||||
//set all variant drop down items as inactive for this editor and then set the selected on as active
|
||||
for (var i = 0; i < scope.editor.content.variants.length; i++) {
|
||||
scope.editor.content.variants[i].active = false;
|
||||
}
|
||||
variantDropDownItem.active = true;
|
||||
|
||||
//get the variant content model and initialize the editor with that
|
||||
var variant = _.find(scope.content.variants, function (v) {
|
||||
return v.language.culture === variantDropDownItem.language.culture;
|
||||
});
|
||||
scope.editor.content = scope.initVariant({ variant: variant });
|
||||
}
|
||||
};
|
||||
|
||||
/** Closes the split view */
|
||||
scope.closeSplitView = function () {
|
||||
//TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
|
||||
scope.editor.loading = true;
|
||||
scope.editor.collapsed = true;
|
||||
$timeout(function () {
|
||||
var index = _.findIndex(scope.editors, function(e) {
|
||||
return e === scope.editor;
|
||||
});
|
||||
scope.editors.splice(index, 1);
|
||||
scope.onSplitViewChanged();
|
||||
}, 400);
|
||||
};
|
||||
|
||||
//set the content to dirty if the header changes
|
||||
scope.$watch("contentHeaderForm.$dirty",
|
||||
function (newValue, oldValue) {
|
||||
if (newValue === true) {
|
||||
scope.editor.content.isDirty = true;
|
||||
}
|
||||
});
|
||||
|
||||
},
|
||||
scope: {
|
||||
//TODO: This should be turned into a proper component
|
||||
|
||||
page: "=",
|
||||
content: "=",
|
||||
editor: "=",
|
||||
editors: "=",
|
||||
//TODO: I don't like having this callback defined and would like to make this directive a bit less
|
||||
// coupled but right now don't have time
|
||||
initVariant: "&",
|
||||
onSplitViewChanged: "&"
|
||||
}
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbVariantContent', variantContentDirective);
|
||||
|
||||
})();
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function umbVariantStateController($scope, $element) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
}
|
||||
|
||||
var umbVariantStateComponent = {
|
||||
templateUrl: 'views/components/content/umb-variant-state.html',
|
||||
bindings: {
|
||||
variant: "<"
|
||||
},
|
||||
controllerAs: 'vm',
|
||||
controller: umbVariantStateController
|
||||
};
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.component('umbVariantState', umbVariantStateComponent);
|
||||
|
||||
})();
|
||||
+22
-21
@@ -37,34 +37,35 @@ Use this directive to construct a main content area inside the main editor windo
|
||||
</ul>
|
||||
**/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function EditorContainerDirective(overlayHelper) {
|
||||
function EditorContainerDirective(overlayHelper) {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
scope.numberOfOverlays = 0;
|
||||
scope.numberOfOverlays = 0;
|
||||
|
||||
scope.$watch(function(){
|
||||
return overlayHelper.getNumberOfOverlays();
|
||||
}, function (newValue) {
|
||||
scope.numberOfOverlays = newValue;
|
||||
});
|
||||
//TODO: this shouldn't be a watch, this should be based on an event handler
|
||||
scope.$watch(function () {
|
||||
return overlayHelper.getNumberOfOverlays();
|
||||
}, function (newValue) {
|
||||
scope.numberOfOverlays = newValue;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var directive = {
|
||||
transclude: true,
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/editor/umb-editor-container.html',
|
||||
link: link
|
||||
};
|
||||
var directive = {
|
||||
transclude: true,
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/editor/umb-editor-container.html',
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
}
|
||||
return directive;
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbEditorContainer', EditorContainerDirective);
|
||||
angular.module('umbraco.directives').directive('umbEditorContainer', EditorContainerDirective);
|
||||
|
||||
})();
|
||||
|
||||
+40
-47
@@ -201,67 +201,49 @@ Use this directive to construct a header inside the main editor window.
|
||||
|
||||
**/
|
||||
|
||||
(function() {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function EditorHeaderDirective(iconHelper, $location, editorService) {
|
||||
function EditorHeaderDirective(iconHelper, editorService, $location, $routeParams) {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
|
||||
scope.vm = {};
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.vm.currentVariant = "";
|
||||
|
||||
function onInit() {
|
||||
setCurrentVariant(scope.variants);
|
||||
setVariantStatusColor(scope.variants);
|
||||
setCurrentVariant();
|
||||
}
|
||||
|
||||
function setCurrentVariant(variants) {
|
||||
|
||||
angular.forEach(variants, function (variant) {
|
||||
if(variant.current) {
|
||||
function setCurrentVariant() {
|
||||
angular.forEach(scope.variants, function (variant) {
|
||||
if (variant.active) {
|
||||
scope.vm.currentVariant = variant;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//TODO: This doesn't really affect any UI currently, need some feedback from mads
|
||||
function setVariantStatusColor(variants) {
|
||||
angular.forEach(variants, function (variant) {
|
||||
|
||||
//TODO: What about variant.exists? If we are applying colors/styles, this should be one of them
|
||||
|
||||
switch (variant.state) {
|
||||
case "Published":
|
||||
variant.stateColor = "success";
|
||||
break;
|
||||
case "Unpublished":
|
||||
//TODO: Not sure if these statuses will ever bubble up to the UI?
|
||||
case "Publishing":
|
||||
case "Unpublishing":
|
||||
default:
|
||||
variant.stateColor = "gray";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scope.goBack = function () {
|
||||
if (scope.onBack) {
|
||||
scope.onBack();
|
||||
$location.path('/' + $routeParams.section + '/' + $routeParams.tree + '/' + $routeParams.method + '/' + scope.menu.currentNode.parentId);
|
||||
}
|
||||
};
|
||||
|
||||
scope.selectVariant = function (event, variant) {
|
||||
scope.vm.dropdownOpen = false;
|
||||
$location.search("cculture", variant.language.culture);
|
||||
|
||||
if (scope.onSelectVariant) {
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.onSelectVariant({ "variant": variant });
|
||||
}
|
||||
};
|
||||
|
||||
scope.openIconPicker = function() {
|
||||
scope.openIconPicker = function () {
|
||||
var iconPicker = {
|
||||
icon: scope.icon.split(' ')[0],
|
||||
color: scope.icon.split(' ')[1],
|
||||
submit: function(model) {
|
||||
submit: function (model) {
|
||||
if (model.icon) {
|
||||
if (model.color) {
|
||||
scope.icon = model.icon + " " + model.color;
|
||||
@@ -273,37 +255,47 @@ Use this directive to construct a header inside the main editor window.
|
||||
}
|
||||
editorService.close();
|
||||
},
|
||||
close: function() {
|
||||
close: function () {
|
||||
editorService.close();
|
||||
}
|
||||
};
|
||||
editorService.iconPicker(iconPicker);
|
||||
};
|
||||
|
||||
scope.closeSplitView = function() {
|
||||
if(scope.onCloseSplitView) {
|
||||
scope.closeSplitView = function () {
|
||||
if (scope.onCloseSplitView) {
|
||||
scope.onCloseSplitView();
|
||||
}
|
||||
};
|
||||
|
||||
scope.openInSplitView = function(event, variant) {
|
||||
if(scope.onOpenInSplitView) {
|
||||
scope.openInSplitView = function (event, variant) {
|
||||
if (scope.onOpenInSplitView) {
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.onOpenInSplitView({"variant": variant});
|
||||
scope.onOpenInSplitView({ "variant": variant });
|
||||
}
|
||||
};
|
||||
|
||||
scope.$watch('variants', function(newValue, oldValue){
|
||||
if(!newValue) return;
|
||||
if(newValue === oldValue) return;
|
||||
setCurrentVariant(newValue);
|
||||
setVariantStatusColor(newValue);
|
||||
}, true);
|
||||
|
||||
onInit();
|
||||
|
||||
//watch for the active culture changing, if it changes, update the current variant
|
||||
if (scope.variants) {
|
||||
scope.$watch(function () {
|
||||
for (var i = 0; i < scope.variants.length; i++) {
|
||||
var v = scope.variants[i];
|
||||
if (v.active) {
|
||||
return v.language.culture;
|
||||
}
|
||||
}
|
||||
return scope.vm.currentVariant.language.culture; //should never get here
|
||||
}, function (newValue, oldValue) {
|
||||
if (newValue !== scope.vm.currentVariant.language.culture) {
|
||||
setCurrentVariant();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var directive = {
|
||||
transclude: true,
|
||||
restrict: 'E',
|
||||
@@ -331,7 +323,8 @@ Use this directive to construct a header inside the main editor window.
|
||||
showBackButton: "<?",
|
||||
splitViewOpen: "=?",
|
||||
onOpenInSplitView: "&?",
|
||||
onCloseSplitView: "&?"
|
||||
onCloseSplitView: "&?",
|
||||
onSelectVariant: "&?"
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
+89
-16
@@ -1,18 +1,79 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function EditorNavigationDirective(eventsService) {
|
||||
function EditorNavigationDirective($window, $timeout, eventsService, windowResizeListener) {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
function link(scope) {
|
||||
|
||||
scope.showNavigation = true;
|
||||
scope.showMoreButton = false;
|
||||
scope.showDropdown = false;
|
||||
scope.overflowingItems = 0;
|
||||
scope.itemsLimit = 6;
|
||||
|
||||
scope.moreButton = {
|
||||
alias: "more",
|
||||
active: false,
|
||||
name: "More"
|
||||
};
|
||||
|
||||
scope.clickNavigationItem = function (selectedItem) {
|
||||
setItemToActive(selectedItem);
|
||||
scope.showDropdown = false;
|
||||
runItemAction(selectedItem);
|
||||
eventsService.emit("app.tabChange", selectedItem);
|
||||
setItemToActive(selectedItem);
|
||||
};
|
||||
|
||||
scope.toggleDropdown = function () {
|
||||
scope.showDropdown = !scope.showDropdown;
|
||||
};
|
||||
|
||||
scope.hideDropdown = function() {
|
||||
scope.showDropdown = false;
|
||||
};
|
||||
|
||||
function onInit() {
|
||||
|
||||
// hide navigation if there is only 1 item
|
||||
if (scope.navigation.length <= 1) {
|
||||
scope.showNavigation = false;
|
||||
}
|
||||
|
||||
$timeout(function(){
|
||||
if($window && $window.innerWidth) {
|
||||
calculateVisibleItems($window.innerWidth);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function calculateVisibleItems(windowWidth) {
|
||||
|
||||
// if we don't get a windowWidth stick with the default item limit
|
||||
if(!windowWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
scope.itemsLimit = 0;
|
||||
|
||||
// set visible items based on browser width
|
||||
if (windowWidth > 1500) {
|
||||
scope.itemsLimit = 6;
|
||||
}
|
||||
else if (windowWidth > 700) {
|
||||
scope.itemsLimit = 4;
|
||||
}
|
||||
|
||||
// toggle more button
|
||||
if(scope.navigation.length > scope.itemsLimit) {
|
||||
scope.showMoreButton = true;
|
||||
scope.overflowingItems = scope.itemsLimit - scope.navigation.length;
|
||||
} else {
|
||||
scope.showMoreButton = false;
|
||||
scope.overflowingItems = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function runItemAction(selectedItem) {
|
||||
if (selectedItem.action) {
|
||||
selectedItem.action(selectedItem);
|
||||
@@ -20,29 +81,41 @@
|
||||
}
|
||||
|
||||
function setItemToActive(selectedItem) {
|
||||
// set all other views to inactive
|
||||
if (selectedItem.view) {
|
||||
|
||||
for (var index = 0; index < scope.navigation.length; index++) {
|
||||
var item = scope.navigation[index];
|
||||
|
||||
// deselect all items
|
||||
angular.forEach(scope.navigation, function(item, index){
|
||||
item.active = false;
|
||||
});
|
||||
|
||||
// set clicked item to active
|
||||
selectedItem.active = true;
|
||||
|
||||
// set more button to active if item in dropdown is clicked
|
||||
var selectedItemIndex = scope.navigation.indexOf(selectedItem);
|
||||
if (selectedItemIndex + 1 > scope.itemsLimit) {
|
||||
scope.moreButton.active = true;
|
||||
} else {
|
||||
scope.moreButton.active = false;
|
||||
}
|
||||
|
||||
// set view to active
|
||||
selectedItem.active = true;
|
||||
}
|
||||
}
|
||||
|
||||
function activate() {
|
||||
|
||||
// hide navigation if there is only 1 item
|
||||
if (scope.navigation.length <= 1) {
|
||||
scope.showNavigation = false;
|
||||
var resizeCallback = function(size) {
|
||||
if(size && size.width) {
|
||||
calculateVisibleItems(size.width);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
windowResizeListener.register(resizeCallback);
|
||||
|
||||
activate();
|
||||
//ensure to unregister from all events and kill jquery plugins
|
||||
scope.$on('$destroy', function () {
|
||||
windowResizeListener.unregister(resizeCallback);
|
||||
});
|
||||
|
||||
onInit();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A directive that renders a defined view with a view model and a the whole content model.
|
||||
**/
|
||||
function EditorSubViewDirective() {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
//The model can contain: view, viewModel, name, alias, icon
|
||||
|
||||
if (!scope.model.view) {
|
||||
throw "No view defined for the content app";
|
||||
}
|
||||
}
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/editor/umb-editor-sub-view.html',
|
||||
scope: {
|
||||
model: "=",
|
||||
content: "="
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbEditorSubView', EditorSubViewDirective);
|
||||
|
||||
})();
|
||||
+23
-40
@@ -1,48 +1,31 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function EditorSubViewsDirective() {
|
||||
/**
|
||||
* A directive that just repeats over a list of defined views which are all able to access the same common model.
|
||||
* This is only used in simple cases, whereas media and content use umbEditorSubView (singular) which allows
|
||||
* passing in a view model specific to the view and the entire content model for support if required.
|
||||
**/
|
||||
function EditorSubViewsDirective() {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
scope.activeView = {};
|
||||
}
|
||||
|
||||
// set toolbar from selected navigation item
|
||||
function setActiveView(items) {
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/editor/umb-editor-sub-views.html',
|
||||
scope: {
|
||||
subViews: "=",
|
||||
model: "="
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
for (var index = 0; index < items.length; index++) {
|
||||
return directive;
|
||||
}
|
||||
|
||||
var item = items[index];
|
||||
|
||||
if (item.active && item.view) {
|
||||
scope.activeView = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// watch for navigation changes
|
||||
scope.$watch('subViews', function(newValue, oldValue) {
|
||||
if (newValue) {
|
||||
setActiveView(newValue);
|
||||
}
|
||||
}, true);
|
||||
|
||||
}
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/editor/umb-editor-sub-views.html',
|
||||
scope: {
|
||||
subViews: "=",
|
||||
model: "="
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbEditorSubViews', EditorSubViewsDirective);
|
||||
angular.module('umbraco.directives').directive('umbEditorSubViews', EditorSubViewsDirective);
|
||||
|
||||
})();
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
|
||||
http://vitalets.github.io/checklist-model/
|
||||
https://vitalets.github.io/checklist-model/
|
||||
<label ng-repeat="role in roles">
|
||||
<input type="checkbox" checklist-model="user.roles" checklist-value="role.id"> {{role.text}}
|
||||
</label>
|
||||
@@ -19,7 +19,7 @@ angular.module('umbraco.directives')
|
||||
return false;
|
||||
}
|
||||
|
||||
// add
|
||||
// add
|
||||
function add(arr, item) {
|
||||
arr = angular.isArray(arr) ? arr : [];
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
@@ -44,7 +44,7 @@ angular.module('umbraco.directives')
|
||||
return arr;
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/19228302/1458162
|
||||
// https://stackoverflow.com/a/19228302/1458162
|
||||
function postLinkFn(scope, elem, attrs) {
|
||||
// compile with `ng-model` pointing to `checked`
|
||||
$compile(elem)(scope);
|
||||
@@ -98,4 +98,4 @@ angular.module('umbraco.directives')
|
||||
return postLinkFn;
|
||||
}
|
||||
};
|
||||
}]);
|
||||
}]);
|
||||
|
||||
+2
-2
@@ -53,7 +53,7 @@ angular.module("umbraco.directives")
|
||||
var $container = element.find(".crop-container");
|
||||
|
||||
//default constraints for drag n drop
|
||||
var constraints = {left: {max: scope.dimensions.margin, min: scope.dimensions.margin}, top: {max: scope.dimensions.margin, min: scope.dimensions.margin}, };
|
||||
var constraints = {left: {max: scope.dimensions.margin, min: scope.dimensions.margin}, top: {max: scope.dimensions.margin, min: scope.dimensions.margin} };
|
||||
scope.constraints = constraints;
|
||||
|
||||
|
||||
@@ -264,7 +264,7 @@ angular.module("umbraco.directives")
|
||||
});
|
||||
|
||||
//ie hack
|
||||
if(window.navigator.userAgent.indexOf("MSIE ")){
|
||||
if(window.navigator.userAgent.indexOf("MSIE ") >= 0){
|
||||
var ranger = element.find("input");
|
||||
ranger.bind("change",function(){
|
||||
scope.$apply(function(){
|
||||
|
||||
+179
-119
@@ -1,139 +1,199 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:umbImageGravity
|
||||
* @restrict E
|
||||
* @function
|
||||
* @description
|
||||
**/
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbImageGravity', function ($timeout, localizationService, $log) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/imaging/umb-image-gravity.html',
|
||||
scope: {
|
||||
src: '=',
|
||||
center: "=",
|
||||
onImageLoaded: "="
|
||||
},
|
||||
link: function(scope, element, attrs) {
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
//Internal values for keeping track of the dot and the size of the editor
|
||||
scope.dimensions = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
top: 0
|
||||
};
|
||||
function umbImageGravityController($scope, $element, $timeout) {
|
||||
|
||||
scope.loaded = false;
|
||||
var vm = this;
|
||||
|
||||
//elements
|
||||
var $viewport = element.find(".viewport");
|
||||
var $image = element.find("img");
|
||||
var $overlay = element.find(".overlay");
|
||||
//Internal values for keeping track of the dot and the size of the editor
|
||||
vm.dimensions = {
|
||||
width: 0,
|
||||
height: 0,
|
||||
left: 0,
|
||||
top: 0
|
||||
};
|
||||
|
||||
scope.style = function () {
|
||||
if(scope.dimensions.width <= 0){
|
||||
setDimensions();
|
||||
}
|
||||
var htmlImage = null; //DOM element reference
|
||||
var htmlOverlay = null; //DOM element reference
|
||||
var draggable = null;
|
||||
|
||||
return {
|
||||
'top': scope.dimensions.top + 'px',
|
||||
'left': scope.dimensions.left + 'px'
|
||||
};
|
||||
};
|
||||
vm.loaded = false;
|
||||
vm.$onInit = onInit;
|
||||
vm.$onChanges = onChanges;
|
||||
vm.$postLink = postLink;
|
||||
vm.$onDestroy = onDestroy;
|
||||
vm.style = style;
|
||||
vm.setFocalPoint = setFocalPoint;
|
||||
|
||||
scope.setFocalPoint = function(event) {
|
||||
/** Sets the css style for the Dot */
|
||||
function style() {
|
||||
|
||||
scope.$emit("imageFocalPointStart");
|
||||
if (vm.dimensions.width <= 0) {
|
||||
//this initializes the dimensions since when the image element first loads
|
||||
//there will be zero dimensions
|
||||
setDimensions();
|
||||
}
|
||||
|
||||
var offsetX = event.offsetX - 10;
|
||||
var offsetY = event.offsetY - 10;
|
||||
return {
|
||||
'top': vm.dimensions.top + 'px',
|
||||
'left': vm.dimensions.left + 'px'
|
||||
};
|
||||
};
|
||||
|
||||
calculateGravity(offsetX, offsetY);
|
||||
function setFocalPoint (event) {
|
||||
|
||||
lazyEndEvent();
|
||||
$scope.$emit("imageFocalPointStart");
|
||||
|
||||
};
|
||||
var offsetX = event.offsetX - 10;
|
||||
var offsetY = event.offsetY - 10;
|
||||
|
||||
var setDimensions = function(){
|
||||
scope.dimensions.width = $image.width();
|
||||
scope.dimensions.height = $image.height();
|
||||
calculateGravity(offsetX, offsetY);
|
||||
|
||||
if(scope.center){
|
||||
scope.dimensions.left = scope.center.left * scope.dimensions.width -10;
|
||||
scope.dimensions.top = scope.center.top * scope.dimensions.height -10;
|
||||
}else{
|
||||
scope.center = { left: 0.5, top: 0.5 };
|
||||
}
|
||||
};
|
||||
lazyEndEvent();
|
||||
|
||||
var calculateGravity = function(offsetX, offsetY){
|
||||
scope.dimensions.left = offsetX;
|
||||
scope.dimensions.top = offsetY;
|
||||
};
|
||||
|
||||
scope.center.left = (scope.dimensions.left+10) / scope.dimensions.width;
|
||||
scope.center.top = (scope.dimensions.top+10) / scope.dimensions.height;
|
||||
};
|
||||
/** Initializes the component */
|
||||
function onInit() {
|
||||
if (!vm.center) {
|
||||
vm.center = { left: 0.5, top: 0.5 };
|
||||
}
|
||||
}
|
||||
|
||||
var lazyEndEvent = _.debounce(function(){
|
||||
scope.$apply(function(){
|
||||
scope.$emit("imageFocalPointStop");
|
||||
});
|
||||
}, 2000);
|
||||
/** Called when the component has linked everything and the DOM is available */
|
||||
function postLink() {
|
||||
//elements
|
||||
htmlImage = $element.find("img");
|
||||
htmlOverlay = $element.find(".overlay");
|
||||
|
||||
|
||||
//Drag and drop positioning, using jquery ui draggable
|
||||
//TODO ensure that the point doesnt go outside the box
|
||||
$overlay.draggable({
|
||||
containment: "parent",
|
||||
start: function(){
|
||||
scope.$apply(function(){
|
||||
scope.$emit("imageFocalPointStart");
|
||||
});
|
||||
},
|
||||
stop: function() {
|
||||
scope.$apply(function(){
|
||||
var offsetX = $overlay[0].offsetLeft;
|
||||
var offsetY = $overlay[0].offsetTop;
|
||||
calculateGravity(offsetX, offsetY);
|
||||
});
|
||||
|
||||
lazyEndEvent();
|
||||
}
|
||||
});
|
||||
|
||||
//// INIT /////
|
||||
$image.load(function() {
|
||||
$timeout(function() {
|
||||
setDimensions();
|
||||
scope.loaded = true;
|
||||
if (angular.isFunction(scope.onImageLoaded)) {
|
||||
scope.onImageLoaded();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(window).on('resize.umbImageGravity', function(){
|
||||
scope.$apply(function(){
|
||||
$timeout(function(){
|
||||
setDimensions();
|
||||
});
|
||||
// Make sure we can find the offset values for the overlay(dot) before calculating
|
||||
// fixes issue with resize event when printing the page (ex. hitting ctrl+p inside the rte)
|
||||
if($overlay.is(':visible')) {
|
||||
var offsetX = $overlay[0].offsetLeft;
|
||||
var offsetY = $overlay[0].offsetTop;
|
||||
calculateGravity(offsetX, offsetY);
|
||||
}
|
||||
});
|
||||
//Drag and drop positioning, using jquery ui draggable
|
||||
draggable = htmlOverlay.draggable({
|
||||
containment: "parent",
|
||||
start: function () {
|
||||
$scope.$apply(function () {
|
||||
$scope.$emit("imageFocalPointStart");
|
||||
});
|
||||
},
|
||||
stop: function () {
|
||||
$scope.$apply(function () {
|
||||
var offsetX = htmlOverlay[0].offsetLeft;
|
||||
var offsetY = htmlOverlay[0].offsetTop;
|
||||
calculateGravity(offsetX, offsetY);
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
$(window).off('.umbImageGravity');
|
||||
});
|
||||
lazyEndEvent();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
$(window).on('resize.umbImageGravity', function () {
|
||||
$scope.$apply(function () {
|
||||
resized();
|
||||
});
|
||||
});
|
||||
|
||||
//if any ancestor directive emits this event, we need to resize
|
||||
$scope.$on("editors.content.splitViewChanged", function () {
|
||||
$timeout(resized, 200);
|
||||
});
|
||||
|
||||
//listen for the image DOM element loading
|
||||
htmlImage.on("load", function () {
|
||||
$timeout(function () {
|
||||
setDimensions();
|
||||
vm.loaded = true;
|
||||
if (vm.onImageLoaded) {
|
||||
vm.onImageLoaded();
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
function onDestroy() {
|
||||
$(window).off('resize.umbImageGravity');
|
||||
if (htmlOverlay) {
|
||||
//TODO: This should be destroyed but this will throw an exception:
|
||||
// "cannot call methods on draggable prior to initialization; attempted to call method 'destroy'"
|
||||
// I've tried lots of things and cannot get this to work, we weren't destroying before so hopefully
|
||||
// there's no mem leaks?
|
||||
//htmlOverlay.draggable("destroy");
|
||||
}
|
||||
if (htmlImage) {
|
||||
htmlImage.off("load");
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when we need to resize based on window or DOM dimensions to re-center the focal point */
|
||||
function resized() {
|
||||
$timeout(function () {
|
||||
setDimensions();
|
||||
});
|
||||
// Make sure we can find the offset values for the overlay(dot) before calculating
|
||||
// fixes issue with resize event when printing the page (ex. hitting ctrl+p inside the rte)
|
||||
if (htmlOverlay.is(':visible')) {
|
||||
var offsetX = htmlOverlay[0].offsetLeft;
|
||||
var offsetY = htmlOverlay[0].offsetTop;
|
||||
calculateGravity(offsetX, offsetY);
|
||||
}
|
||||
}
|
||||
|
||||
/** Watches the one way binding changes */
|
||||
function onChanges(changes) {
|
||||
if (changes.center && !changes.center.isFirstChange()
|
||||
&& changes.center.currentValue
|
||||
&& !angular.equals(changes.center.currentValue, changes.center.previousValue)) {
|
||||
//when center changes update the dimensions
|
||||
setDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the width/height/left/top dimentions based on the image size and the "center" value */
|
||||
function setDimensions() {
|
||||
if (htmlImage && vm.center) {
|
||||
vm.dimensions.width = htmlImage.width();
|
||||
vm.dimensions.height = htmlImage.height();
|
||||
vm.dimensions.left = vm.center.left * vm.dimensions.width - 10;
|
||||
vm.dimensions.top = vm.center.top * vm.dimensions.height - 10;
|
||||
}
|
||||
return vm.dimensions.width;
|
||||
};
|
||||
|
||||
/**
|
||||
* based on the offset selected calculates the "center" value and calls the callback
|
||||
* @param {any} offsetX
|
||||
* @param {any} offsetY
|
||||
*/
|
||||
function calculateGravity(offsetX, offsetY) {
|
||||
|
||||
vm.onValueChanged({
|
||||
left: (offsetX + 10) / vm.dimensions.width,
|
||||
top: (offsetY + 10) / vm.dimensions.height
|
||||
});
|
||||
|
||||
//vm.center.left = (offsetX + 10) / scope.dimensions.width;
|
||||
//vm.center.top = (offsetY + 10) / scope.dimensions.height;
|
||||
};
|
||||
|
||||
var lazyEndEvent = _.debounce(function () {
|
||||
$scope.$apply(function () {
|
||||
$scope.$emit("imageFocalPointStop");
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
}
|
||||
|
||||
var umbImageGravityComponent = {
|
||||
templateUrl: 'views/components/imaging/umb-image-gravity.html',
|
||||
bindings: {
|
||||
src: "<",
|
||||
center: "<",
|
||||
onImageLoaded: "&?",
|
||||
onValueChanged: "&"
|
||||
},
|
||||
controllerAs: 'vm',
|
||||
controller: umbImageGravityController
|
||||
};
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.component('umbImageGravity', umbImageGravityComponent);
|
||||
|
||||
})();
|
||||
|
||||
+21
-20
@@ -1,31 +1,32 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function OverlayBackdropDirective(overlayHelper) {
|
||||
function OverlayBackdropDirective(overlayHelper) {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
scope.numberOfOverlays = 0;
|
||||
scope.numberOfOverlays = 0;
|
||||
|
||||
scope.$watch(function(){
|
||||
return overlayHelper.getNumberOfOverlays();
|
||||
}, function (newValue) {
|
||||
scope.numberOfOverlays = newValue;
|
||||
});
|
||||
//TODO: this shouldn't be a watch, this should be based on an event handler
|
||||
scope.$watch(function () {
|
||||
return overlayHelper.getNumberOfOverlays();
|
||||
}, function (newValue) {
|
||||
scope.numberOfOverlays = newValue;
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/overlays/umb-overlay-backdrop.html',
|
||||
link: link
|
||||
};
|
||||
var directive = {
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/overlays/umb-overlay-backdrop.html',
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
return directive;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbOverlayBackdrop', OverlayBackdropDirective);
|
||||
angular.module('umbraco.directives').directive('umbOverlayBackdrop', OverlayBackdropDirective);
|
||||
|
||||
})();
|
||||
|
||||
@@ -350,6 +350,9 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
|
||||
if (args.cacheKey) {
|
||||
$scope.cachekey = args.cacheKey;
|
||||
}
|
||||
if (args.customTreeParams) {
|
||||
$scope.customtreeparams = args.customTreeParams;
|
||||
}
|
||||
}
|
||||
|
||||
//load the tree
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@
|
||||
/**
|
||||
* ACE editor session.
|
||||
* @type object
|
||||
* @see [EditSession]{@link http://ace.c9.io/#nav=api&api=edit_session}
|
||||
* @see [EditSession]{@link https://ace.c9.io/#nav=api&api=edit_session}
|
||||
*/
|
||||
var session = acee.getSession();
|
||||
|
||||
|
||||
-2
@@ -29,9 +29,7 @@ Use this directive to generate color swatches to pick from.
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
scope.setColor = function (color) {
|
||||
//scope.selectedColor({color: color });
|
||||
scope.selectedColor = color;
|
||||
|
||||
if (scope.onSelect) {
|
||||
scope.onSelect(color);
|
||||
}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
@description
|
||||
<b>Added in Umbraco version 7.6</b>
|
||||
This directive is a wrapper of the bootstrap datetime picker version 3.1.3. Use it to render a date time picker.
|
||||
For extra details about options and events take a look here: http://eonasdan.github.io/bootstrap-datetimepicker/
|
||||
For extra details about options and events take a look here: https://eonasdan.github.io/bootstrap-datetimepicker/
|
||||
|
||||
Use this directive to render a date time picker
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
transclude: true,
|
||||
templateUrl: 'views/components/umb-dropdown-item.html',
|
||||
templateUrl: 'views/components/umb-dropdown-item.html'
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
+47
-77
@@ -57,7 +57,7 @@
|
||||
},
|
||||
stop: function(e, ui) {
|
||||
updateTabsSortOrder();
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
scope.sortableOptionsProperty = {
|
||||
@@ -245,51 +245,21 @@
|
||||
scope.openCompositionsDialog = function() {
|
||||
|
||||
scope.compositionsDialogModel = {
|
||||
title: "Compositions",
|
||||
contentType: scope.model,
|
||||
compositeContentTypes: scope.model.compositeContentTypes,
|
||||
view: "views/common/overlays/contenttypeeditor/compositions/compositions.html",
|
||||
confirmSubmit: {
|
||||
title: "Warning",
|
||||
description: "Removing a composition will delete all the associated property data. Once you save the document type there's no way back, are you sure?",
|
||||
checkboxLabel: "I know what I'm doing",
|
||||
enable: true
|
||||
},
|
||||
submit: function(model, oldModel, confirmed) {
|
||||
view: "views/common/infiniteeditors/compositions/compositions.html",
|
||||
size: "small",
|
||||
submit: function() {
|
||||
|
||||
// make sure that all tabs has an init property
|
||||
if (scope.model.groups.length !== 0) {
|
||||
angular.forEach(scope.model.groups, function(group) {
|
||||
addInitProperty(group);
|
||||
});
|
||||
}
|
||||
|
||||
var compositionRemoved = false;
|
||||
|
||||
// check if any compositions has been removed
|
||||
for(var i = 0; oldModel.compositeContentTypes.length > i; i++) {
|
||||
|
||||
var oldComposition = oldModel.compositeContentTypes[i];
|
||||
|
||||
if(_.contains(model.compositeContentTypes, oldComposition) === false) {
|
||||
compositionRemoved = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// show overlay confirm box if compositions has been removed.
|
||||
if(compositionRemoved && confirmed === false) {
|
||||
|
||||
scope.compositionsDialogModel.confirmSubmit.show = true;
|
||||
|
||||
// submit overlay if no compositions has been removed
|
||||
// or the action has been confirmed
|
||||
} else {
|
||||
|
||||
// make sure that all tabs has an init property
|
||||
if (scope.model.groups.length !== 0) {
|
||||
angular.forEach(scope.model.groups, function(group) {
|
||||
addInitProperty(group);
|
||||
});
|
||||
}
|
||||
|
||||
// remove overlay
|
||||
scope.compositionsDialogModel.show = false;
|
||||
scope.compositionsDialogModel = null;
|
||||
}
|
||||
// remove overlay
|
||||
editorService.close();
|
||||
|
||||
},
|
||||
close: function(oldModel) {
|
||||
@@ -299,8 +269,7 @@
|
||||
scope.model.compositeContentTypes = oldModel.contentType.compositeContentTypes;
|
||||
|
||||
// remove overlay
|
||||
scope.compositionsDialogModel.show = false;
|
||||
scope.compositionsDialogModel = null;
|
||||
editorService.close();
|
||||
|
||||
},
|
||||
selectCompositeContentType: function (selectedContentType) {
|
||||
@@ -348,39 +317,40 @@
|
||||
|
||||
}
|
||||
};
|
||||
//select which resource methods to use, eg document Type or Media Type versions
|
||||
var availableContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes;
|
||||
var whereUsedContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getWhereCompositionIsUsedInContentTypes : mediaTypeResource.getWhereCompositionIsUsedInContentTypes;
|
||||
var countContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getCount : mediaTypeResource.getCount;
|
||||
|
||||
//get the currently assigned property type aliases - ensure we pass these to the server side filer
|
||||
var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function(g) {
|
||||
return _.map(g.properties, function(p) {
|
||||
return p.alias;
|
||||
});
|
||||
})), function(f) {
|
||||
return f !== null && f !== undefined;
|
||||
});
|
||||
$q.all([
|
||||
//get available composite types
|
||||
availableContentTypeResource(scope.model.id, [], propAliasesExisting).then(function (result) {
|
||||
setupAvailableContentTypesModel(result);
|
||||
}),
|
||||
//get where used document types
|
||||
whereUsedContentTypeResource(scope.model.id).then(function (whereUsed) {
|
||||
//pass to the dialog model the content type eg documentType or mediaType
|
||||
scope.compositionsDialogModel.section = scope.contentType;
|
||||
//pass the list of 'where used' document types
|
||||
scope.compositionsDialogModel.whereCompositionUsed = whereUsed;
|
||||
}),
|
||||
//get content type count
|
||||
countContentTypeResource().then(function(result) {
|
||||
scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10);
|
||||
})
|
||||
]).then(function() {
|
||||
//resolves when both other promises are done, now show it
|
||||
scope.compositionsDialogModel.show = true;
|
||||
});
|
||||
//select which resource methods to use, eg document Type or Media Type versions
|
||||
var availableContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getAvailableCompositeContentTypes : mediaTypeResource.getAvailableCompositeContentTypes;
|
||||
var whereUsedContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getWhereCompositionIsUsedInContentTypes : mediaTypeResource.getWhereCompositionIsUsedInContentTypes;
|
||||
var countContentTypeResource = scope.contentType === "documentType" ? contentTypeResource.getCount : mediaTypeResource.getCount;
|
||||
|
||||
//get the currently assigned property type aliases - ensure we pass these to the server side filer
|
||||
var propAliasesExisting = _.filter(_.flatten(_.map(scope.model.groups, function(g) {
|
||||
return _.map(g.properties, function(p) {
|
||||
return p.alias;
|
||||
});
|
||||
})), function(f) {
|
||||
return f !== null && f !== undefined;
|
||||
});
|
||||
$q.all([
|
||||
//get available composite types
|
||||
availableContentTypeResource(scope.model.id, [], propAliasesExisting).then(function (result) {
|
||||
setupAvailableContentTypesModel(result);
|
||||
}),
|
||||
//get where used document types
|
||||
whereUsedContentTypeResource(scope.model.id).then(function (whereUsed) {
|
||||
//pass to the dialog model the content type eg documentType or mediaType
|
||||
scope.compositionsDialogModel.section = scope.contentType;
|
||||
//pass the list of 'where used' document types
|
||||
scope.compositionsDialogModel.whereCompositionUsed = whereUsed;
|
||||
}),
|
||||
//get content type count
|
||||
countContentTypeResource().then(function(result) {
|
||||
scope.compositionsDialogModel.totalContentTypes = parseInt(result, 10);
|
||||
})
|
||||
]).then(function() {
|
||||
//resolves when both other promises are done, now show it
|
||||
editorService.open(scope.compositionsDialogModel);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
|
||||
-1
@@ -4,7 +4,6 @@
|
||||
* @restrict E
|
||||
* @function
|
||||
* @description
|
||||
* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form.
|
||||
**/
|
||||
|
||||
/*
|
||||
|
||||
+305
@@ -0,0 +1,305 @@
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* A component to manage file uploads for content properties
|
||||
* @param {any} $scope
|
||||
* @param {any} fileManager
|
||||
* @param {any} mediaHelper
|
||||
* @param {any} angularHelper
|
||||
*/
|
||||
function umbPropertyFileUploadController($scope, $q, fileManager, mediaHelper, angularHelper) {
|
||||
|
||||
//NOTE: this component supports multiple files, though currently the uploader does not but perhaps sometime in the future
|
||||
// we'd want it to, so i'll leave the multiple file support in place
|
||||
|
||||
var vm = this;
|
||||
|
||||
vm.$onInit = onInit;
|
||||
vm.$onChanges = onChanges;
|
||||
vm.$postLink = postLink;
|
||||
vm.clear = clearFiles;
|
||||
|
||||
/** Clears the file collections when content is saving (if we need to clear) or after saved */
|
||||
function clearFiles() {
|
||||
//clear the files collection (we don't want to upload any!)
|
||||
fileManager.setFiles({
|
||||
propertyAlias: vm.propertyAlias,
|
||||
culture: vm.culture,
|
||||
files: []
|
||||
});
|
||||
//clear the current files
|
||||
vm.files = [];
|
||||
|
||||
//notify the callback
|
||||
notifyFilesSelected(null);
|
||||
notifyFilesChanged(null);
|
||||
}
|
||||
|
||||
function notifyFilesSelected(val, files) {
|
||||
|
||||
if (!val) {
|
||||
val = null;
|
||||
}
|
||||
if (!files) {
|
||||
files = null;
|
||||
}
|
||||
|
||||
//notify the callback
|
||||
vm.onFilesSelected({ value: val, files: files });
|
||||
|
||||
//need to explicity setDirty here to track changes
|
||||
vm.fileUploadForm.$setDirty();
|
||||
}
|
||||
|
||||
function notifyFilesChanged(files) {
|
||||
if (!files) {
|
||||
files = null;
|
||||
}
|
||||
|
||||
//notify the callback
|
||||
vm.onFilesChanged({ files: files });
|
||||
}
|
||||
|
||||
function notifyInit(val, files) {
|
||||
if (!val) {
|
||||
val = null;
|
||||
}
|
||||
if (!files) {
|
||||
files = null;
|
||||
}
|
||||
|
||||
if (vm.onInit) {
|
||||
vm.onInit({ value: val, files: files });
|
||||
}
|
||||
}
|
||||
|
||||
/** Called when the component initializes */
|
||||
function onInit() {
|
||||
$scope.$on("filesSelected", onFilesSelected);
|
||||
initialize();
|
||||
}
|
||||
|
||||
/** Called when the component has linked all elements, this is when the form controller is available */
|
||||
function postLink() {
|
||||
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
//normalize culture to null if it's not there
|
||||
if (!vm.culture) {
|
||||
vm.culture = null;
|
||||
}
|
||||
|
||||
//TODO: need to figure out what we can do for things like Nested Content
|
||||
|
||||
var existingClientFiles = checkPendingClientFiles();
|
||||
//create the property to show the list of files currently saved
|
||||
if (existingClientFiles.length > 0) {
|
||||
updateModelFromSelectedFiles(existingClientFiles).then(function (newVal) {
|
||||
//notify the callback
|
||||
notifyInit(newVal, vm.files);
|
||||
});
|
||||
}
|
||||
else if (vm.value) {
|
||||
|
||||
var files = vm.value.split(",");
|
||||
|
||||
vm.files = _.map(files, function (file) {
|
||||
var f = {
|
||||
fileName: file,
|
||||
isImage: mediaHelper.detectIfImageByExtension(file),
|
||||
extension: getExtension(file)
|
||||
};
|
||||
f.fileSrc = getThumbnail(f);
|
||||
return f;
|
||||
});
|
||||
|
||||
//notify the callback
|
||||
notifyInit();
|
||||
}
|
||||
else {
|
||||
vm.files = [];
|
||||
|
||||
//notify the callback
|
||||
notifyInit();
|
||||
}
|
||||
}
|
||||
|
||||
function checkPendingClientFiles() {
|
||||
|
||||
//normalize culture to null if it's not there
|
||||
if (!vm.culture) {
|
||||
vm.culture = null;
|
||||
}
|
||||
|
||||
//check the file manager to see if there's already local files pending for this editor
|
||||
var existingClientFiles = _.map(
|
||||
_.filter(fileManager.getFiles(),
|
||||
function (f) {
|
||||
return f.alias === vm.propertyAlias && f.culture === vm.culture;
|
||||
}),
|
||||
function (f) {
|
||||
return f.file;
|
||||
});
|
||||
return existingClientFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* Watch for model changes
|
||||
* @param {any} changes
|
||||
*/
|
||||
function onChanges(changes) {
|
||||
|
||||
if (changes.value && !changes.value.isFirstChange() && changes.value.currentValue !== changes.value.previousValue) {
|
||||
|
||||
if (!changes.value.currentValue && changes.value.previousValue) {
|
||||
//if the value has been cleared, clear the files (ignore if the previous value is also falsy)
|
||||
vm.files = [];
|
||||
}
|
||||
else if (changes.value.currentValue && !changes.value.previousValue && vm.files.length === 0) {
|
||||
//if a new value has been added after being cleared
|
||||
|
||||
var existingClientFiles = checkPendingClientFiles();
|
||||
//create the property to show the list of files currently saved
|
||||
if (existingClientFiles.length > 0) {
|
||||
updateModelFromSelectedFiles(existingClientFiles).then(function () {
|
||||
//raise this event which means the files have changed but this wasn't the instance that
|
||||
//added the file
|
||||
notifyFilesChanged(vm.files);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function getThumbnail(file) {
|
||||
if (!file.isImage) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var thumbnailUrl = mediaHelper.getThumbnailFromPath(file.fileName);
|
||||
|
||||
return thumbnailUrl;
|
||||
}
|
||||
|
||||
function getExtension(fileName) {
|
||||
var extension = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length);
|
||||
return extension.toLowerCase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the vm.files model from the selected files and returns a promise containing the csv of all file names selected
|
||||
* @param {any} files
|
||||
*/
|
||||
function updateModelFromSelectedFiles(files) {
|
||||
|
||||
//we return a promise because the FileReader api is async
|
||||
var promises = [];
|
||||
|
||||
//clear the current files
|
||||
vm.files = [];
|
||||
var newVal = "";
|
||||
|
||||
var reader = new FileReader();
|
||||
|
||||
//for each file load in the contents from the file reader and set it as an fileSrc
|
||||
//property of the vm.files array item
|
||||
var fileCount = files.length;
|
||||
for (var i = 0; i < fileCount; i++) {
|
||||
var index = i; //capture
|
||||
|
||||
var isImage = mediaHelper.detectIfImageByExtension(files[i].name);
|
||||
|
||||
//save the file object to the files collection
|
||||
vm.files.push({
|
||||
isImage: isImage,
|
||||
extension: getExtension(files[i].name),
|
||||
fileName: files[i].name,
|
||||
isClientSide: true
|
||||
});
|
||||
|
||||
newVal += files[i].name + ",";
|
||||
|
||||
if (isImage) {
|
||||
|
||||
var deferred = $q.defer();
|
||||
|
||||
reader.onload = function(e) {
|
||||
vm.files[index].fileSrc = e.target.result;
|
||||
deferred.resolve(newVal);
|
||||
};
|
||||
promises.push(deferred.promise);
|
||||
reader.readAsDataURL(files[i]);
|
||||
}
|
||||
else {
|
||||
promises.push($q.when(newVal));
|
||||
}
|
||||
}
|
||||
|
||||
return $q.all(promises).then(function (p) {
|
||||
//return the last value in the list of promises which will be the final value
|
||||
return $q.when(p[p.length - 1]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* listen for when a file is selected
|
||||
* @param {any} event
|
||||
* @param {any} args
|
||||
*/
|
||||
function onFilesSelected(event, args) {
|
||||
|
||||
if (args.files && args.files.length > 0) {
|
||||
|
||||
//set the files collection
|
||||
fileManager.setFiles({
|
||||
propertyAlias: vm.propertyAlias,
|
||||
files: args.files,
|
||||
culture: vm.culture
|
||||
});
|
||||
|
||||
updateModelFromSelectedFiles(args.files).then(function(newVal) {
|
||||
angularHelper.safeApply($scope,
|
||||
function() {
|
||||
//pass in the file names and the model files
|
||||
notifyFilesSelected(newVal, vm.files);
|
||||
notifyFilesChanged(vm.files);
|
||||
});
|
||||
});
|
||||
}
|
||||
else {
|
||||
angularHelper.safeApply($scope);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var umbPropertyFileUploadComponent = {
|
||||
templateUrl: 'views/components/upload/umb-property-file-upload.html',
|
||||
bindings: {
|
||||
culture: "@?",
|
||||
propertyAlias: "@",
|
||||
value: "<",
|
||||
hideSelection: "<",
|
||||
/**
|
||||
* Called when a file is selected on this instance
|
||||
*/
|
||||
onFilesSelected: "&",
|
||||
/**
|
||||
* Called when the file collection changes (i.e. a new file has been selected but maybe it wasn't this instance that caused the change)
|
||||
*/
|
||||
onFilesChanged: "&",
|
||||
onInit: "&"
|
||||
},
|
||||
transclude: true,
|
||||
controllerAs: 'vm',
|
||||
controller: umbPropertyFileUploadController
|
||||
};
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.component('umbPropertyFileUpload', umbPropertyFileUploadComponent);
|
||||
|
||||
})();
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:autoScale
|
||||
* @element div
|
||||
* @function
|
||||
* @description
|
||||
* Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set
|
||||
* So if you only want to scale the div to 70 pixels from the bottom you pass "70"
|
||||
|
||||
* @example
|
||||
* <example module="umbraco.directives">
|
||||
* <file name="index.html">
|
||||
* <div auto-scale="70" class="input-block-level"></div>
|
||||
* </file>
|
||||
* </example>
|
||||
**/
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.directive('autoScale', function ($window, $timeout, windowResizeListener) {
|
||||
return function (scope, el, attrs) {
|
||||
|
||||
var totalOffset = 0;
|
||||
var offsety = parseInt(attrs.autoScale, 10);
|
||||
var window = angular.element($window);
|
||||
if (offsety !== undefined) {
|
||||
totalOffset += offsety;
|
||||
}
|
||||
|
||||
$timeout(function () {
|
||||
setElementSize();
|
||||
});
|
||||
|
||||
function setElementSize() {
|
||||
el.height(window.height() - (el.offset().top + totalOffset));
|
||||
}
|
||||
|
||||
var resizeCallback = function() {
|
||||
setElementSize();
|
||||
};
|
||||
|
||||
windowResizeListener.register(resizeCallback);
|
||||
|
||||
//ensure to unregister from all events and kill jquery plugins
|
||||
scope.$on('$destroy', function () {
|
||||
windowResizeListener.unregister(resizeCallback);
|
||||
});
|
||||
|
||||
};
|
||||
});
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Konami Code directive for AngularJS
|
||||
* @version v0.0.1
|
||||
* @license MIT License, http://www.opensource.org/licenses/MIT
|
||||
* @license MIT License, https://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
angular.module('umbraco.directives')
|
||||
@@ -59,4 +59,4 @@ angular.module('umbraco.directives')
|
||||
scope.$on('$destroy', stopListening);
|
||||
}
|
||||
};
|
||||
}]);
|
||||
}]);
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:noDirtyCheck
|
||||
* @restrict A
|
||||
* @description Can be attached to form inputs to prevent them from setting the form as dirty (http://stackoverflow.com/questions/17089090/prevent-input-from-setting-form-dirty-angularjs)
|
||||
* @description Can be attached to form inputs to prevent them from setting the form as dirty (https://stackoverflow.com/questions/17089090/prevent-input-from-setting-form-dirty-angularjs)
|
||||
**/
|
||||
function noDirtyCheck() {
|
||||
return {
|
||||
@@ -20,4 +20,4 @@ function noDirtyCheck() {
|
||||
}
|
||||
};
|
||||
}
|
||||
angular.module('umbraco.directives.validation').directive("noDirtyCheck", noDirtyCheck);
|
||||
angular.module('umbraco.directives.validation').directive("noDirtyCheck", noDirtyCheck);
|
||||
|
||||
+9
-1
@@ -8,7 +8,14 @@
|
||||
|
||||
link: function (scope, element, attr, ctrl) {
|
||||
|
||||
element.hide();
|
||||
//We can either get the form submitted status by the parent directive valFormManager (if we add a property to it)
|
||||
//or we can just check upwards in the DOM for the css class (easier for now).
|
||||
//The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot
|
||||
//reset the status.
|
||||
var submitted = element.closest(".show-validation").length > 0;
|
||||
if (!submitted) {
|
||||
element.hide();
|
||||
}
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
@@ -20,6 +27,7 @@
|
||||
element.hide();
|
||||
}));
|
||||
|
||||
//no isolate scope to listen to element destroy
|
||||
element.bind('$destroy', function () {
|
||||
for (var u in unsubscribe) {
|
||||
unsubscribe[u]();
|
||||
|
||||
+16
-10
@@ -3,21 +3,27 @@
|
||||
|
||||
function SetDirtyOnChange() {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
if(attr.ngModel) {
|
||||
scope.$watch(attr.ngModel, function(newValue, oldValue) {
|
||||
if (!newValue) {return;}
|
||||
if (newValue === oldValue) {return;}
|
||||
ctrl.$setDirty();
|
||||
}, true);
|
||||
function link(scope, el, attr, ctrls) {
|
||||
|
||||
var formCtrl = ctrls[0];
|
||||
|
||||
if (ctrls.length > 1) {
|
||||
//if an ngModel is supplied, assign a render function which is called when the model is changed
|
||||
var modelCtrl = ctrls[1];
|
||||
var oldRender = modelCtrl.$render;
|
||||
modelCtrl.$render = function () {
|
||||
formCtrl.$setDirty();
|
||||
//call any previously set render method
|
||||
if (oldRender) {
|
||||
oldRender();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
var initValue = attr.umbSetDirtyOnChange;
|
||||
|
||||
attr.$observe("umbSetDirtyOnChange", function (newValue) {
|
||||
if(newValue !== initValue) {
|
||||
ctrl.$setDirty();
|
||||
formCtrl.$setDirty();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -25,7 +31,7 @@
|
||||
}
|
||||
|
||||
var directive = {
|
||||
require: "^^form",
|
||||
require: ["^^form", "?ngModel"],
|
||||
restrict: 'A',
|
||||
link: link
|
||||
};
|
||||
|
||||
+21
-16
@@ -12,7 +12,7 @@
|
||||
* Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will
|
||||
* be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly.
|
||||
**/
|
||||
function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams) {
|
||||
function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService, eventsService, $routeParams, navigationService) {
|
||||
|
||||
var SHOW_VALIDATION_CLASS_NAME = "show-validation";
|
||||
var SAVING_EVENT_NAME = "formSubmitting";
|
||||
@@ -111,22 +111,27 @@ function valFormManager(serverValidationManager, $rootScope, $log, $timeout, not
|
||||
return;
|
||||
}
|
||||
|
||||
var path = nextLocation.split("#")[1];
|
||||
if (path) {
|
||||
if (path.indexOf("%253") || path.indexOf("%252")) {
|
||||
path = decodeURIComponent(path);
|
||||
var nextPath = nextLocation.split("#")[1];
|
||||
|
||||
if (nextPath) {
|
||||
|
||||
if (navigationService.isRouteChangingNavigation(currentLocation, nextLocation)) {
|
||||
if (!notificationsService.hasView()) {
|
||||
|
||||
if (nextPath.indexOf("%253") || nextPath.indexOf("%252")) {
|
||||
nextPath = decodeURIComponent(nextPath);
|
||||
}
|
||||
|
||||
var msg = { view: "confirmroutechange", args: { path: nextPath, listener: locationEvent } };
|
||||
notificationsService.add(msg);
|
||||
}
|
||||
|
||||
//prevent the route!
|
||||
event.preventDefault();
|
||||
|
||||
//raise an event
|
||||
eventsService.emit("valFormManager.pendingChanges", true);
|
||||
}
|
||||
|
||||
if (!notificationsService.hasView()) {
|
||||
var msg = { view: "confirmroutechange", args: { path: path, listener: locationEvent } };
|
||||
notificationsService.add(msg);
|
||||
}
|
||||
|
||||
//prevent the route!
|
||||
event.preventDefault();
|
||||
|
||||
//raise an event
|
||||
eventsService.emit("valFormManager.pendingChanges", true);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
+79
-67
@@ -12,37 +12,38 @@
|
||||
function valPropertyMsg(serverValidationManager) {
|
||||
|
||||
return {
|
||||
scope: {
|
||||
property: "="
|
||||
},
|
||||
require: ['^^form', '^^valFormManager'],
|
||||
require: ['^^form', '^^valFormManager', '^^umbProperty'],
|
||||
replace: true,
|
||||
restrict: "E",
|
||||
template: "<div ng-show=\"errorMsg != ''\" class='alert alert-error property-error' >{{errorMsg}}</div>",
|
||||
|
||||
scope: {},
|
||||
link: function (scope, element, attrs, ctrl) {
|
||||
|
||||
//the property form controller api
|
||||
var formCtrl = ctrl[0];
|
||||
|
||||
//the valFormManager controller api
|
||||
var valFormManager = ctrl[1];
|
||||
//the property controller api
|
||||
var umbPropCtrl = ctrl[2];
|
||||
|
||||
scope.currentProperty = umbPropCtrl.property;
|
||||
var currentCulture = scope.currentProperty.culture;
|
||||
|
||||
var watcher = null;
|
||||
|
||||
// Gets the error message to display
|
||||
function getErrorMsg() {
|
||||
//this can be null if no property was assigned
|
||||
if (scope.property) {
|
||||
if (scope.currentProperty) {
|
||||
//first try to get the error msg from the server collection
|
||||
var err = serverValidationManager.getPropertyError(scope.property.alias, "");
|
||||
var err = serverValidationManager.getPropertyError(scope.currentProperty.alias, null, "");
|
||||
//if there's an error message use it
|
||||
if (err && err.errorMsg) {
|
||||
return err.errorMsg;
|
||||
}
|
||||
else {
|
||||
//TODO: localize
|
||||
return scope.property.propertyErrorMessage ? scope.property.propertyErrorMessage : "Property has errors";
|
||||
return scope.currentProperty.propertyErrorMessage ? scope.currentProperty.propertyErrorMessage : "Property has errors";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -59,29 +60,30 @@ function valPropertyMsg(serverValidationManager) {
|
||||
function startWatch() {
|
||||
//if there's not already a watch
|
||||
if (!watcher) {
|
||||
watcher = scope.$watch("property.value", function (newValue, oldValue) {
|
||||
|
||||
if (!newValue || angular.equals(newValue, oldValue)) {
|
||||
return;
|
||||
}
|
||||
watcher = scope.$watch("currentProperty.value",
|
||||
function (newValue, oldValue) {
|
||||
|
||||
var errCount = 0;
|
||||
for (var e in formCtrl.$error) {
|
||||
if (angular.isArray(formCtrl.$error[e])) {
|
||||
errCount++;
|
||||
if (!newValue || angular.equals(newValue, oldValue)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//we are explicitly checking for valServer errors here, since we shouldn't auto clear
|
||||
// based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
|
||||
// is the only one, then we'll clear.
|
||||
var errCount = 0;
|
||||
for (var e in formCtrl.$error) {
|
||||
if (angular.isArray(formCtrl.$error[e])) {
|
||||
errCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
|
||||
scope.errorMsg = "";
|
||||
formCtrl.$setValidity('valPropertyMsg', true);
|
||||
stopWatch();
|
||||
}
|
||||
}, true);
|
||||
//we are explicitly checking for valServer errors here, since we shouldn't auto clear
|
||||
// based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg
|
||||
// is the only one, then we'll clear.
|
||||
|
||||
if ((errCount === 1 && angular.isArray(formCtrl.$error.valPropertyMsg)) || (formCtrl.$invalid && angular.isArray(formCtrl.$error.valServer))) {
|
||||
scope.errorMsg = "";
|
||||
formCtrl.$setValidity('valPropertyMsg', true);
|
||||
stopWatch();
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,19 +95,7 @@ function valPropertyMsg(serverValidationManager) {
|
||||
}
|
||||
}
|
||||
|
||||
//if there's any remaining errors in the server validation service then we should show them.
|
||||
var showValidation = serverValidationManager.items.length > 0;
|
||||
var hasError = false;
|
||||
|
||||
//create properties on our custom scope so we can use it in our template
|
||||
scope.errorMsg = "";
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
//listen for form validation changes.
|
||||
//The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then
|
||||
// subscribing to this single watch.
|
||||
valFormManager.onValidationStatusChanged(function (evt, args) {
|
||||
function checkValidationStatus() {
|
||||
if (formCtrl.$invalid) {
|
||||
//first we need to check if the valPropertyMsg validity is invalid
|
||||
if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) {
|
||||
@@ -130,10 +120,35 @@ function valPropertyMsg(serverValidationManager) {
|
||||
hasError = false;
|
||||
scope.errorMsg = "";
|
||||
}
|
||||
}
|
||||
|
||||
//if there's any remaining errors in the server validation service then we should show them.
|
||||
var showValidation = serverValidationManager.items.length > 0;
|
||||
if (!showValidation) {
|
||||
//We can either get the form submitted status by the parent directive valFormManager (if we add a property to it)
|
||||
//or we can just check upwards in the DOM for the css class (easier for now).
|
||||
//The initial hidden state can't always be hidden because when we switch variants in the content editor we cannot
|
||||
//reset the status.
|
||||
showValidation = element.closest(".show-validation").length > 0;
|
||||
}
|
||||
|
||||
|
||||
var hasError = false;
|
||||
|
||||
//create properties on our custom scope so we can use it in our template
|
||||
scope.errorMsg = "";
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
//listen for form validation changes.
|
||||
//The alternative is to add a watch to formCtrl.$invalid but that would lead to many more watches then
|
||||
// subscribing to this single watch.
|
||||
valFormManager.onValidationStatusChanged(function (evt, args) {
|
||||
checkValidationStatus();
|
||||
});
|
||||
|
||||
//listen for the forms saving event
|
||||
unsubscribe.push(scope.$on("formSubmitting", function(ev, args) {
|
||||
unsubscribe.push(scope.$on("formSubmitting", function (ev, args) {
|
||||
showValidation = true;
|
||||
if (hasError && scope.errorMsg === "") {
|
||||
scope.errorMsg = getErrorMsg();
|
||||
@@ -145,7 +160,7 @@ function valPropertyMsg(serverValidationManager) {
|
||||
}));
|
||||
|
||||
//listen for the forms saved event
|
||||
unsubscribe.push(scope.$on("formSubmitted", function(ev, args) {
|
||||
unsubscribe.push(scope.$on("formSubmitted", function (ev, args) {
|
||||
showValidation = false;
|
||||
scope.errorMsg = "";
|
||||
formCtrl.$setValidity('valPropertyMsg', true);
|
||||
@@ -160,35 +175,32 @@ function valPropertyMsg(serverValidationManager) {
|
||||
// indicate that a content property is invalid at the property level since developers may not actually implement
|
||||
// the correct field validation in their property editors.
|
||||
|
||||
if (scope.property) { //this can be null if no property was assigned
|
||||
serverValidationManager.subscribe(scope.property.alias, "", function (isValid, propertyErrors, allErrors) {
|
||||
hasError = !isValid;
|
||||
if (hasError) {
|
||||
//set the error message to the server message
|
||||
scope.errorMsg = propertyErrors[0].errorMsg;
|
||||
//flag that the current validator is invalid
|
||||
formCtrl.$setValidity('valPropertyMsg', false);
|
||||
startWatch();
|
||||
}
|
||||
else {
|
||||
scope.errorMsg = "";
|
||||
//flag that the current validator is valid
|
||||
formCtrl.$setValidity('valPropertyMsg', true);
|
||||
stopWatch();
|
||||
}
|
||||
});
|
||||
if (scope.currentProperty) { //this can be null if no property was assigned
|
||||
unsubscribe.push(serverValidationManager.subscribe(scope.currentProperty.alias,
|
||||
currentCulture,
|
||||
"",
|
||||
function(isValid, propertyErrors, allErrors) {
|
||||
hasError = !isValid;
|
||||
if (hasError) {
|
||||
//set the error message to the server message
|
||||
scope.errorMsg = propertyErrors[0].errorMsg;
|
||||
//flag that the current validator is invalid
|
||||
formCtrl.$setValidity('valPropertyMsg', false);
|
||||
startWatch();
|
||||
}
|
||||
else {
|
||||
scope.errorMsg = "";
|
||||
//flag that the current validator is valid
|
||||
formCtrl.$setValidity('valPropertyMsg', true);
|
||||
stopWatch();
|
||||
}
|
||||
}));
|
||||
|
||||
//when the element is disposed we need to unsubscribe!
|
||||
// NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain
|
||||
// but they are a different callback instance than the above.
|
||||
element.bind('$destroy', function () {
|
||||
stopWatch();
|
||||
serverValidationManager.unsubscribe(scope.property.alias, "");
|
||||
});
|
||||
}
|
||||
|
||||
//when the scope is disposed we need to unsubscribe
|
||||
scope.$on('$destroy', function () {
|
||||
stopWatch();
|
||||
for (var u in unsubscribe) {
|
||||
unsubscribe[u]();
|
||||
}
|
||||
|
||||
+5
-4
@@ -33,11 +33,9 @@ function valPropertyValidator(serverValidationManager) {
|
||||
if (!scope.valPropertyValidator || !angular.isFunction(scope.valPropertyValidator)) {
|
||||
throw new Error('val-property-validator directive must specify a function to call');
|
||||
}
|
||||
|
||||
var initResult = scope.valPropertyValidator();
|
||||
|
||||
|
||||
// Validation method
|
||||
var validate = function (viewValue) {
|
||||
function validate (viewValue) {
|
||||
// Calls the validition method
|
||||
var result = scope.valPropertyValidator();
|
||||
if (!result.errorKey || result.isValid === undefined || !result.errorMsg) {
|
||||
@@ -62,6 +60,9 @@ function valPropertyValidator(serverValidationManager) {
|
||||
// Parsers are called as soon as the value in the form input is modified
|
||||
modelCtrl.$parsers.push(validate);
|
||||
|
||||
//call on init
|
||||
validate();
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user