diff --git a/.github/V8_GETTING_STARTED.md b/.github/V8_GETTING_STARTED.md index def923e0d0..62b376b0e7 100644 --- a/.github/V8_GETTING_STARTED.md +++ b/.github/V8_GETTING_STARTED.md @@ -23,7 +23,7 @@ We recommend running the site with the Visual Studio since you'll be able to rem ### Making code changes -* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/docs/CONTRIBUTING.md)_ +* _[The process for making code changes in v8 is the same as v7](https://github.com/umbraco/Umbraco-CMS/blob/dev-v7/.github/CONTRIBUTING.md)_ * Any .NET changes you make you just need to compile * Any Angular/JS changes you make you will need to make sure you are running the Gulp build. Easiest way to do this is from within Visual Studio in the `Task Runner Explorer`. You can find this window by pressing `ctrl + q` and typing in `Task Runner Explorer`. In this window you'll see all Gulp tasks, double click on the `dev` task, this will compile the angular solution and start a file watcher, then any html/js changes you make are automatically built. * When making js changes, you should have the chrome developer tools open to ensure that cache is disabled diff --git a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs index 3d4b24d359..d5f6c2b8c4 100644 --- a/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs +++ b/src/Umbraco.Core/Manifest/ManifestContentAppDefinition.cs @@ -20,7 +20,8 @@ namespace Umbraco.Core.Manifest // show: [ // optional, default is always show // '-content/foo', // hide for content type 'foo' // '+content/*', // show for all other content types - // '+media/*' // show for all media types + // '+media/*', // show for all media types + // '+role/admin' // show for admin users. Role based permissions will override others. // ] // }, // ... diff --git a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs index 7f6eb72a25..a5ab62d25f 100644 --- a/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs +++ b/src/Umbraco.Core/Persistence/NPocoSqlExtensions.cs @@ -73,7 +73,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias = null) { - var (s, a) = sql.SqlContext.Visit(predicate, alias); + var (s, a) = sql.SqlContext.VisitDto(predicate, alias); return sql.Where(s, a); } @@ -89,7 +89,7 @@ namespace Umbraco.Core.Persistence /// The Sql statement. public static Sql Where(this Sql sql, Expression> predicate, string alias1 = null, string alias2 = null) { - var (s, a) = sql.SqlContext.Visit(predicate, alias1, alias2); + var (s, a) = sql.SqlContext.VisitDto(predicate, alias1, alias2); return sql.Where(s, a); } @@ -321,9 +321,9 @@ namespace Umbraco.Core.Persistence /// Appends an ORDER BY DESC clause to the Sql statement. /// /// The Sql statement. - /// Expression specifying the fields. + /// Fields. /// The Sql statement. - public static Sql OrderByDescending(this Sql sql, params object[] fields) + public static Sql OrderByDescending(this Sql sql, params string[] fields) { return sql.Append("ORDER BY " + string.Join(", ", fields.Select(x => x + " DESC"))); } @@ -664,7 +664,7 @@ namespace Umbraco.Core.Persistence /// Adds columns to a SELECT Sql statement. /// /// The origin sql. - /// Expression indicating the column to select. + /// Columns to select. /// The Sql statement. public static Sql AndSelect(this Sql sql, params string[] fields) { @@ -688,7 +688,6 @@ namespace Umbraco.Core.Persistence return sql.Append(", " + string.Join(", ", sql.GetColumns(columnExpressions: fields))); } - /// /// Adds columns to a SELECT Sql statement. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs index f7341d112b..217719e144 100644 --- a/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/IContentRepository.cs @@ -19,6 +19,12 @@ namespace Umbraco.Core.Persistence.Repositories /// Current version is first, and then versions are ordered with most recent first. IEnumerable GetAllVersions(int nodeId); + /// + /// Gets versions. + /// + /// Current version is first, and then versions are ordered with most recent first. + IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take); + /// /// Gets version identifiers. /// diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs index 41f0f16225..36b7ab07f1 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentRepositoryBase.cs @@ -53,6 +53,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // gets all versions, current first public abstract IEnumerable GetAllVersions(int nodeId); + // gets all versions, current first + public virtual IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take) + => GetAllVersions(nodeId).Skip(skip).Take(take); + // gets all version ids, current first public virtual IEnumerable GetVersionIds(int nodeId, int maxRows) { @@ -252,8 +256,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // if we do not do this then we end up with issues where we are ordering by a field that has duplicate values (i.e. the 'text' column // is empty for many nodes) - see: http://issues.umbraco.org/issue/U4-8831 - var dbfield = GetQuotedFieldName("umbracoNode", "id"); - (dbfield, _) = SqlContext.Visit(x => x.NodeId); // fixme?! + var (dbfield, _) = SqlContext.VisitDto(x => x.NodeId); if (ordering.IsCustomField || !ordering.OrderBy.InvariantEquals("id")) { psql.OrderBy(GetAliasedField(dbfield, sql)); // fixme why aliased? @@ -262,7 +265,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // create prepared sql // ensure it's single-line as NPoco PagingHelper has issues with multi-lines psql = Sql(psql.SQL.ToSingleLine(), psql.Arguments); - // replace the magic culture parameter (see DocumentRepository.GetBaseQuery()) if (!ordering.Culture.IsNullOrWhiteSpace()) @@ -353,6 +355,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (ordering.Culture.IsNullOrWhiteSpace()) return GetAliasedField(SqlSyntax.GetFieldName(x => x.Text), sql); + // "variantName" alias is defined in DocumentRepository.GetBaseQuery + // fixme - what if it is NOT a document but a ... media or whatever? + // previously, we inserted the join+select *here* so we were sure to have it, + // but now that's not the case anymore! return "variantName"; } @@ -433,7 +439,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // sort and filter sql = PreparePageSql(sql, filter, ordering); - // get a page of DTOs and the total count var pagedResult = Database.Page(pageIndex + 1, pageSize, sql); totalRecords = Convert.ToInt32(pagedResult.TotalItems); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs index 1659ca6427..f5de3dc1d7 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/DocumentRepository.cs @@ -95,6 +95,10 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return GetBaseQuery(queryType, true); } + // gets the COALESCE expression for variant/invariant name + private string VariantNameSqlExpression + => SqlContext.VisitDto((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql; + protected virtual Sql GetBaseQuery(QueryType queryType, bool current) { var sql = SqlContext.Sql(); @@ -116,7 +120,9 @@ namespace Umbraco.Core.Persistence.Repositories.Implement r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto)) .Select(documentDto => documentDto.PublishedVersionDto, "pdv", r1 => r1.Select(documentVersionDto => documentVersionDto.ContentVersionDto, "pcv"))) - .AndSelect(SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql + " AS variantName"); + + // select the variant name, coalesce to the invariant name, as "variantName" + .AndSelect(VariantNameSqlExpression + " AS variantName"); break; } @@ -135,18 +141,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement .LeftJoin(nested => nested.InnerJoin("pdv") .On((left, right) => left.Id == right.Id && right.Published, "pcv", "pdv"), "pcv") - .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv"); + .On((left, right) => left.NodeId == right.NodeId, aliasRight: "pcv") - //the magic [[[ISOCODE]]] will be replaced in ContentRepositoryBase.GetPage() by the current Iso code - sql + // left join on optional culture variation + //the magic "[[[ISOCODE]]]" parameter value will be replaced in ContentRepositoryBase.GetPage() by the actual ISO code .LeftJoin(nested => nested.InnerJoin("lang").On((ccv, lang) => ccv.LanguageId == lang.Id && lang.IsoCode == "[[[ISOCODE]]]", "ccv", "lang"), "ccv") - .On((version, ccv) => version.Id == ccv.VersionId, "pcv", "ccv"); + .On((version, ccv) => version.Id == ccv.VersionId, aliasRight: "ccv"); sql .Where(x => x.NodeObjectType == NodeObjectTypeId); - // this would ensure we don't get the published version - keep for reference //sql // .WhereAny( @@ -157,7 +162,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement if (current) sql.Where(x => x.Current); // always get the current version - return sql; } @@ -216,6 +220,16 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return MapDtosToContent(Database.Fetch(sql), true); } + public override IEnumerable GetAllVersionsSlim(int nodeId, int skip, int take) + { + var sql = GetBaseQuery(QueryType.Many, false) + .Where(x => x.NodeId == nodeId) + .OrderByDescending(x => x.Current) + .AndByDescending(x => x.VersionDate); + + return MapDtosToContent(Database.Fetch(sql), true, true); + } + public override IContent GetVersion(int versionId) { var sql = GetBaseQuery(QueryType.Single, false) @@ -246,7 +260,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + var content = (Content) entity; content.AddingEntity(); var publishing = content.PublishedState == PublishedState.Publishing; @@ -420,7 +434,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // however, it's not just so we have access to AddingEntity // there are tons of things at the end of the methods, that can only work with a true Content // and basically, the repository requires a Content, not an IContent - var content = (Content)entity; + var content = (Content) entity; // check if we need to make any database changes at all if ((content.PublishedState == PublishedState.Published || content.PublishedState == PublishedState.Unpublished) && !content.IsEntityDirty() && !content.IsAnyUserPropertyDirty()) @@ -707,30 +721,27 @@ namespace Umbraco.Core.Persistence.Repositories.Implement { Sql filterSql = null; - // Here we create a default where clause from a temp IContent which will look in the contentVersion table for the content name - // if we are searching in a list view that contains variants, we want to look in the contentVersionCultureVariation table instead. - // The resulting clause will be used in the foreach below to compare against the original clause that comes from the "filter" and if they are the same - // we know that we are searching a list view and the proper where clause will be replaced to look in contentVersionCultureVariation table for the names. - var temp = Query().Where(x => x.Name.Contains("foo")); - var clause = temp.GetWhereClauses().First().Item1.Split(' ')[0]; - + // if we have a filter, map its clauses to an Sql statement if (filter != null) { + // if the clause works on "name", we need to swap the field and use the variantName instead, + // so that querying also works on variant content (for instance when searching a listview). + + // figure out how the "name" field is going to look like - so we can look for it + var nameField = SqlContext.VisitModelField(x => x.Name); + filterSql = Sql(); foreach (var filterClause in filter.GetWhereClauses()) { - // fixme - is this the right way of doing it??? + var clauseSql = filterClause.Item1; + var clauseArgs = filterClause.Item2; - // - var where = filterClause.Item1.Split(' ')[0] == clause - // normally, this would be the field alias (variantName) of the coalesce result between ContentVersionCulture and NodeDto names, however - // you can't refer to field alias in a WHERE clause so we have to put the coalesce calculation instead which refers to the original field - ? SqlContext.Visit((ccv, node) => ccv.Name ?? node.Text, "ccv").Sql - : filterClause.Item1; + // replace the name field + // we cannot reference an aliased field in a WHERE clause, so have to repeat the expression here + clauseSql = clauseSql.Replace(nameField, VariantNameSqlExpression); - filterSql.Append( - where.Contains("COALESCE") ? $"AND upper({where}) LIKE upper(@0)" : $"AND ({where})", - filterClause.Item2); + // append the clause + filterSql.Append($"AND ({clauseSql})", clauseArgs); } } @@ -910,7 +921,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement return base.ApplySystemOrdering(ref sql, ordering); } - private IEnumerable MapDtosToContent(List dtos, bool withCache = false) + private IEnumerable MapDtosToContent(List dtos, bool withCache = false, bool slim = false) { var temps = new List>(); var contentTypes = new Dictionary(); @@ -928,7 +939,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var cached = IsolatedCache.GetCacheItem(RepositoryCacheKeys.GetKey(dto.NodeId)); if (cached != null && cached.VersionId == dto.DocumentVersionDto.ContentVersionDto.Id) { - content[i] = (Content)cached; + content[i] = (Content) cached; continue; } } @@ -943,18 +954,21 @@ namespace Umbraco.Core.Persistence.Repositories.Implement var c = content[i] = ContentBaseFactory.BuildEntity(dto, contentType); - // need templates - var templateId = dto.DocumentVersionDto.TemplateId; - if (templateId.HasValue && templateId.Value > 0) - templateIds.Add(templateId.Value); - if (dto.Published) + if (!slim) { - templateId = dto.PublishedVersionDto.TemplateId; + // need templates + var templateId = dto.DocumentVersionDto.TemplateId; if (templateId.HasValue && templateId.Value > 0) templateIds.Add(templateId.Value); + if (dto.Published) + { + templateId = dto.PublishedVersionDto.TemplateId; + if (templateId.HasValue && templateId.Value > 0) + templateIds.Add(templateId.Value); + } } - // need properties + // need temps, for properties, templates and variations var versionId = dto.DocumentVersionDto.Id; var publishedVersionId = dto.Published ? dto.PublishedVersionDto.Id : 0; var temp = new TempContent(dto.NodeId, versionId, publishedVersionId, contentType, c) @@ -965,25 +979,28 @@ namespace Umbraco.Core.Persistence.Repositories.Implement temps.Add(temp); } - // load all required templates in 1 query, and index - var templates = _templateRepository.GetMany(templateIds.ToArray()) - .ToDictionary(x => x.Id, x => x); - - // load all properties for all documents from database in 1 query - indexed by version id - var properties = GetPropertyCollections(temps); - - // assign templates and properties - foreach (var temp in temps) + if (!slim) { - // complete the item - if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template)) - temp.Content.Template = template; - if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template)) - temp.Content.PublishTemplate = template; - temp.Content.Properties = properties[temp.VersionId]; + // load all required templates in 1 query, and index + var templates = _templateRepository.GetMany(templateIds.ToArray()) + .ToDictionary(x => x.Id, x => x); - // reset dirty initial properties (U4-1946) - temp.Content.ResetDirtyProperties(false); + // load all properties for all documents from database in 1 query - indexed by version id + var properties = GetPropertyCollections(temps); + + // assign templates and properties + foreach (var temp in temps) + { + // complete the item + if (temp.Template1Id.HasValue && templates.TryGetValue(temp.Template1Id.Value, out var template)) + temp.Content.Template = template; + if (temp.Template2Id.HasValue && templates.TryGetValue(temp.Template2Id.Value, out template)) + temp.Content.PublishTemplate = template; + temp.Content.Properties = properties[temp.VersionId]; + + // reset dirty initial properties (U4-1946) + temp.Content.ResetDirtyProperties(false); + } } // set variations, if varying diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs index 2390ce9a7b..dbfdc8e980 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MediaRepository.cs @@ -8,12 +8,12 @@ using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Exceptions; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -100,7 +100,12 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Many: sql = sql.Select(r => r.Select(x => x.NodeDto) - .Select(x => x.ContentVersionDto)); + .Select(x => x.ContentVersionDto)) + + // ContentRepositoryBase expects a variantName field to order by name + // for now, just return the plain invariant node name + // fixme media should support variants !! + .AndSelect(x => Alias(x.Text, "variantName")); break; } diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs index 84ef154ae8..fd79b231de 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/MemberRepository.cs @@ -6,12 +6,12 @@ using NPoco; using Umbraco.Core.Cache; using Umbraco.Core.Logging; using Umbraco.Core.Models; -using Umbraco.Core.Persistence.DatabaseModelDefinitions; using Umbraco.Core.Persistence.Dtos; using Umbraco.Core.Persistence.Factories; using Umbraco.Core.Persistence.Querying; using Umbraco.Core.Scoping; using Umbraco.Core.Services; +using static Umbraco.Core.Persistence.NPocoSqlExtensions.Statics; namespace Umbraco.Core.Persistence.Repositories.Implement { @@ -114,9 +114,13 @@ namespace Umbraco.Core.Persistence.Repositories.Implement case QueryType.Single: case QueryType.Many: sql = sql.Select(r => - r.Select(x => x.ContentVersionDto) - .Select(x => x.ContentDto, r1 => - r1.Select(x => x.NodeDto))); + r.Select(x => x.ContentVersionDto) + .Select(x => x.ContentDto, r1 => + r1.Select(x => x.NodeDto))) + + // ContentRepositoryBase expects a variantName field to order by name + // so get it here, though for members it's just the plain node name + .AndSelect(x => Alias(x.Text, "variantName")); break; } diff --git a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs index e28816b6a4..249e2cafd0 100644 --- a/src/Umbraco.Core/Persistence/SqlContextExtensions.cs +++ b/src/Umbraco.Core/Persistence/SqlContextExtensions.cs @@ -17,11 +17,11 @@ namespace Umbraco.Core.Persistence /// An expression to visit. /// An optional table alias. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -33,11 +33,11 @@ namespace Umbraco.Core.Persistence /// An expression to visit. /// An optional table alias. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -50,11 +50,11 @@ namespace Umbraco.Core.Persistence /// An optional table alias for the first DTO. /// An optional table alias for the second DTO. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); } /// @@ -68,11 +68,42 @@ namespace Umbraco.Core.Persistence /// An optional table alias for the first DTO. /// An optional table alias for the second DTO. /// A SQL statement, and arguments, corresponding to the expression. - public static (string Sql, object[] Args) Visit(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) + public static (string Sql, object[] Args) VisitDto(this ISqlContext sqlContext, Expression> expression, string alias1 = null, string alias2 = null) { - var expresionist = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); - var visited = expresionist.Visit(expression); - return (visited, expresionist.GetSqlParameters()); + var visitor = new PocoToSqlExpressionVisitor(sqlContext, alias1, alias2); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); + } + + /// + /// Visit a model expression. + /// + /// The type of the model. + /// An . + /// An expression to visit. + /// A SQL statement, and arguments, corresponding to the expression. + public static (string Sql, object[] Args) VisitModel(this ISqlContext sqlContext, Expression> expression) + { + var visitor = new ModelToSqlExpressionVisitor(sqlContext.SqlSyntax, sqlContext.Mappers); + var visited = visitor.Visit(expression); + return (visited, visitor.GetSqlParameters()); + } + + /// + /// Visit a model expression representing a field. + /// + /// The type of the model. + /// An . + /// An expression to visit, representing a field. + /// The name of the field. + public static string VisitModelField(this ISqlContext sqlContext, Expression> field) + { + var (sql, _) = sqlContext.VisitModel(field); + + // going to return " = @0" + // take the first part only + var pos = sql.IndexOf(' '); + return sql.Substring(0, pos); } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Core/Runtime/CoreRuntime.cs b/src/Umbraco.Core/Runtime/CoreRuntime.cs index 0d967cb054..04eaba3f9a 100644 --- a/src/Umbraco.Core/Runtime/CoreRuntime.cs +++ b/src/Umbraco.Core/Runtime/CoreRuntime.cs @@ -307,9 +307,9 @@ namespace Umbraco.Core.Runtime throw new BootFailedException("A connection string is configured but Umbraco could not connect to the database."); } - // if we already know we want to upgrade, no need to look for migrations... - if (_state.Level == RuntimeLevel.Upgrade) - return; + // if we already know we want to upgrade, + // still run EnsureUmbracoUpgradeState to get the states + // (v7 will just get a null state, that's ok) // else // look for a matching migration entry - bypassing services entirely - they are not 'up' yet diff --git a/src/Umbraco.Core/Services/IContentService.cs b/src/Umbraco.Core/Services/IContentService.cs index 022bee8b41..64877e393e 100644 --- a/src/Umbraco.Core/Services/IContentService.cs +++ b/src/Umbraco.Core/Services/IContentService.cs @@ -134,6 +134,12 @@ namespace Umbraco.Core.Services /// Versions are ordered with current first, then most recent first. IEnumerable GetVersions(int id); + /// + /// Gets all versions of a document. + /// + /// Versions are ordered with current first, then most recent first. + IEnumerable GetVersionsSlim(int id, int skip, int take); + /// /// Gets top versions of a document. /// @@ -461,5 +467,21 @@ namespace Umbraco.Core.Services IContent CreateAndSave(string name, IContent parent, string contentTypeAlias, int userId = 0); #endregion + + #region Rollback + + /// + /// Rolls back the content to a specific version. + /// + /// The id of the content node. + /// The version id to roll back to. + /// An optional culture to roll back. + /// The identifier of the user who is performing the roll back. + /// + /// When no culture is specified, all cultures are rolled back. + /// + OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0); + + #endregion } } diff --git a/src/Umbraco.Core/Services/Implement/ContentService.cs b/src/Umbraco.Core/Services/Implement/ContentService.cs index a4fb535f04..2af6de24c7 100644 --- a/src/Umbraco.Core/Services/Implement/ContentService.cs +++ b/src/Umbraco.Core/Services/Implement/ContentService.cs @@ -474,6 +474,19 @@ namespace Umbraco.Core.Services.Implement } } + /// + /// Gets a collection of an objects versions by Id + /// + /// An Enumerable list of objects + public IEnumerable GetVersionsSlim(int id, int skip, int take) + { + using (var scope = ScopeProvider.CreateScope(autoComplete: true)) + { + scope.ReadLock(Constants.Locks.ContentTree); + return _documentRepository.GetAllVersionsSlim(id, skip, take); + } + } + /// /// Gets a list of all version Ids for the given content item ordered so latest is first /// @@ -2538,5 +2551,68 @@ namespace Umbraco.Core.Services.Implement } #endregion + + #region Rollback + + public OperationResult Rollback(int id, int versionId, string culture = "*", int userId = 0) + { + var evtMsgs = EventMessagesFactory.Get(); + + //Get the current copy of the node + var content = GetById(id); + + //Get the version + var version = GetVersion(versionId); + + //Good ole null checks + if (content == null || version == null) + { + return new OperationResult(OperationResultType.FailedCannot, evtMsgs); + } + + //Store the result of doing the save of content for the rollback + OperationResult rollbackSaveResult; + + using (var scope = ScopeProvider.CreateScope()) + { + var rollbackEventArgs = new RollbackEventArgs(content); + + //Emit RollingBack event aka before + if (scope.Events.DispatchCancelable(RollingBack, this, rollbackEventArgs)) + { + scope.Complete(); + return OperationResult.Cancel(evtMsgs); + } + + //Copy the changes from the version + content.CopyFrom(version, culture); + + //Save the content for the rollback + rollbackSaveResult = Save(content, userId); + + //Depending on the save result - is what we log & audit along with what we return + if (rollbackSaveResult.Success == false) + { + //Log the error/warning + Logger.Error("User '{UserId}' was unable to rollback content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + } + else + { + //Emit RolledBack event aka after + rollbackEventArgs.CanCancel = false; + scope.Events.Dispatch(RolledBack, this, rollbackEventArgs); + + //Logging & Audit message + Logger.Info("User '{UserId}' rolled back content '{ContentId}' to version '{VersionId}'", userId, id, versionId); + Audit(AuditType.RollBack, $"Content '{content.Name}' was rolled back to version '{versionId}'", userId, id); + } + + scope.Complete(); + } + + return rollbackSaveResult; + } + + #endregion } } diff --git a/src/Umbraco.Tests/Services/ContentServiceTests.cs b/src/Umbraco.Tests/Services/ContentServiceTests.cs index 99aa9b788a..5aad7c4d90 100644 --- a/src/Umbraco.Tests/Services/ContentServiceTests.cs +++ b/src/Umbraco.Tests/Services/ContentServiceTests.cs @@ -25,6 +25,7 @@ using Umbraco.Core.Services.Implement; using Umbraco.Tests.Testing; using Umbraco.Web.PropertyEditors; using System.Reflection; +using Umbraco.Core.Persistence.DatabaseModelDefinitions; namespace Umbraco.Tests.Services { @@ -2415,6 +2416,22 @@ namespace Umbraco.Tests.Services } Console.WriteLine("-"); + var versionsSlim = ServiceContext.ContentService.GetVersionsSlim(page.Id, 0, 50).ToArray(); + Assert.AreEqual(5, versionsSlim.Length); + + for (var i = 0; i < 5; i++) + { + Console.Write("[{0}] ", i); + Console.WriteLine(versionsSlim[i].UpdateDate.ToString("O").Substring(11)); + Console.WriteLine(" fr: {0}", versionsSlim[i].GetUpdateDate("fr")?.ToString("O").Substring(11)); + Console.WriteLine(" da: {0}", versionsSlim[i].GetUpdateDate("da")?.ToString("O").Substring(11)); + } + Console.WriteLine("-"); + + // what we do in the controller to get rollback versions + var versionsSlimFr = versionsSlim.Where(x => x.UpdateDate == x.GetUpdateDate("fr")).ToArray(); + Assert.AreEqual(3, versionsSlimFr.Length); + // alas, at the moment we do *not* properly track 'dirty' for cultures, meaning // that we cannot synchronize dates the way we do with publish dates - and so this // would fail - the version UpdateDate is greater than the cultures'. @@ -2830,6 +2847,107 @@ namespace Umbraco.Tests.Services } } + [Test] + public void Can_Get_Paged_Children_WithFilterAndOrder() + { + var languageService = ServiceContext.LocalizationService; + + var langUk = new Language("en-UK") { IsDefault = true }; + var langFr = new Language("fr-FR"); + var langDa = new Language("da-DK"); + + languageService.Save(langFr); + languageService.Save(langUk); + languageService.Save(langDa); + + var contentTypeService = ServiceContext.ContentTypeService; + + var contentType = contentTypeService.Get("umbTextpage"); + contentType.Variations = ContentVariation.Culture; + contentTypeService.Save(contentType); + + var contentService = ServiceContext.ContentService; + + var o = new[] { 2, 1, 3, 0, 4 }; // randomly different + for (var i = 0; i < 5; i++) + { + var contentA = new Content(null, -1, contentType); + contentA.SetCultureName("contentA" + i + "uk", langUk.IsoCode); + contentA.SetCultureName("contentA" + o[i] + "fr", langFr.IsoCode); + contentA.SetCultureName("contentX" + i + "da", langDa.IsoCode); + contentService.Save(contentA); + + var contentB = new Content(null, -1, contentType); + contentB.SetCultureName("contentB" + i + "uk", langUk.IsoCode); + contentB.SetCultureName("contentB" + o[i] + "fr", langFr.IsoCode); + contentB.SetCultureName("contentX" + i + "da", langDa.IsoCode); + contentService.Save(contentB); + } + + // get all + var list = contentService.GetPagedChildren(-1, 0, 100, out var total).ToList(); + + Console.WriteLine("ALL"); + WriteList(list); + + // 10 items (there's already a Home content in there...) + Assert.AreEqual(11, total); + Assert.AreEqual(11, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentX")), + Ordering.By("name", culture: langFr.IsoCode)).ToList(); + + Assert.AreEqual(0, total); + Assert.AreEqual(0, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentX")), + Ordering.By("name", culture: langDa.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME da:'contentX'"); + WriteList(list); + + Assert.AreEqual(10, total); + Assert.AreEqual(10, list.Count); + + // filter + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentA")), + Ordering.By("name", culture: langFr.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME fr:'contentA', ORDER ASC"); + WriteList(list); + + Assert.AreEqual(5, total); + Assert.AreEqual(5, list.Count); + + for (var i = 0; i < 5; i++) + Assert.AreEqual("contentA" + i + "fr", list[i].GetCultureName(langFr.IsoCode)); + + list = contentService.GetPagedChildren(-1, 0, 100, out total, + SqlContext.Query().Where(x => x.Name.Contains("contentA")), + Ordering.By("name", direction: Direction.Descending, culture: langFr.IsoCode)).ToList(); + + Console.WriteLine("FILTER BY NAME fr:'contentA', ORDER DESC"); + WriteList(list); + + Assert.AreEqual(5, total); + Assert.AreEqual(5, list.Count); + + for (var i = 0; i < 5; i++) + Assert.AreEqual("contentA" + (4-i) + "fr", list[i].GetCultureName(langFr.IsoCode)); + } + + private void WriteList(List list) + { + foreach (var content in list) + Console.WriteLine("[{0}] {1} {2} {3} {4}", content.Id, content.Name, content.GetCultureName("en-UK"), content.GetCultureName("fr-FR"), content.GetCultureName("da-DK")); + Console.WriteLine("-"); + } + [Test] public void Can_SaveRead_Variations() { diff --git a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs index 21f1ce82b2..6b52137542 100644 --- a/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs +++ b/src/Umbraco.Tests/TestHelpers/TestWithDatabaseBase.cs @@ -64,6 +64,8 @@ namespace Umbraco.Tests.TestHelpers internal ScopeProvider ScopeProvider => Current.ScopeProvider as ScopeProvider; + protected ISqlContext SqlContext => Container.GetInstance(); + public override void SetUp() { base.SetUp(); diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index ed03193271..d1bbfe65db 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -6,21 +6,22 @@ var wrap = require("gulp-wrap-js"); var sort = require('gulp-sort'); var connect = require('gulp-connect'); var open = require('gulp-open'); -const babel = require("gulp-babel"); +var babel = require("gulp-babel"); var runSequence = require('run-sequence'); -const imagemin = require('gulp-imagemin'); +var imagemin = require('gulp-imagemin'); var _ = require('lodash'); var MergeStream = require('merge-stream'); // js -const eslint = require('gulp-eslint'); +var eslint = require('gulp-eslint'); //Less + css var postcss = require('gulp-postcss'); var less = require('gulp-less'); var autoprefixer = require('autoprefixer'); var cssnano = require('cssnano'); +var cleanCss = require("gulp-clean-css"); // Documentation var gulpDocs = require('gulp-ngdocs'); @@ -49,14 +50,14 @@ function processJs(files, out) { } function processLess(files, out) { - var processors = [ autoprefixer, - cssnano({zindex: false}), + cssnano({zindex: false}) ]; return gulp.src(files) .pipe(less()) + .pipe(cleanCss()) .pipe(postcss(processors)) .pipe(rename(out)) .pipe(gulp.dest(root + targets.css)); @@ -329,7 +330,7 @@ gulp.task('dependencies', function () { "src": [ "./node_modules/moment/min/moment.min.js", "./node_modules/moment/min/moment-with-locales.js", - "./node_modules/moment/min/moment-with-locales.min.js", + "./node_modules/moment/min/moment-with-locales.min.js" ], "base": "./node_modules/moment/min" }, diff --git a/src/Umbraco.Web.UI.Client/package-lock.json b/src/Umbraco.Web.UI.Client/package-lock.json index 6cce6d7708..deda717214 100644 --- a/src/Umbraco.Web.UI.Client/package-lock.json +++ b/src/Umbraco.Web.UI.Client/package-lock.json @@ -1266,7 +1266,7 @@ "array-slice": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha1-42jqFfibxwaff/uJrsOmx9SsItQ=", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true }, "array-union": { @@ -1440,7 +1440,7 @@ "base": { "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha1-e95c7RRbbVUakNuH+DxVi060io8=", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { "cache-base": "^1.0.1", @@ -1464,7 +1464,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1473,7 +1473,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -1482,7 +1482,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -1680,7 +1680,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -1696,7 +1696,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -1822,7 +1822,7 @@ "braces": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha1-WXn9PxTNUxVl5fot8av/8d+u5yk=", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "dev": true, "requires": { "arr-flatten": "^1.1.0", @@ -1948,7 +1948,7 @@ }, "uuid": { "version": "2.0.3", - "resolved": "http://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-2.0.3.tgz", "integrity": "sha1-Z+LoY3lyFVMN/zGOW/nc6/1Hsho=", "dev": true }, @@ -2014,7 +2014,7 @@ "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha1-Cn9GQWgxyLZi7jb+TnxZ129marI=", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { "collection-visit": "^1.0.0", @@ -2225,7 +2225,7 @@ "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha1-+TNprouafOAv1B+q0MqDAzGQxGM=", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -2245,6 +2245,23 @@ } } }, + "clean-css": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", + "dev": true, + "requires": { + "source-map": "~0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "cli-cursor": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", @@ -2458,7 +2475,7 @@ }, "commander": { "version": "2.8.1", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "dev": true, "requires": { @@ -2700,7 +2717,7 @@ "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "dev": true }, "convert-source-map": { @@ -3114,7 +3131,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -3211,7 +3228,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -3227,7 +3244,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -3318,7 +3335,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -3379,7 +3396,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -3439,7 +3456,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -3532,7 +3549,7 @@ "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha1-1Flono1lS6d+AqgX+HENcCyxbp0=", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { "is-descriptor": "^1.0.2", @@ -3542,7 +3559,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -3551,7 +3568,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -3560,7 +3577,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -3828,7 +3845,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -3925,7 +3942,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -3941,7 +3958,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -4034,7 +4051,7 @@ "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha1-7SljTRm6ukY7bOa4CjchPqtx7EM=", + "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { "once": "^1.4.0" @@ -4048,7 +4065,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -4064,7 +4081,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -4246,7 +4263,7 @@ "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha1-RoTXF3mtOa8Xfj8AeZb3xnyFJhg=", + "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", "dev": true, "optional": true, "requires": { @@ -4955,7 +4972,7 @@ "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha1-rQD+TcYSqSMuhxhxHcXLWrAoVUM=", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { "array-unique": "^0.3.2", @@ -4989,7 +5006,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -4998,7 +5015,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -5007,7 +5024,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -6042,7 +6059,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true, "optional": true @@ -6272,7 +6289,7 @@ "global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha1-bXcPDrUjrHgWTXK15xqIdyZcw+o=", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { "global-prefix": "^1.0.1", @@ -6384,7 +6401,7 @@ }, "got": { "version": "5.7.1", - "resolved": "http://registry.npmjs.org/got/-/got-5.7.1.tgz", + "resolved": "https://registry.npmjs.org/got/-/got-5.7.1.tgz", "integrity": "sha1-X4FjWmHkplifGAVp6k44FoClHzU=", "dev": true, "requires": { @@ -6422,7 +6439,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -6510,6 +6527,18 @@ } } }, + "gulp-clean-css": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/gulp-clean-css/-/gulp-clean-css-3.10.0.tgz", + "integrity": "sha512-7Isf9Y690o/Q5MVjEylH1H7L8WeZ89woW7DnhD5unTintOdZb67KdOayRgp9trUFo+f9UyJtuatV42e/+kghPg==", + "dev": true, + "requires": { + "clean-css": "4.2.1", + "plugin-error": "1.0.1", + "through2": "2.0.3", + "vinyl-sourcemaps-apply": "0.2.1" + } + }, "gulp-concat": { "version": "2.6.1", "resolved": "https://registry.npmjs.org/gulp-concat/-/gulp-concat-2.6.1.tgz", @@ -6588,7 +6617,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -6604,7 +6633,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -6921,7 +6950,7 @@ }, "lodash": { "version": "2.4.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-2.4.1.tgz", "integrity": "sha1-W3cjA03aTSYuWkb7LFjXzCL3FCA=", "dev": true }, @@ -8015,7 +8044,7 @@ "is-absolute": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha1-OV4a6EsR8mrReV5zwXN45IowFXY=", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { "is-relative": "^1.0.0", @@ -8119,7 +8148,7 @@ "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha1-Nm2CQN3kh8pRgjsaufB6EKeCUco=", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", @@ -8130,7 +8159,7 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha1-cpyR4thXt6QZofmqZWhcTDP1hF0=", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true } } @@ -8261,7 +8290,7 @@ }, "is-obj": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", "dev": true }, @@ -8353,7 +8382,7 @@ "is-relative": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha1-obtpNc6MXboei5dUubLcwCDiJg0=", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { "is-unc-path": "^1.0.0" @@ -8410,7 +8439,7 @@ "is-unc-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha1-1zHoiY7QkKEsNSrS6u1Qla0yLJ0=", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { "unc-path-regex": "^0.1.2" @@ -8437,7 +8466,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha1-0YUOuXkezRjmGCzhKjDzlmNLsZ0=", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, "is-zip": { @@ -8946,7 +8975,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -8962,7 +8991,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -9055,7 +9084,7 @@ "livereload-js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", - "integrity": "sha1-w6si6Kr1vzUF2A0JjLrWdyZUjJo=", + "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==", "dev": true }, "load-json-file": { @@ -9293,7 +9322,7 @@ "lodash.merge": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.1.tgz", - "integrity": "sha1-rcJdnLmbk5HFliTzefu6YNcRHVQ=", + "integrity": "sha512-AOYza4+Hf5z1/0Hztxpm2/xiPZgi/cjMqdnKTUWTBSKchJlxXXuUSxCCl8rJlf4g6yww/j6mA8nC8Hw/EZWxKQ==", "dev": true }, "lodash.partialright": { @@ -9712,7 +9741,7 @@ "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", - "integrity": "sha1-KbM/MSqo9UfEpeSQ9Wr87JkTOtY=", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", "dev": true, "requires": { "kind-of": "^6.0.2" @@ -9871,7 +9900,7 @@ "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha1-cIWbyVyYQJUvNZoGij/En57PrCM=", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { "arr-diff": "^4.0.0", @@ -9934,7 +9963,7 @@ "mixin-deep": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha1-pJ5yaNzhoNlpjkUybFYm3zVD0P4=", + "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { "for-in": "^1.0.2", @@ -9944,7 +9973,7 @@ "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha1-p0cPnkJnM9gb2B4RVSZOOjUHyrQ=", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { "is-plain-object": "^2.0.4" @@ -14070,7 +14099,7 @@ "promise": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", - "integrity": "sha1-BktyYCsY+Q8pGSuLG8QY/9Hr078=", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", "dev": true, "optional": true, "requires": { @@ -14283,7 +14312,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -14299,7 +14328,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -14506,7 +14535,7 @@ "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha1-H07OJ+ALC2XgJHpoEOaoXYOldSw=", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { "extend-shallow": "^3.0.2", @@ -14742,7 +14771,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha1-uKSCXVvbH8P29Twrwz+BOIaBx7w=", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", "dev": true }, "right-align": { @@ -15050,7 +15079,7 @@ "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha1-ca5KiPD+77v1LR6mBPP7MV67YnQ=", + "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, "requires": { "extend-shallow": "^2.0.1", @@ -15155,7 +15184,7 @@ "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha1-ZJIufFZbDhQgS6GqfWlkJ40lGC0=", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { "base": "^0.11.1", @@ -15191,7 +15220,7 @@ "snapdragon-node": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha1-bBdfhv8UvbByRWPo88GwIaKGhTs=", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { "define-property": "^1.0.0", @@ -15211,7 +15240,7 @@ "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha1-FpwvbT3x+ZJhgHI2XJsOofaHhlY=", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -15220,7 +15249,7 @@ "is-data-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha1-2Eh2Mh0Oet0DmQQGq7u9NrqSaMc=", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", "dev": true, "requires": { "kind-of": "^6.0.0" @@ -15229,7 +15258,7 @@ "is-descriptor": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha1-OxWXRqZmBLBPjIFSS6NlxfFNhuw=", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", @@ -15242,7 +15271,7 @@ "snapdragon-util": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha1-+VZHlIbyrNeXAGk/b3uAXkWrVuI=", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { "kind-of": "^3.2.0" @@ -15441,7 +15470,7 @@ "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha1-fLCd2jqGWFcFxks5pkZgOGguj+I=", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { "extend-shallow": "^3.0.0" @@ -15567,7 +15596,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -15583,7 +15612,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -15758,7 +15787,7 @@ }, "strip-dirs": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-1.1.1.tgz", "integrity": "sha1-lgu9EoeETzl1pFWKoQOoJV4kVqA=", "dev": true, "requires": { @@ -15787,7 +15816,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true } @@ -15948,7 +15977,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -16265,7 +16294,7 @@ "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha1-E8/dmzNlUvMLUfM6iuG0Knp1mc4=", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { "define-property": "^2.0.2", @@ -16365,7 +16394,7 @@ "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", - "integrity": "sha1-+JzjQVQcZysl7nrjxz3uOyvlAZQ=", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", "dev": true, "requires": { "media-typer": "0.3.0", @@ -16713,7 +16742,7 @@ "vendors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/vendors/-/vendors-1.0.2.tgz", - "integrity": "sha1-f8te759WI7FWvOqJ7DfWNnbyGAE=", + "integrity": "sha512-w/hry/368nO21AN9QljsaIhb9ZiZtZARoVH5f3CsFbawdLdayCgKRPup7CggujvySMxx0I91NOyxdVENohprLQ==", "dev": true }, "verror": { @@ -16770,7 +16799,7 @@ }, "readable-stream": { "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { @@ -16786,7 +16815,7 @@ "string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha1-nPFhG6YmhdcDCunkujQUnDrwP8g=", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { "safe-buffer": "~5.1.0" @@ -16987,7 +17016,7 @@ "websocket-extensions": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", - "integrity": "sha1-XS/yKXcAPsaHpLhwc9+7rBRszyk=", + "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, "when": { diff --git a/src/Umbraco.Web.UI.Client/package.json b/src/Umbraco.Web.UI.Client/package.json index 21e0eafc33..e5948367da 100644 --- a/src/Umbraco.Web.UI.Client/package.json +++ b/src/Umbraco.Web.UI.Client/package.json @@ -44,6 +44,7 @@ "@babel/preset-env": "^7.1.0", "autoprefixer": "^6.5.0", "bower-installer": "^1.2.0", + "gulp-clean-css": "3.10.0", "cssnano": "^3.7.6", "gulp": "^3.9.1", "gulp-babel": "^8.0.0-beta.2", diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 98e02f3d55..6670a41ac4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -103,6 +103,17 @@ loadContent(); } })); + + evts.push(eventsService.on("editors.content.reload", function (name, args) { + // if this content item uses the updated doc type we need to reload the content item + if(args && args.node && args.node.key === $scope.content.key) { + $scope.page.loading = true; + loadContent().then(function() { + $scope.page.loading = false; + }); + } + })); + } /** diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index f3dc6c7aa7..78efc8f789 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -114,6 +114,22 @@ scope.node.template = templateAlias; }; + scope.openRollback = function() { + + var rollback = { + node: scope.node, + submit: function(model) { + const args = { node: scope.node }; + eventsService.emit("editors.content.reload", args); + editorService.close(); + }, + close: function() { + editorService.close(); + } + }; + editorService.rollback(rollback); + }; + function loadAuditTrail() { //don't load this if it's already done diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js index 721cd4da57..71cbe3b8d7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/content.resource.js @@ -808,8 +808,106 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) { ), "Failed to create blueprint from content with id " + contentId ); - } + }, + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersions + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns an array of previous version id's, given a node id and a culture + * + * ##usage + *
+          * contentResource.getRollbackVersions(id, culture)
+          *    .then(function(versions) {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id Id of node + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object containing the versions + * + */ + getRollbackVersions: function (contentId, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersions", { + contentId: contentId, + culture: culture + }) + ), + "Failed to get rollback versions for content item with id " + contentId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#getRollbackVersion + * @methodOf umbraco.resources.contentResource + * + * @description + * Returns a previous version of a content item + * + * ##usage + *
+          * contentResource.getRollbackVersion(versionId, culture)
+          *    .then(function(version) {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object containing the version + * + */ + getRollbackVersion: function (versionId, culture) { + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "GetRollbackVersion", { + versionId: versionId, + culture: culture + }) + ), + "Failed to get version for content item with id " + versionId + ); + }, + + /** + * @ngdoc method + * @name umbraco.resources.contentResource#rollback + * @methodOf umbraco.resources.contentResource + * + * @description + * Roll backs a content item to a previous version + * + * ##usage + *
+          * contentResource.rollback(contentId, versionId, culture)
+          *    .then(function() {
+          *        alert('its here!');
+          *    });
+          * 
+ * + * @param {Int} id Id of node + * @param {Int} versionId The version Id + * @param {Int} culture if provided, the results will be for this specific culture/variant + * @returns {Promise} resourcePromise object + * + */ + rollback: function (contentId, versionId, culture) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl("contentApiBaseUrl", "PostRollbackContent", { + contentId: contentId, versionId:versionId, culture:culture + }) + ), + "Failed to roll back content item with id " + contentId + ); + } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js index a75e8d4a47..31ff5ff47b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/contenteditinghelper.service.js @@ -188,7 +188,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica hotKey: "ctrl+u", hotKeyWhenHidden: true, alias: "unpublish", - addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false" + addEllipsis: "true" }; case "SCHEDULE": //schedule publish - schedule doesn't have a permission letter so diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index eab167c2ec..7d1ef3e9b9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -180,6 +180,25 @@ open(editor); } + /** + * @ngdoc method + * @name umbraco.services.editorService#rollback + * @methodOf umbraco.services.editorService + * + * @description + * Opens a rollback editor in infinite editing. + * @param {String} editor.node The node to rollback + * @param {Callback} editor.submit Saves, submits, and closes the editor + * @param {Callback} editor.close Closes the editor + * @returns {Object} editor object + */ + + function rollback(editor) { + editor.view = "views/common/infiniteeditors/rollback/rollback.html"; + editor.size = "small"; + open(editor); + } + /** * @ngdoc method * @name umbraco.services.editorService#linkPicker @@ -481,6 +500,7 @@ copy: copy, move: move, embed: embed, + rollback: rollback, linkPicker: linkPicker, mediaPicker: mediaPicker, iconPicker: iconPicker, diff --git a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less index 4b670ab781..d3f2fee5d5 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-button.less @@ -104,7 +104,7 @@ } .umb-button--xs { - padding: 5px 16px; + padding: 5px 13px; font-size: 14px; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less index f2cacc26b3..fb83504a1f 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-box.less @@ -8,6 +8,9 @@ .umb-box-header { padding: 10px 20px; border-bottom: 1px solid @gray-9; + display: flex; + align-items: center; + justify-content: space-between; } .umb-box-header-title { diff --git a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less index 7acf47d22e..de2dca5f91 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/users/umb-user-cards.less @@ -1,18 +1,13 @@ .umb-user-cards { - display: flex; - flex-direction: row; - flex-wrap: wrap; - margin: -10px; + display: grid; + grid-gap: 20px; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } .umb-user-card { - padding: 10px; box-sizing: border-box; - flex: 0 0 100%; max-width: 100%; display: flex; - flex-wrap: wrap; - flex-direction: column; } .umb-user-card:hover, @@ -21,49 +16,6 @@ text-decoration: none !important; } -@media (min-width: 768px) { - .umb-user-card { - flex: 0 0 50%; - max-width: 50%; - } -} - -@media (min-width: 1200px) { - .umb-user-card { - flex: 0 0 33.33%; - max-width: 33.33%; - } -} - -@media (min-width: 1400px) { - .umb-user-card { - flex: 0 0 25%; - max-width: 25%; - } -} - -@media (min-width: 1700px) { - .umb-user-card { - flex: 0 0 20%; - max-width: 20%; - } -} - - -@media (min-width: 1900px) { - .umb-user-card { - flex: 0 0 16.66%; - max-width: 16.66%; - } -} - -@media (min-width: 2200px) { - .umb-user-card { - flex: 0 0 14.28%; - max-width: 14.28%; - } -} - .umb-user-card__content { position: relative; padding: 15px; diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index 43f5aa5a9a..198885e6be 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -2,7 +2,12 @@ // Container styles // -------------------------------------------------- .umb-property-editor { - min-width:66.6%; + @media (max-width: 800px) { + width: 100%; + } + @media (min-width: 800px) { + min-width:66.6%; + } &-pull { float:left; diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less b/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less index b8fb5ca5db..5767b0b474 100644 --- a/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/typography/_white-space.less @@ -7,6 +7,8 @@ .ws-normal { white-space: normal; } .nowrap { white-space: nowrap; } .pre { white-space: pre; } +.pre-wrap { white-space: pre-wrap; } +.pre-line { white-space: pre-line; } .truncate { white-space: nowrap; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js new file mode 100644 index 0000000000..6b8462b583 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -0,0 +1,177 @@ +(function () { + "use strict"; + + function RollbackController($scope, contentResource, localizationService, assetsService) { + + var vm = this; + + vm.rollback = rollback; + vm.changeLanguage = changeLanguage; + vm.changeVersion = changeVersion; + vm.submit = submit; + vm.close = close; + + ////////// + + function onInit() { + + vm.loading = true; + vm.variantVersions = []; + vm.diff = null; + vm.currentVersion = null; + vm.rollbackButtonDisabled = true; + + // find the current version for invariant nodes + if($scope.model.node.variants.length === 1) { + vm.currentVersion = $scope.model.node.variants[0]; + } + + // find the current version for nodes with variants + if($scope.model.node.variants.length > 1) { + var active = _.find($scope.model.node.variants, function (v) { + return v.active; + }); + + // preselect the language in the dropdown + if(active) { + vm.selectedLanguage = active; + vm.currentVersion = active; + } + } + + // set default title + if(!$scope.model.title) { + localizationService.localize("actions_rollback").then(function(value){ + $scope.model.title = value; + }); + } + + // Load in diff library + assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { + + getVersions().then(function(){ + vm.loading = false; + }); + + }); + + } + + function changeLanguage(language) { + vm.currentVersion = language; + getVersions(); + } + + function changeVersion(version) { + + if(version && version.versionId) { + + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + contentResource.getRollbackVersion(version.versionId, culture) + .then(function(data){ + vm.previousVersion = data; + vm.previousVersion.versionId = version.versionId; + createDiff(vm.currentVersion, vm.previousVersion); + vm.rollbackButtonDisabled = false; + }); + + } else { + vm.diff = null; + vm.rollbackButtonDisabled = true; + } + } + + function getVersions() { + + const nodeId = $scope.model.node.id; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.getRollbackVersions(nodeId, culture) + .then(function(data){ + vm.previousVersions = data.map(version => { + version.displayValue = version.versionDate + " - " + version.versionAuthorName; + return version; + }); + }); + } + + /** + * This will load in a new version + */ + function createDiff(currentVersion, previousVersion) { + + vm.diff = {}; + vm.diff.properties = []; + + // find diff in name + vm.diff.name = JsDiff.diffWords(currentVersion.name, previousVersion.name); + + // extract all properties from the tabs and create new object for the diff + currentVersion.tabs.forEach((tab, tabIndex) => { + tab.properties.forEach((property, propertyIndex) => { + var oldProperty = previousVersion.tabs[tabIndex].properties[propertyIndex]; + + // we have to make properties storing values as object into strings (Grid, nested content, etc.) + if(property.value instanceof Object) { + property.value = JSON.stringify(property.value, null, 1); + property.isObject = true; + } + + if(oldProperty.value instanceof Object) { + oldProperty.value = JSON.stringify(oldProperty.value, null, 1); + oldProperty.isObject = true; + } + + // create new property object used in the diff table + var diffProperty = { + "alias": property.alias, + "label": property.label, + "diff": (property.value || oldProperty.value) ? JsDiff.diffWords(property.value, oldProperty.value) : "", + "isObject": (property.isObject || oldProperty.isObject) ? true : false + }; + + vm.diff.properties.push(diffProperty); + + }); + }); + + } + + function rollback() { + + vm.rollbackButtonState = "busy"; + + const nodeId = $scope.model.node.id; + const versionId = vm.previousVersion.versionId; + const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; + + return contentResource.rollback(nodeId, versionId, culture) + .then(data => { + vm.rollbackButtonState = "success"; + submit(); + }, error => { + vm.rollbackButtonState = "error"; + }); + + } + + function submit() { + if($scope.model.submit) { + $scope.model.submit($scope.model.submit); + } + } + + function close() { + if($scope.model.close) { + $scope.model.close(); + } + } + + onInit(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.RollbackController", RollbackController); + +})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html new file mode 100644 index 0000000000..f7ab78b7a0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.html @@ -0,0 +1,104 @@ +
+ + + + + + + + + + + + + + +
+
+ +
+ +
+ +
+

{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})

+ +
+ +
+ +
+ +
Changes
+ + + + + + + + + + + + + +
Name + + {{part.value}} + {{part.value}} + {{part.value}} + +
{{property.label}} + + {{part.value}} + {{part.value}} + {{part.value}} + +
+ +
+ +
+
+
+ + + + + + + + + + +
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index bdc4ad6c7f..1cfdb92610 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -41,7 +41,19 @@ - + + + + + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html index 8c59061788..e9f771f09c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/html/umb-box/umb-box-header.html @@ -1,10 +1,13 @@ -
-
- - {{title}} -
-
- - {{description}} +
+
+
+ + {{title}} +
+
+ + {{description}} +
+
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html index bbf70f7a8e..13a5491184 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property-editor.html @@ -1,3 +1,3 @@ -
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js deleted file mode 100644 index 9da43b1430..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/content/content.rollback.controller.js +++ /dev/null @@ -1,117 +0,0 @@ -(function () { - "use strict"; - - function ContentRollbackController($scope, assetsService) { - - var vm = this; - - vm.rollback = rollback; - vm.loadVersion = loadVersion; - vm.closeDialog = closeDialog; - - function onInit() { - - vm.loading = true; - vm.variantVersions = []; - vm.diff = null; - - // Load in diff library - assetsService.loadJs('lib/jsdiff/diff.min.js', $scope).then(function () { - - var currentLanguage = $scope.currentNode.metaData.culture; - - vm.currentVersion = { - "id": 1, - "createDate": "22/08/2018 13.32", - "name": "Forside", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "Velkommen" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content" - } - ] - }; - - vm.previousVersions = [ - { - "id": 2, - "name": "Forside", - "createDate": "21/08/2018 19.25" - } - ]; - - vm.loading = false; - - }); - - } - - function rollback() { - console.log("rollback"); - } - - /** - * This will load in a new version - */ - function loadVersion(id) { - - // fake load version - var currentLanguage = $scope.currentNode.metaData.culture; - - vm.diff = {}; - vm.diff.properties = []; - - var oldVersion = { - "id": 2, - "name": "Foride", - "properties": [ - { - "alias": "headline", - "label": "Headline", - "value": "" - }, - { - "alias": "text", - "label": "Text", - "value": "This is my danish Content Test" - } - ] - }; - - // find diff in name - vm.diff.name = JsDiff.diffWords(vm.currentVersion.name, oldVersion.name); - - // find diff in properties - angular.forEach(vm.currentVersion.properties, - function(newProperty, index){ - var oldProperty = oldVersion.properties[index]; - var diffProperty = { - "alias": newProperty.alias, - "label": newProperty.label, - "diff": JsDiff.diffWords(newProperty.value, oldProperty.value) - }; - vm.diff.properties.push(diffProperty); - }); - - } - - /** - * This will close the dialog - */ - function closeDialog() { - $scope.nav.hideDialog(); - } - - onInit(); - - } - - angular.module("umbraco").controller("Umbraco.Editors.Content.RollbackController", ContentRollbackController); - -})(); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html index c7855651ec..8ca8b78b23 100644 --- a/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html +++ b/src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html @@ -25,7 +25,7 @@ {{ variant.language.name }} -
+
-
diff --git a/src/Umbraco.Web.UI.Client/src/views/content/rollback.html b/src/Umbraco.Web.UI.Client/src/views/content/rollback.html deleted file mode 100644 index 389b8f30db..0000000000 --- a/src/Umbraco.Web.UI.Client/src/views/content/rollback.html +++ /dev/null @@ -1,80 +0,0 @@ -
- -
- - - -
- -
-
Rollback {{ currentNode.name }}
- -
-

{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})

- -
- -
- -
- -
Changes
- - - - - - - - - - - - - -
Name - - {{part.value}} - {{part.value}} - {{part.value}} - -
{{property.label}} - - {{part.value}} - {{part.value}} - {{part.value}} - -
- -
- -
-
- - - -
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html index 735bf16d94..dfec56fbc0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/languages/overview.html +++ b/src/Umbraco.Web.UI.Client/src/views/languages/overview.html @@ -31,6 +31,7 @@ Language + ISO Default Mandatory Fallback @@ -42,6 +43,9 @@ {{ language.name }} + + {{ language.culture }} +
- diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index 391b0f43e1..de52021220 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -349,6 +349,7 @@ + Designer diff --git a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml index 79968939ec..1d397bfd01 100644 --- a/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Install/Views/Index.cshtml @@ -45,7 +45,7 @@

Did you know

-

+

{{installer.feedback}}

diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx deleted file mode 100644 index 84b2cc5cb8..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/rollBack.aspx +++ /dev/null @@ -1,98 +0,0 @@ -<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master"AutoEventWireup="True" Inherits="umbraco.presentation.dialogs.rollBack" %> - -<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - - -
- - - - - - - - () - - - - - - - - Diff - Html - - - - - - -
-
-

- -

-
- - - -
-
-
-
- - -
diff --git a/src/Umbraco.Web.UI/Umbraco/js/UmbracoSpeechBubbleBackEnd.js b/src/Umbraco.Web.UI/Umbraco/js/UmbracoSpeechBubbleBackEnd.js index f0d041d37b..f738538f90 100644 --- a/src/Umbraco.Web.UI/Umbraco/js/UmbracoSpeechBubbleBackEnd.js +++ b/src/Umbraco.Web.UI/Umbraco/js/UmbracoSpeechBubbleBackEnd.js @@ -8,21 +8,21 @@ function UmbracoSpeechBubble(id) { this.GenerateSpeechBubble(); } -UmbracoSpeechBubble.prototype.GenerateSpeechBubble = function() { +UmbracoSpeechBubble.prototype.GenerateSpeechBubble = function () { var sbHtml = document.getElementById(this.id); sbHtml.innerHTML = '' + - '
' + - '
' + - '' + - ' Close' + - '

The header!

' + - '

Default Text Container!


' + - '
' + - '
' -} + '
' + + '
' + + '' + + ' Close' + + '

The header!

' + + '

Default Text Container!


' + + '
' + + '
'; +}; UmbracoSpeechBubble.prototype.ShowMessage = function (icon, header, message, dontAutoHide) { var speechBubble = jQuery("#" + this.id); @@ -46,7 +46,7 @@ UmbracoSpeechBubble.prototype.ShowMessage = function (icon, header, message, don jQuery(".speechClose").show(); } } -} +}; UmbracoSpeechBubble.prototype.Hide = function () { if (!this.ie) { @@ -54,7 +54,7 @@ UmbracoSpeechBubble.prototype.Hide = function () { } else { jQuery("#" + this.id).hide(); } -} +}; // Initialize var UmbSpeechBubble = null @@ -65,4 +65,4 @@ function InitUmbracoSpeechBubble() { jQuery(document).ready(function() { InitUmbracoSpeechBubble(); -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web/CompositionExtensions.cs b/src/Umbraco.Web/CompositionExtensions.cs index 332380009d..f33c8e98a8 100644 --- a/src/Umbraco.Web/CompositionExtensions.cs +++ b/src/Umbraco.Web/CompositionExtensions.cs @@ -10,6 +10,7 @@ using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; using Umbraco.Web.Routing; using Umbraco.Web._Legacy.Actions; +using Umbraco.Web.ContentApps; // the namespace here is intentional - although defined in Umbraco.Web assembly, // this class should be visible when using Umbraco.Core.Components, alongside @@ -32,7 +33,15 @@ namespace Umbraco.Core.Components /// internal static ActionCollectionBuilder Actions(this Composition composition) => composition.Container.GetInstance(); - + + /// + /// Gets the content apps collection builder. + /// + /// The composition. + /// + public static ContentAppDefinitionCollectionBuilder ContentApps(this Composition composition) + => composition.Container.GetInstance(); + /// /// Gets the content finders collection builder. /// diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index adc2a78804..cce9a2d2ed 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -1702,5 +1702,93 @@ namespace Umbraco.Web.Editors Services.NotificationService.SetNotifications(Security.CurrentUser, content, notifyOptions); } + [HttpGet] + public IEnumerable GetRollbackVersions(int contentId, string culture = null) + { + var rollbackVersions = new List(); + var writerIds = new HashSet(); + + //Return a list of all versions of a specific content node + // fixme - cap at 50 versions for now? + var versions = Services.ContentService.GetVersionsSlim(contentId, 0, 50); + + //Not all nodes are variants & thus culture can be null + if (culture != null) + { + //Get cultures that were published with the version = their update date is equal to the version's + versions = versions.Where(x => x.UpdateDate == x.GetUpdateDate(culture)); + } + + //First item is our current item/state (cant rollback to ourselves) + versions = versions.Skip(1); + + foreach (var version in versions) + { + var rollbackVersion = new RollbackVersion + { + VersionId = version.VersionId, + VersionDate = version.UpdateDate, + VersionAuthorId = version.WriterId + }; + + rollbackVersions.Add(rollbackVersion); + + writerIds.Add(version.WriterId); + } + + var users = Services.UserService + .GetUsersById(writerIds.ToArray()) + .ToDictionary(x => x.Id, x => x.Name); + + foreach (var rollbackVersion in rollbackVersions) + { + if (users.TryGetValue(rollbackVersion.VersionAuthorId, out var userName)) + rollbackVersion.VersionAuthorName = userName; + } + + return rollbackVersions; + } + + [HttpGet] + public ContentVariantDisplay GetRollbackVersion(int versionId, string culture = null) + { + var version = Services.ContentService.GetVersion(versionId); + var content = MapToDisplay(version); + + return culture == null + ? content.Variants.FirstOrDefault() //No culture set - so this is an invariant node - so just list me the first item in here + : content.Variants.FirstOrDefault(x => x.Language.IsoCode == culture); + } + + [HttpPost] + public HttpResponseMessage PostRollbackContent(int contentId, int versionId, string culture = "*") + { + var rollbackResult = Services.ContentService.Rollback(contentId, versionId, culture, Security.GetUserId().ResultOr(0)); + + if (rollbackResult.Success) + return Request.CreateResponse(HttpStatusCode.OK); + + var notificationModel = new SimpleNotificationModel(); + + switch (rollbackResult.Result) + { + case OperationResultType.Failed: + case OperationResultType.FailedCannot: + case OperationResultType.FailedExceptionThrown: + case OperationResultType.NoOperation: + default: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationFailedHeader"), + null); //TODO: There is no specific failed to save error message AFAIK + break; + case OperationResultType.FailedCancelledByEvent: + notificationModel.AddErrorNotification( + Services.TextService.Localize("speechBubbles/operationCancelledHeader"), + Services.TextService.Localize("speechBubbles/operationCancelledText")); + break; + } + + return Request.CreateValidationErrorResponse(notificationModel); + } } } diff --git a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs index 33ceffe616..7d47139721 100644 --- a/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs +++ b/src/Umbraco.Web/Install/InstallSteps/UpgradeStep.cs @@ -19,13 +19,12 @@ namespace Umbraco.Web.Install.InstallSteps { get { - var currentVersion = UmbracoVersion.Local; - - //fixme - in this case there's a db but the version is cleared which is fine and a normal way to force the upgrader - // to execute, but before we would detect the current version via the DB like DatabaseSchemaResult.DetermineInstalledVersion - // what now, do we need to? - if (currentVersion == null) - currentVersion = new Semver.SemVersion(0); + // fixme - if UmbracoVersion.Local is null? + // it means that there is a database but the web.config version is cleared + // that was a "normal" way to force the upgrader to execute, and we would detect the current + // version via the DB like DatabaseSchemaResult.DetermineInstalledVersion - magic, do we really + // need this now? + var currentVersion = (UmbracoVersion.Local ?? new Semver.SemVersion(0)).ToString(); var newVersion = UmbracoVersion.SemanticVersion.ToString(); diff --git a/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs new file mode 100644 index 0000000000..dad1341a8c --- /dev/null +++ b/src/Umbraco.Web/Models/ContentEditing/RollbackVersion.cs @@ -0,0 +1,21 @@ +using System; +using System.Runtime.Serialization; + +namespace Umbraco.Web.Models.ContentEditing +{ + [DataContract(Name = "rollbackVersion", Namespace = "")] + public class RollbackVersion + { + [DataMember(Name = "versionId")] + public int VersionId { get; set; } + + [DataMember(Name = "versionDate")] + public DateTime VersionDate { get; set; } + + [DataMember(Name = "versionAuthorId")] + public int VersionAuthorId { get; set; } + + [DataMember(Name = "versionAuthorName")] + public string VersionAuthorName { get; set; } + } +} diff --git a/src/Umbraco.Web/Trees/ContentTreeController.cs b/src/Umbraco.Web/Trees/ContentTreeController.cs index e414e69607..d04bb1c83b 100644 --- a/src/Umbraco.Web/Trees/ContentTreeController.cs +++ b/src/Umbraco.Web/Trees/ContentTreeController.cs @@ -238,7 +238,6 @@ namespace Umbraco.Web.Trees AddActionNode(item, menu, true); - AddActionNode(item, menu); AddActionNode(item, menu, convert: true); AddActionNode(item, menu); AddActionNode(item, menu, convert: true); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index dbdfce132a..6b4273e829 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -147,6 +147,7 @@ + @@ -1310,12 +1311,6 @@ editPackage.aspx - - rollBack.aspx - - - rollBack.aspx - @@ -1400,9 +1395,6 @@ ASPXCodeBehind - - ASPXCodeBehind - @@ -1456,10 +1448,7 @@ umbraco_org_umbraco_update_CheckForUpgrade - - - - +