Merge branch 'temp8' into temp8-241-unpublish
This commit is contained in:
+2
-1
@@ -48,4 +48,5 @@ Umbraco is contribution focused and community driven. If you want to contribute
|
||||
|
||||
Another way you can contribute to Umbraco is by providing issue reports. For information on how to submit an issue report refer to our [online guide for reporting issues](CONTRIBUTING_DETAILED.md#reporting-bugs).
|
||||
|
||||
To view existing issues, please visit [http://issues.umbraco.org](http://issues.umbraco.org).
|
||||
You can comment and report issues on the [github issue tracker](https://github.com/umbraco/Umbraco-CMS/issues).
|
||||
Since [September 2018](https://umbraco.com/blog/a-second-take-on-umbraco-issue-tracker-hello-github-issues/) the old issue tracker is in read only mode, but can still be found at [http://issues.umbraco.org](http://issues.umbraco.org).
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
|
||||
<!-- these files are copied by install.ps1 -->
|
||||
<file src="$BuildTmp$\WebApp\Web.config" target="UmbracoFiles\Web.config" />
|
||||
<file src="$BuildTmp$\WebApp\App_Browsers\**" target="UmbracoFiles\App_Browsers" />
|
||||
<file src="$BuildTmp$\WebApp\umbraco\**" target="UmbracoFiles\umbraco" />
|
||||
<file src="$BuildTmp$\WebApp\config\splashes\**" target="UmbracoFiles\Config\splashes" />
|
||||
|
||||
|
||||
@@ -21,6 +21,6 @@ The following items will now be automatically included when creating a deploy pa
|
||||
system: umbraco, config\splashes and global.asax.
|
||||
|
||||
Please read the release notes on our.umbraco.com:
|
||||
http://our.umbraco.com/contribute/releases
|
||||
https://our.umbraco.com/download/releases
|
||||
|
||||
- Umbraco
|
||||
|
||||
@@ -81,6 +81,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
|
||||
[ConfigurationProperty("loginBackgroundImage")]
|
||||
internal InnerTextConfigurationElement<string> LoginBackgroundImage => GetOptionalTextElement("loginBackgroundImage", string.Empty);
|
||||
[ConfigurationProperty("StripUdiAttributes")]
|
||||
internal InnerTextConfigurationElement<bool> StripUdiAttributes
|
||||
{
|
||||
get { return GetOptionalTextElement("StripUdiAttributes", true); }
|
||||
}
|
||||
|
||||
|
||||
string IContentSection.NotificationEmailAddress => Notifications.NotificationEmailAddress;
|
||||
|
||||
@@ -136,6 +142,8 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
|
||||
bool IContentSection.EnableInheritedMediaTypes => EnableInheritedMediaTypes;
|
||||
|
||||
bool IContentSection.StripUdiAttributes => StripUdiAttributes;
|
||||
|
||||
string IContentSection.LoginBackgroundImage => LoginBackgroundImage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
IEnumerable<string> ImageTagAllowedAttributes { get; }
|
||||
|
||||
IEnumerable<IImagingAutoFillUploadField> ImageAutoFillProperties { get; }
|
||||
|
||||
|
||||
string ScriptFolderPath { get; }
|
||||
|
||||
IEnumerable<string> ScriptFileTypes { get; }
|
||||
@@ -66,5 +66,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
|
||||
bool EnableInheritedMediaTypes { get; }
|
||||
|
||||
string LoginBackgroundImage { get; }
|
||||
bool StripUdiAttributes { get; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,11 @@ namespace Umbraco.Core
|
||||
/// Property alias for the Media's file extension.
|
||||
/// </summary>
|
||||
public const string Extension = "umbracoExtension";
|
||||
|
||||
/// <summary>
|
||||
/// The default height/width of an image file if the size can't be determined from the metadata
|
||||
/// </summary>
|
||||
public const int DefaultSize = 200;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
/// </remarks>
|
||||
public const string RecycleBinMediaPathPrefix = "-1,-21,";
|
||||
|
||||
public const int DefaultLabelDataTypeId = -92;
|
||||
public const string UmbracoConnectionName = "umbracoDbDSN";
|
||||
public const string UmbracoUpgradePlanName = "Umbraco.Core";
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A set of tag changes.
|
||||
/// </summary>
|
||||
internal class PropertyTagChange
|
||||
{
|
||||
public ChangeType Type { get; set; }
|
||||
|
||||
public IEnumerable<(string Type, string Tags)> Tags { get; set; }
|
||||
|
||||
public enum ChangeType
|
||||
{
|
||||
Replace,
|
||||
Remove,
|
||||
Merge
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
/// </summary>
|
||||
/// <remarks>Every strongly-typed property set class should inherit from <c>PublishedElementModel</c>
|
||||
/// (or inherit from a class that inherits from... etc.) so they are picked by the factory.</remarks>
|
||||
public class PublishedElementModel : PublishedElementWrapped
|
||||
public abstract class PublishedElementModel : PublishedElementWrapped
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
|
||||
@@ -748,7 +748,7 @@ ORDER BY colName";
|
||||
|
||||
if (excludeUserGroups != null && excludeUserGroups.Length > 0)
|
||||
{
|
||||
var subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id
|
||||
const string subQuery = @"AND (umbracoUser.id NOT IN (SELECT DISTINCT umbracoUser.id
|
||||
FROM umbracoUser
|
||||
INNER JOIN umbracoUser2UserGroup ON umbracoUser2UserGroup.userId = umbracoUser.id
|
||||
INNER JOIN umbracoUserGroup ON umbracoUserGroup.id = umbracoUser2UserGroup.userGroupId
|
||||
@@ -809,7 +809,7 @@ ORDER BY colName";
|
||||
sql = new SqlTranslator<IUser>(sql, query).Translate();
|
||||
|
||||
// get sorted and filtered sql
|
||||
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql), orderDirection, orderBy);
|
||||
var sqlNodeIdsWithSort = ApplySort(ApplyFilter(sql, filterSql, query != null), orderDirection, orderBy);
|
||||
|
||||
// get a page of results and total count
|
||||
var pagedResult = Database.Page<UserDto>(pageIndex + 1, pageSize, sqlNodeIdsWithSort);
|
||||
@@ -820,11 +820,17 @@ ORDER BY colName";
|
||||
return pagedResult.Items.Select(UserFactory.BuildEntity);
|
||||
}
|
||||
|
||||
private Sql<ISqlContext> ApplyFilter(Sql<ISqlContext> sql, Sql<ISqlContext> filterSql)
|
||||
private Sql<ISqlContext> ApplyFilter(Sql<ISqlContext> sql, Sql<ISqlContext> filterSql, bool hasWhereClause)
|
||||
{
|
||||
if (filterSql == null) return sql;
|
||||
|
||||
sql.Append(SqlContext.Sql(" WHERE " + filterSql.SQL.TrimStart("AND "), filterSql.Arguments));
|
||||
//ensure we don't append a WHERE if there is already one
|
||||
var args = filterSql.Arguments;
|
||||
var sqlFilter = hasWhereClause
|
||||
? filterSql.SQL
|
||||
: " WHERE " + filterSql.SQL.TrimStart("AND ");
|
||||
|
||||
sql.Append(SqlContext.Sql(sqlFilter, args));
|
||||
|
||||
return sql;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Security.Principal;
|
||||
@@ -63,5 +65,6 @@ namespace Umbraco.Core.Security
|
||||
/// Used so that we aren't creating a new CultureInfo object for every single request
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, CultureInfo> UserCultures = new ConcurrentDictionary<string, CultureInfo>();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,7 +635,9 @@ namespace Umbraco.Core.Security
|
||||
|| identityUser.LastLoginDateUtc.HasValue && user.LastLoginDate.ToUniversalTime() != identityUser.LastLoginDateUtc.Value)
|
||||
{
|
||||
anythingChanged = true;
|
||||
user.LastLoginDate = identityUser.LastLoginDateUtc.Value.ToLocalTime();
|
||||
//if the LastLoginDate is being set to MinValue, don't convert it ToLocalTime
|
||||
var dt = identityUser.LastLoginDateUtc == DateTime.MinValue ? DateTime.MinValue : identityUser.LastLoginDateUtc.Value.ToLocalTime();
|
||||
user.LastLoginDate = dt;
|
||||
}
|
||||
if (identityUser.IsPropertyDirty("LastPasswordChangeDateUtc")
|
||||
|| (user.LastPasswordChangeDate != default(DateTime) && identityUser.LastPasswordChangeDateUtc.HasValue == false)
|
||||
|
||||
@@ -52,6 +52,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
private readonly IAuditRepository _auditRepository;
|
||||
private readonly IContentTypeRepository _contentTypeRepository;
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private static HttpClient _httpClient;
|
||||
|
||||
public PackagingService(
|
||||
ILogger logger,
|
||||
@@ -1441,7 +1442,6 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns></returns>
|
||||
public string FetchPackageFile(Guid packageId, Version umbracoVersion, int userId)
|
||||
{
|
||||
using (var httpClient = new HttpClient())
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
//includeHidden = true because we don't care if it's hidden we want to get the file regardless
|
||||
@@ -1449,7 +1449,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
byte[] bytes;
|
||||
try
|
||||
{
|
||||
bytes = httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult();
|
||||
if (_httpClient == null)
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
bytes = _httpClient.GetByteArrayAsync(url).GetAwaiter().GetResult();
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
|
||||
@@ -76,11 +76,11 @@ namespace Umbraco.Core.Services.Implement
|
||||
// reload - cheap, cached
|
||||
|
||||
// default role is single server, but if registrations contain more
|
||||
// than one active server, then role is master or slave
|
||||
// than one active server, then role is master or replica
|
||||
regs = _serverRegistrationRepository.GetMany().ToArray();
|
||||
|
||||
// default role is single server, but if registrations contain more
|
||||
// than one active server, then role is master or slave
|
||||
// than one active server, then role is master or replica
|
||||
_currentServerRole = regs.Count(x => x.IsActive) > 1
|
||||
? (server.IsMaster ? ServerRole.Master : ServerRole.Replica)
|
||||
: ServerRole.Single;
|
||||
|
||||
@@ -1384,6 +1384,30 @@ namespace Umbraco.Core
|
||||
return idCheckList.Contains(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a file name to a friendly name for a content item
|
||||
/// </summary>
|
||||
/// <param name="fileName"></param>
|
||||
/// <returns></returns>
|
||||
public static string ToFriendlyName(this string fileName)
|
||||
{
|
||||
// strip the file extension
|
||||
fileName = fileName.StripFileExtension();
|
||||
|
||||
// underscores and dashes to spaces
|
||||
fileName = fileName.ReplaceMany(new[] { '_', '-' }, ' ');
|
||||
|
||||
// any other conversions ?
|
||||
|
||||
// Pascalcase (to be done last)
|
||||
fileName = CultureInfo.InvariantCulture.TextInfo.ToTitleCase(fileName);
|
||||
|
||||
// Replace multiple consecutive spaces with a single space
|
||||
fileName = string.Join(" ", fileName.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries));
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
// From: http://stackoverflow.com/a/961504/5018
|
||||
// filters control characters but allows only properly-formed surrogate sequences
|
||||
private static readonly Lazy<Regex> InvalidXmlChars = new Lazy<Regex>(() =>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
Single = 1,
|
||||
|
||||
/// <summary>
|
||||
/// In a multi-servers environment, the server is a slave server.
|
||||
/// In a multi-servers environment, the server is a replica server.
|
||||
/// </summary>
|
||||
Replica = 2,
|
||||
|
||||
|
||||
@@ -727,7 +727,6 @@
|
||||
<Compile Include="Models\PropertyCollection.cs" />
|
||||
<Compile Include="Models\PropertyGroup.cs" />
|
||||
<Compile Include="Models\PropertyGroupCollection.cs" />
|
||||
<Compile Include="Models\PropertyTagChange.cs" />
|
||||
<Compile Include="Models\PropertyType.cs" />
|
||||
<Compile Include="Models\PropertyTypeCollection.cs" />
|
||||
<Compile Include="Models\PublicAccessEntry.cs" />
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
|
||||
namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest)]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, Logger = UmbracoTestOptions.Logger.Console)]
|
||||
public class AuditRepositoryTest : TestWithDatabaseBase
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void Can_Add_Audit_Entry()
|
||||
{
|
||||
@@ -23,7 +27,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
using (var scope = sp.CreateScope())
|
||||
{
|
||||
var repo = new AuditRepository((IScopeAccessor) sp, CacheHelper, Logger);
|
||||
repo.Save(new AuditItem(-1, "This is a System audit trail", AuditType.System, 0));
|
||||
repo.Save(new AuditItem(-1, "This is a System audit trail", AuditType.System, -1));
|
||||
|
||||
var dtos = scope.Database.Fetch<LogDto>("WHERE id > -1");
|
||||
|
||||
@@ -42,8 +46,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, 0));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, 0));
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
@@ -60,6 +64,49 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items_By_User_Id_With_Query_And_Filter()
|
||||
{
|
||||
var sp = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = sp.CreateScope())
|
||||
{
|
||||
var repo = new AuditRepository((IScopeAccessor)sp, CacheHelper, Logger);
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
using (var scope = sp.CreateScope())
|
||||
{
|
||||
var repo = new AuditRepository((IScopeAccessor)sp, CacheHelper, Logger);
|
||||
|
||||
var query = sp.SqlContext.Query<IAuditItem>().Where(x => x.UserId == -1);
|
||||
|
||||
try
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
|
||||
|
||||
var page = repo.GetPagedResultsByQuery(query, 0, 10, out var total, Direction.Descending,
|
||||
new[] { AuditType.Publish },
|
||||
sp.SqlContext.Query<IAuditItem>().Where(x => x.UserId > -2));
|
||||
|
||||
Assert.AreEqual(10, page.Count());
|
||||
Assert.AreEqual(100, total);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_Paged_Items_With_AuditType_Filter()
|
||||
{
|
||||
@@ -70,8 +117,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, 0));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, 0));
|
||||
repo.Save(new AuditItem(i, $"Content {i} created", AuditType.New, -1));
|
||||
repo.Save(new AuditItem(i, $"Content {i} published", AuditType.Publish, -1));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
@@ -101,8 +148,8 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
for (var i = 0; i < 100; i++)
|
||||
{
|
||||
repo.Save(new AuditItem(i, "Content created", AuditType.New, 0));
|
||||
repo.Save(new AuditItem(i, "Content published", AuditType.Publish, 0));
|
||||
repo.Save(new AuditItem(i, "Content created", AuditType.New, -1));
|
||||
repo.Save(new AuditItem(i, "Content published", AuditType.Publish, -1));
|
||||
}
|
||||
|
||||
scope.Complete();
|
||||
|
||||
@@ -8,17 +8,19 @@ using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.Persistence.Mappers;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Repositories;
|
||||
using Umbraco.Core.Persistence.Repositories.Implement;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Tests.Testing;
|
||||
using Umbraco.Core.Persistence;
|
||||
|
||||
namespace Umbraco.Tests.Persistence.Repositories
|
||||
{
|
||||
[TestFixture]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true)]
|
||||
[UmbracoTest(Database = UmbracoTestOptions.Database.NewSchemaPerTest, WithApplication = true, Logger = UmbracoTestOptions.Logger.Console)]
|
||||
public class UserRepositoryTest : TestWithDatabaseBase
|
||||
{
|
||||
private MediaRepository CreateMediaRepository(IScopeProvider provider, out IMediaTypeRepository mediaTypeRepository)
|
||||
@@ -50,14 +52,14 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
private UserRepository CreateRepository(IScopeProvider provider)
|
||||
{
|
||||
var accessor = (IScopeAccessor) provider;
|
||||
var repository = new UserRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Mock.Of<ILogger>(), Mock.Of<IMapperCollection>(), TestObjects.GetGlobalSettings());
|
||||
var repository = new UserRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Logger, Mappers, TestObjects.GetGlobalSettings());
|
||||
return repository;
|
||||
}
|
||||
|
||||
private UserGroupRepository CreateUserGroupRepository(IScopeProvider provider)
|
||||
{
|
||||
var accessor = (IScopeAccessor) provider;
|
||||
return new UserGroupRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Mock.Of<ILogger>());
|
||||
return new UserGroupRepository(accessor, CacheHelper.CreateDisabledCacheHelper(), Logger);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -338,7 +340,72 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var result = repository.Count(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.GreaterThanOrEqualTo(2));
|
||||
Assert.AreEqual(2, result);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Paged_Results_By_Query_And_Filter_And_Groups()
|
||||
{
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var users = CreateAndCommitMultipleUsers(repository);
|
||||
var query = provider.SqlContext.Query<IUser>().Where(x => x.Username == "TestUser1" || x.Username == "TestUser2");
|
||||
|
||||
try
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
|
||||
|
||||
// Act
|
||||
var result = repository.GetPagedResultsByQuery(query, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
|
||||
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
|
||||
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id > -1));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(2, totalRecs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Can_Get_Paged_Results_With_Filter_And_Groups()
|
||||
{
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = provider.CreateScope())
|
||||
{
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
var users = CreateAndCommitMultipleUsers(repository);
|
||||
|
||||
try
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = true;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = true;
|
||||
|
||||
// Act
|
||||
var result = repository.GetPagedResultsByQuery(null, 0, 10, out var totalRecs, user => user.Id, Direction.Ascending,
|
||||
includeUserGroups: new[] { Constants.Security.AdminGroupAlias, Constants.Security.SensitiveDataGroupAlias },
|
||||
excludeUserGroups: new[] { Constants.Security.TranslatorGroupAlias },
|
||||
filter: provider.SqlContext.Query<IUser>().Where(x => x.Id == -1));
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(1, totalRecs);
|
||||
}
|
||||
finally
|
||||
{
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlTrace = false;
|
||||
scope.Database.AsUmbracoDatabase().EnableSqlCount = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,9 +3,11 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Strings;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.Testing;
|
||||
@@ -30,6 +32,14 @@ namespace Umbraco.Tests.Strings
|
||||
Assert.IsInstanceOf<MockShortStringHelper>(helper);
|
||||
}
|
||||
|
||||
[TestCase("hello-world.png", "Hello World")]
|
||||
[TestCase("hello-world .png", "Hello World")]
|
||||
[TestCase("_hello-world __1.png", "Hello World 1")]
|
||||
public void To_Friendly_Name(string first, string second)
|
||||
{
|
||||
Assert.AreEqual(first.ToFriendlyName(), second);
|
||||
}
|
||||
|
||||
[TestCase("hello", "world", false)]
|
||||
[TestCase("hello", "hello", true)]
|
||||
[TestCase("hellohellohellohellohellohellohello", "hellohellohellohellohellohellohelloo", false)]
|
||||
|
||||
@@ -172,6 +172,11 @@ namespace Umbraco.Tests.Testing
|
||||
Container.RegisterSingleton<ILogger>(f => new SerilogLogger(new FileInfo(TestHelper.MapPathForTest("~/unit-test.config"))));
|
||||
Container.RegisterSingleton<IProfiler>(f => new LogProfiler(f.GetInstance<ILogger>()));
|
||||
}
|
||||
else if (option == UmbracoTestOptions.Logger.Console)
|
||||
{
|
||||
Container.RegisterSingleton<ILogger>(f => new ConsoleLogger());
|
||||
Container.RegisterSingleton<IProfiler>(f => new LogProfiler(f.GetInstance<ILogger>()));
|
||||
}
|
||||
|
||||
Container.RegisterSingleton(f => new ProfilingLogger(f.GetInstance<ILogger>(), f.GetInstance<IProfiler>()));
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
// pure mocks
|
||||
Mock,
|
||||
// Serilog for tests
|
||||
Serilog
|
||||
Serilog,
|
||||
// console logger
|
||||
Console
|
||||
}
|
||||
|
||||
public enum Database
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
<PackageReference Include="AutoMapper" Version="7.0.1" />
|
||||
<PackageReference Include="Castle.Core" Version="4.2.1" />
|
||||
<PackageReference Include="Examine" Version="1.0.0-beta025" />
|
||||
<PackageReference Include="HtmlAgilityPack">
|
||||
<Version>1.8.9</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="LightInject" Version="5.1.2" />
|
||||
<PackageReference Include="LightInject.Annotation" Version="1.1.0" />
|
||||
<PackageReference Include="Lucene.Net" Version="3.0.3" />
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using LightInject;
|
||||
using HtmlAgilityPack;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
@@ -12,7 +12,6 @@ using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.Stubs;
|
||||
using Umbraco.Tests.Testing.Objects.Accessors;
|
||||
@@ -21,6 +20,8 @@ using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.Routing;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.Templates;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Services;
|
||||
|
||||
namespace Umbraco.Tests.Web
|
||||
{
|
||||
@@ -62,6 +63,14 @@ namespace Umbraco.Tests.Web
|
||||
[TestCase("hello href=\"{localLink:umb://document-type/9931BDE0AAC34BABB838909A7B47570E}\" world ", "hello href=\"/my-test-url\" world ")]
|
||||
//this one has an invalid char so won't match
|
||||
[TestCase("hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ", "hello href=\"{localLink:umb^://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\" world ")]
|
||||
// with a-tag with data-udi attribute, that needs to be stripped
|
||||
[TestCase("hello <a data-udi=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\"> world</a> ", "hello <a href=\"/my-test-url\"> world</a> ")]
|
||||
// with a-tag with data-udi attribute spelled wrong, so don't need stripping
|
||||
[TestCase("hello <a data-uid=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" href=\"{localLink:umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570E}\"> world</a> ", "hello <a data-uid=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" href=\"/my-test-url\"> world</a> ")]
|
||||
// with a img-tag with data-udi id, that needs to be strippde
|
||||
[TestCase("hello <img data-udi=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" src=\"imageofcats.jpg\"> world ", "hello <img src=\"imageofcats.jpg\"> world ")]
|
||||
// with a img-tag with data-udi id spelled wrong, so don't need stripping
|
||||
[TestCase("hello <img data-uid=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" src=\"imageofcats.jpg\"> world ", "hello <img data-uid=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" src=\"imageofcats.jpg\"> world ")]
|
||||
public void ParseLocalLinks(string input, string result)
|
||||
{
|
||||
var serviceCtxMock = new TestObjects(null).GetServiceContextMock();
|
||||
@@ -102,7 +111,7 @@ namespace Umbraco.Tests.Web
|
||||
//setup a quick mock of the WebRouting section
|
||||
Mock.Of<IUmbracoSettingsSection>(section => section.WebRouting == Mock.Of<IWebRoutingSection>(routingSection => routingSection.UrlProviderMode == "AutoLegacy")),
|
||||
//pass in the custom url provider
|
||||
new[]{ testUrlProvider.Object },
|
||||
new[] { testUrlProvider.Object },
|
||||
globalSettings,
|
||||
new TestVariationContextAccessor(),
|
||||
true))
|
||||
@@ -112,5 +121,27 @@ namespace Umbraco.Tests.Web
|
||||
Assert.AreEqual(result, output);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StripDataUdiAttributesUsingSrtringOnLinks()
|
||||
{
|
||||
var input = "hello <a data-udi=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" href=\"/my-test-url\"> world</a> ";
|
||||
var expected = "hello <a href=\"/my-test-url\"> world</a> ";
|
||||
|
||||
var result = TemplateUtilities.StripUdiDataAttributes(input);
|
||||
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void StripDataUdiAttributesUsingStringOnImages()
|
||||
{
|
||||
var input = "hello <img data-udi=\"umb://document-type/9931BDE0-AAC3-4BAB-B838-909A7B47570\" src=\"imageofcats.jpg\"> world ";
|
||||
var expected = "hello <img src=\"imageofcats.jpg\"> world ";
|
||||
|
||||
var result = TemplateUtilities.StripUdiDataAttributes(input);
|
||||
|
||||
Assert.AreEqual(expected, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2235
-2237
File diff suppressed because it is too large
Load Diff
@@ -1,19 +1,4 @@
|
||||
{
|
||||
"author": "Umbraco HQ",
|
||||
"name": "umbraco",
|
||||
"homepage": "https://github.com/umbraco/umbraco-cms/",
|
||||
"version": "0.0.0",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/umbraco/Umbraco-CMS.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://issues.umbraco.org"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.4"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "bower-installer",
|
||||
"test": "karma start test/config/karma.conf.js --singlerun",
|
||||
|
||||
+53
-22
@@ -41,10 +41,17 @@
|
||||
//We fetch all ancestors of the node to generate the footer breadcrumb navigation
|
||||
if (!$scope.page.isNew) {
|
||||
if (content.parentId && content.parentId !== -1) {
|
||||
entityResource.getAncestors(content.id, "document")
|
||||
entityResource.getAncestors(content.id, "document", $scope.culture)
|
||||
.then(function (anc) {
|
||||
$scope.ancestors = anc;
|
||||
});
|
||||
$scope.$watch('culture',
|
||||
function(value, oldValue) {
|
||||
entityResource.getAncestors(content.id, "document", value)
|
||||
.then(function (anc) {
|
||||
$scope.ancestors = anc;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,10 +157,19 @@
|
||||
if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") {
|
||||
$scope.defaultButton = null;
|
||||
$scope.subButtons = null;
|
||||
$scope.page.showSaveButton = false;
|
||||
$scope.page.showPreviewButton = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// create the save button
|
||||
if(_.contains($scope.content.allowedActions, "A")) {
|
||||
$scope.page.showSaveButton = true;
|
||||
// add ellipsis to the save button if it opens the variant overlay
|
||||
$scope.page.saveButtonEllipsis = content.variants && content.variants.length > 1 ? "true" : "false";
|
||||
}
|
||||
|
||||
// create the pubish combo button
|
||||
$scope.page.buttonGroupState = "init";
|
||||
var buttons = contentEditingHelper.configureContentEditorButtons({
|
||||
create: $scope.page.isNew,
|
||||
@@ -161,7 +177,6 @@
|
||||
methods: {
|
||||
saveAndPublish: $scope.saveAndPublish,
|
||||
sendToPublish: $scope.sendToPublish,
|
||||
save: $scope.save,
|
||||
unPublish: $scope.unPublish
|
||||
}
|
||||
});
|
||||
@@ -218,9 +233,7 @@
|
||||
|
||||
// This is a helper method to reduce the amount of code repitition for actions: Save, Publish, SendToPublish
|
||||
function performSave(args) {
|
||||
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
|
||||
|
||||
eventsService.emit("content.saving", { content: $scope.content, action: args.action });
|
||||
|
||||
return contentEditingHelper.contentEditorPerformSave({
|
||||
@@ -235,8 +248,6 @@
|
||||
|
||||
syncTreeNode($scope.content, data.path);
|
||||
|
||||
$scope.page.buttonGroupState = "success";
|
||||
|
||||
eventsService.emit("content.saved", { content: $scope.content, action: args.action });
|
||||
|
||||
return $q.when(data);
|
||||
@@ -250,8 +261,6 @@
|
||||
editorState.set($scope.content);
|
||||
}
|
||||
|
||||
$scope.page.buttonGroupState = "error";
|
||||
|
||||
return $q.reject(err);
|
||||
});
|
||||
}
|
||||
@@ -444,13 +453,20 @@
|
||||
}
|
||||
}
|
||||
else {
|
||||
return performSave({ saveMethod: contentResource.sendToPublish, action: "sendToPublish" });
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
return performSave({
|
||||
saveMethod: contentResource.sendToPublish,
|
||||
action: "sendToPublish"
|
||||
}).then(function(){
|
||||
$scope.page.buttonGroupState = "success";
|
||||
}, function () {
|
||||
$scope.page.buttonGroupState = "error";
|
||||
});;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.saveAndPublish = function () {
|
||||
clearNotifications($scope.content);
|
||||
// TODO: Add "..." to publish button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
|
||||
if (showSaveOrPublishDialog()) {
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "publish" })) {
|
||||
@@ -496,7 +512,15 @@
|
||||
else {
|
||||
//ensure the publish flag is set
|
||||
$scope.content.variants[0].publish = true;
|
||||
return performSave({ saveMethod: contentResource.publish, action: "publish" });
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
return performSave({
|
||||
saveMethod: contentResource.publish,
|
||||
action: "publish"
|
||||
}).then(function(){
|
||||
$scope.page.buttonGroupState = "success";
|
||||
}, function () {
|
||||
$scope.page.buttonGroupState = "error";
|
||||
});;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -527,15 +551,14 @@
|
||||
clearNotifications($scope.content);
|
||||
overlayService.close();
|
||||
return $q.when(data);
|
||||
},
|
||||
function (err) {
|
||||
clearDirtyState($scope.content.variants);
|
||||
model.submitButtonState = "error";
|
||||
//re-map the dialog model since we've re-bound the properties
|
||||
dialog.variants = $scope.content.variants;
|
||||
//don't reject, we've handled the error
|
||||
return $q.when(err);
|
||||
});
|
||||
}, function (err) {
|
||||
clearDirtyState($scope.content.variants);
|
||||
model.submitButtonState = "error";
|
||||
//re-map the dialog model since we've re-bound the properties
|
||||
dialog.variants = $scope.content.variants;
|
||||
//don't reject, we've handled the error
|
||||
return $q.when(err);
|
||||
});
|
||||
},
|
||||
close: function (oldModel) {
|
||||
overlayService.close();
|
||||
@@ -546,7 +569,15 @@
|
||||
}
|
||||
}
|
||||
else {
|
||||
return performSave({ saveMethod: $scope.saveMethod(), action: "save" });
|
||||
$scope.page.saveButtonState = "busy";
|
||||
return performSave({
|
||||
saveMethod: $scope.saveMethod(),
|
||||
action: "save"
|
||||
}).then(function(){
|
||||
$scope.page.saveButtonState = "success";
|
||||
}, function () {
|
||||
$scope.page.saveButtonState = "error";
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
+31
-7
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService, dateHelper, editorService) {
|
||||
function ContentNodeInfoDirective($timeout, $location, logResource, eventsService, userService, localizationService, dateHelper, editorService, redirectUrlsResource) {
|
||||
|
||||
function link(scope, element, attrs, ctrl) {
|
||||
|
||||
@@ -72,6 +72,9 @@
|
||||
// make sure dates are formatted to the user's locale
|
||||
formatDatesToLocal();
|
||||
|
||||
//default setting for redirect url management
|
||||
scope.urlTrackerDisabled = false;
|
||||
|
||||
// Declare a fallback URL for the <umb-node-preview/> directive
|
||||
if (scope.documentType !== null) {
|
||||
scope.previewOpenUrl = '#/settings/documenttypes/edit/' + scope.documentType.id;
|
||||
@@ -139,7 +142,7 @@
|
||||
|
||||
// get current backoffice user and format dates
|
||||
userService.getCurrentUser().then(function (currentUser) {
|
||||
angular.forEach(data.items, function(item) {
|
||||
angular.forEach(data.items, function (item) {
|
||||
item.timestampFormatted = dateHelper.getLocalDate(item.timestamp, currentUser.locale, 'LLL');
|
||||
});
|
||||
});
|
||||
@@ -156,6 +159,25 @@
|
||||
});
|
||||
|
||||
}
|
||||
function loadRedirectUrls() {
|
||||
scope.loadingRedirectUrls = true;
|
||||
//check if Redirect Url Management is enabled
|
||||
redirectUrlsResource.getEnableState().then(function (response) {
|
||||
scope.urlTrackerDisabled = response.enabled !== true;
|
||||
if (scope.urlTrackerDisabled === false) {
|
||||
|
||||
redirectUrlsResource.getRedirectsForContentItem(scope.node.udi)
|
||||
.then(function (data) {
|
||||
scope.redirectUrls = data.searchResults;
|
||||
scope.hasRedirects = (typeof data.searchResults !== 'undefined' && data.searchResults.length > 0);
|
||||
scope.loadingRedirectUrls = false;
|
||||
});
|
||||
}
|
||||
else {
|
||||
scope.loadingRedirectUrls = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setAuditTrailLogTypeColor(auditTrail) {
|
||||
angular.forEach(auditTrail, function (item) {
|
||||
@@ -312,12 +334,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
// load audit trail when on the info tab
|
||||
// load audit trail and redirects when on the info tab
|
||||
evts.push(eventsService.on("app.tabChange", function (event, args) {
|
||||
$timeout(function(){
|
||||
$timeout(function () {
|
||||
if (args.alias === "umbInfo") {
|
||||
isInfoTab = true;
|
||||
loadAuditTrail();
|
||||
loadRedirectUrls();
|
||||
} else {
|
||||
isInfoTab = false;
|
||||
}
|
||||
@@ -325,13 +348,14 @@
|
||||
}));
|
||||
|
||||
// watch for content state updates
|
||||
scope.$watch('node.updateDate', function(newValue, oldValue){
|
||||
scope.$watch('node.updateDate', function (newValue, oldValue) {
|
||||
|
||||
if(!newValue) { return; }
|
||||
if(newValue === oldValue) { return; }
|
||||
if (!newValue) { return; }
|
||||
if (newValue === oldValue) { return; }
|
||||
|
||||
if(isInfoTab) {
|
||||
loadAuditTrail();
|
||||
loadRedirectUrls();
|
||||
formatDatesToLocal();
|
||||
setNodePublishStatus(scope.node);
|
||||
}
|
||||
|
||||
+29
-25
@@ -11,32 +11,36 @@ function hexBgColor() {
|
||||
restrict: "A",
|
||||
link: function (scope, element, attr, formCtrl) {
|
||||
|
||||
var origColor = null;
|
||||
if (attr.hexBgOrig) {
|
||||
//set the orig based on the attribute if there is one
|
||||
origColor = attr.hexBgOrig;
|
||||
}
|
||||
|
||||
attr.$observe("hexBgColor", function (newVal) {
|
||||
if (newVal) {
|
||||
if (!origColor) {
|
||||
//get the orig color before changing it
|
||||
origColor = element.css("border-color");
|
||||
}
|
||||
//validate it - test with and without the leading hash.
|
||||
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", "#" + newVal);
|
||||
return;
|
||||
}
|
||||
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", newVal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
element.css("background-color", origColor);
|
||||
});
|
||||
// Only add inline hex background color if defined and not "true".
|
||||
if (attr.hexBgInline === undefined || (attr.hexBgInline !== undefined && attr.hexBgInline === "true")) {
|
||||
|
||||
var origColor = null;
|
||||
if (attr.hexBgOrig) {
|
||||
// Set the orig based on the attribute if there is one.
|
||||
origColor = attr.hexBgOrig;
|
||||
}
|
||||
|
||||
attr.$observe("hexBgColor", function (newVal) {
|
||||
if (newVal) {
|
||||
if (!origColor) {
|
||||
// Get the orig color before changing it.
|
||||
origColor = element.css("border-color");
|
||||
}
|
||||
// Validate it - test with and without the leading hash.
|
||||
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", "#" + newVal);
|
||||
return;
|
||||
}
|
||||
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
|
||||
element.css("background-color", newVal);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
element.css("background-color", origColor);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
angular.module('umbraco.directives').directive("hexBgColor", hexBgColor);
|
||||
angular.module('umbraco.directives').directive("hexBgColor", hexBgColor);
|
||||
|
||||
+15
-3
@@ -41,7 +41,6 @@
|
||||
};
|
||||
|
||||
function setFocalPoint (event) {
|
||||
|
||||
$scope.$emit("imageFocalPointStart");
|
||||
|
||||
var offsetX = event.offsetX - 10;
|
||||
@@ -50,7 +49,6 @@
|
||||
calculateGravity(offsetX, offsetY);
|
||||
|
||||
lazyEndEvent();
|
||||
|
||||
};
|
||||
|
||||
/** Initializes the component */
|
||||
@@ -148,12 +146,26 @@
|
||||
|
||||
/** Sets the width/height/left/top dimentions based on the image size and the "center" value */
|
||||
function setDimensions() {
|
||||
if (htmlImage && vm.center) {
|
||||
|
||||
if (vm.src.endsWith(".svg")) {
|
||||
// svg files don't automatically get a size by
|
||||
// loading them set a default size for now
|
||||
vm.dimensions.width = 200;
|
||||
vm.dimensions.height = 200;
|
||||
vm.dimensions.left = vm.center.left * vm.dimensions.width - 10;
|
||||
vm.dimensions.top = vm.center.top * vm.dimensions.height - 10;
|
||||
// can't crop an svg file, don't show the focal point
|
||||
if (htmlOverlay) {
|
||||
htmlOverlay.remove();
|
||||
}
|
||||
}
|
||||
else if (htmlImage && vm.center) {
|
||||
vm.dimensions.width = htmlImage.width();
|
||||
vm.dimensions.height = htmlImage.height();
|
||||
vm.dimensions.left = vm.center.left * vm.dimensions.width - 10;
|
||||
vm.dimensions.top = vm.center.top * vm.dimensions.height - 10;
|
||||
}
|
||||
|
||||
return vm.dimensions.width;
|
||||
};
|
||||
|
||||
|
||||
+9
-1
@@ -73,7 +73,15 @@ angular.module("umbraco.directives")
|
||||
if (node.selected) {
|
||||
css.push("umb-tree-node-checked");
|
||||
}
|
||||
|
||||
|
||||
//is this the current action node (this is not the same as the current selected node!)
|
||||
var actionNode = appState.getMenuState("currentNode");
|
||||
if (actionNode) {
|
||||
if (actionNode.id === node.id) {
|
||||
css.push("active");
|
||||
}
|
||||
}
|
||||
|
||||
return css.join(" ");
|
||||
};
|
||||
|
||||
|
||||
+11
-4
@@ -1,5 +1,4 @@
|
||||
|
||||
/**
|
||||
/**
|
||||
@ngdoc directive
|
||||
@name umbraco.directives.directive:umbColorSwatches
|
||||
@restrict E
|
||||
@@ -15,9 +14,10 @@ Use this directive to generate color swatches to pick from.
|
||||
</umb-color-swatches>
|
||||
</pre>
|
||||
@param {array} colors (<code>attribute</code>): The array of colors.
|
||||
@param {string} colors (<code>attribute</code>): The array of colors.
|
||||
@param {string} selectedColor (<code>attribute</code>): The selected color.
|
||||
@param {string} size (<code>attribute</code>): The size (s, m).
|
||||
@param {string} useLabel (<code>attribute</code>): Specify if labels should be used.
|
||||
@param {string} useColorClass (<code>attribute</code>): Specify if color values are css classes.
|
||||
@param {function} onSelect (<code>expression</code>): Callback function when the item is selected.
|
||||
**/
|
||||
|
||||
@@ -28,6 +28,11 @@ Use this directive to generate color swatches to pick from.
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
// Set default to true if not defined
|
||||
if (angular.isUndefined(scope.useColorClass)) {
|
||||
scope.useColorClass = false;
|
||||
}
|
||||
|
||||
scope.setColor = function (color) {
|
||||
scope.selectedColor = color;
|
||||
if (scope.onSelect) {
|
||||
@@ -45,7 +50,9 @@ Use this directive to generate color swatches to pick from.
|
||||
colors: '=?',
|
||||
size: '@',
|
||||
selectedColor: '=',
|
||||
onSelect: '&'
|
||||
onSelect: '&',
|
||||
useLabel: '=',
|
||||
useColorClass: '=?'
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
+11
-1
@@ -125,6 +125,14 @@ Use this directive to generate a thumbnail grid of media items.
|
||||
i--;
|
||||
}
|
||||
|
||||
if (scope.includeSubFolders !== 'true') {
|
||||
if (item.parentId !== parseInt(scope.currentFolderId)) {
|
||||
scope.items.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (scope.items.length > 0) {
|
||||
@@ -316,7 +324,9 @@ Use this directive to generate a thumbnail grid of media items.
|
||||
itemMaxHeight: "@",
|
||||
itemMinWidth: "@",
|
||||
itemMinHeight: "@",
|
||||
onlyImages: "@"
|
||||
onlyImages: "@",
|
||||
includeSubFolders: "@",
|
||||
currentFolderId: "@"
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
+2
-1
@@ -221,7 +221,8 @@
|
||||
isClientSide: true
|
||||
});
|
||||
|
||||
newVal += files[i].name + ",";
|
||||
//special check for a comma in the name
|
||||
newVal += files[i].name.replace(',', '-') + ",";
|
||||
|
||||
if (isImage) {
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
(function () {
|
||||
/**
|
||||
* @ngdoc directive
|
||||
* @name umbraco.directives.directive:multi
|
||||
* @restrict A
|
||||
* @description Used on input fields when you want to validate multiple fields at once.
|
||||
**/
|
||||
function multi($parse, $rootScope) {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function (scope, elem, attrs, ngModelCtrl) {
|
||||
var validate = $parse(attrs.multi)(scope);
|
||||
ngModelCtrl.$viewChangeListeners.push(function () {
|
||||
// ngModelCtrl.$setValidity('multi', validate());
|
||||
$rootScope.$broadcast('multi:valueChanged');
|
||||
});
|
||||
|
||||
var deregisterListener = scope.$on('multi:valueChanged', function (event) {
|
||||
ngModelCtrl.$setValidity('multi', validate());
|
||||
});
|
||||
scope.$on('$destroy', deregisterListener); // optional, only required for $rootScope.$on
|
||||
}
|
||||
};
|
||||
}
|
||||
angular.module('umbraco.directives.validation').directive('multi', ['$parse', '$rootScope', multi]);
|
||||
})();
|
||||
@@ -242,6 +242,44 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
{ id: id, culture: culture })),
|
||||
'Failed to publish content with id ' + id);
|
||||
},
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.contentResource#getCultureAndDomains
|
||||
* @methodOf umbraco.resources.contentResource
|
||||
*
|
||||
* @description
|
||||
* Gets the culture and hostnames for a content item with the given Id
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
* contentResource.getCultureAndDomains(1234)
|
||||
* .then(function(data) {
|
||||
* alert(data.Domains, data.Language);
|
||||
* });
|
||||
* </pre>
|
||||
* @param {Int} id the ID of the node to get the culture and domains for.
|
||||
* @returns {Promise} resourcePromise object.
|
||||
*
|
||||
*/
|
||||
getCultureAndDomains: function (id) {
|
||||
if (!id) {
|
||||
throw "id cannot be null";
|
||||
}
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetCultureAndDomains", { id: id })),
|
||||
'Failed to retreive culture and hostnames for ' + id);
|
||||
},
|
||||
saveLanguageAndDomains: function (model) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.post(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"PostSaveLanguageAndDomains"),
|
||||
model));
|
||||
},
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.contentResource#emptyRecycleBin
|
||||
@@ -334,26 +372,26 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
*/
|
||||
getById: function (id) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetById",
|
||||
{ id: id })),
|
||||
'Failed to retrieve data for content id ' + id)
|
||||
.then(function(result) {
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetById",
|
||||
{ id: id })),
|
||||
'Failed to retrieve data for content id ' + id)
|
||||
.then(function (result) {
|
||||
return $q.when(umbDataFormatter.formatContentGetData(result));
|
||||
});
|
||||
},
|
||||
|
||||
getBlueprintById: function (id) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetBlueprintById",
|
||||
[{ id: id }])),
|
||||
'Failed to retrieve data for content id ' + id)
|
||||
.then(function(result) {
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetBlueprintById",
|
||||
[{ id: id }])),
|
||||
'Failed to retrieve data for content id ' + id)
|
||||
.then(function (result) {
|
||||
return $q.when(umbDataFormatter.formatContentGetData(result));
|
||||
});
|
||||
},
|
||||
@@ -410,15 +448,15 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
});
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetByIds",
|
||||
idQuery)),
|
||||
'Failed to retrieve data for content with multiple ids')
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetByIds",
|
||||
idQuery)),
|
||||
'Failed to retrieve data for content with multiple ids')
|
||||
.then(function (result) {
|
||||
//each item needs to be re-formatted
|
||||
_.each(result, function(r) {
|
||||
_.each(result, function (r) {
|
||||
umbDataFormatter.formatContentGetData(r)
|
||||
});
|
||||
return $q.when(result);
|
||||
@@ -461,13 +499,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
getScaffold: function (parentId, alias) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetEmpty",
|
||||
[{ contentTypeAlias: alias }, { parentId: parentId }])),
|
||||
'Failed to retrieve data for empty content item type ' + alias)
|
||||
.then(function(result) {
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetEmpty",
|
||||
[{ contentTypeAlias: alias }, { parentId: parentId }])),
|
||||
'Failed to retrieve data for empty content item type ' + alias)
|
||||
.then(function (result) {
|
||||
return $q.when(umbDataFormatter.formatContentGetData(result));
|
||||
});
|
||||
},
|
||||
@@ -475,13 +513,13 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
|
||||
getBlueprintScaffold: function (parentId, blueprintId) {
|
||||
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetEmpty",
|
||||
[{ blueprintId: blueprintId }, { parentId: parentId }])),
|
||||
'Failed to retrieve blueprint for id ' + blueprintId)
|
||||
.then(function(result) {
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"contentApiBaseUrl",
|
||||
"GetEmpty",
|
||||
[{ blueprintId: blueprintId }, { parentId: parentId }])),
|
||||
'Failed to retrieve blueprint for id ' + blueprintId)
|
||||
.then(function (result) {
|
||||
return $q.when(umbDataFormatter.formatContentGetData(result));
|
||||
});
|
||||
},
|
||||
|
||||
@@ -288,17 +288,19 @@ function entityResource($q, $http, umbRequestHelper) {
|
||||
* Gets ancestor entities for a given item
|
||||
*
|
||||
*
|
||||
* @param {string} type Object type name
|
||||
* @param {string} type Object type name
|
||||
* @param {string} culture Culture
|
||||
* @returns {Promise} resourcePromise object containing the entity.
|
||||
*
|
||||
*/
|
||||
getAncestors: function (id, type) {
|
||||
getAncestors: function (id, type, culture) {
|
||||
if (culture === undefined) culture = "";
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"entityApiBaseUrl",
|
||||
"GetAncestors",
|
||||
[{id: id}, {type: type}])),
|
||||
[{ id: id }, { type: type }, { culture: culture }])),
|
||||
'Failed to retrieve ancestor data for id ' + id);
|
||||
},
|
||||
|
||||
|
||||
@@ -7,10 +7,60 @@
|
||||
**/
|
||||
function logResource($q, $http, umbRequestHelper) {
|
||||
|
||||
function isValidDate(input) {
|
||||
if (input) {
|
||||
if (Object.prototype.toString.call(input) === "[object Date]" && !isNaN(input.getTime())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
function dateToValidIsoString(input) {
|
||||
if (isValidDate(input)) {
|
||||
return input.toISOString();
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
//the factory object returned
|
||||
return {
|
||||
|
||||
getPagedEntityLog: function (options) {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.logResource#getPagedEntityLog
|
||||
* @methodOf umbraco.resources.logResource
|
||||
*
|
||||
* @description
|
||||
* Gets a paginated log history for a entity
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
* var options = {
|
||||
* id : 1234
|
||||
* pageSize : 10,
|
||||
* pageNumber : 1,
|
||||
* orderDirection : "Descending",
|
||||
* sinceDate : new Date(2018,0,1)
|
||||
* };
|
||||
* logResource.getPagedEntityLog(options)
|
||||
* .then(function(log) {
|
||||
* alert('its here!');
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param {Object} options options object
|
||||
* @param {Int} options.id the id of the entity
|
||||
* @param {Int} options.pageSize if paging data, number of nodes per page, default = 10, set to 0 to disable paging
|
||||
* @param {Int} options.pageNumber if paging data, current page index, default = 1
|
||||
* @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Descending`
|
||||
* @param {Date} options.sinceDate if provided this will only get log entries going back to this date
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getPagedEntityLog: function(options) {
|
||||
|
||||
var defaults = {
|
||||
pageSize: 10,
|
||||
@@ -24,11 +74,15 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
angular.extend(defaults, options);
|
||||
//now copy back to the options we will use
|
||||
options = defaults;
|
||||
|
||||
if (options.hasOwnProperty('sinceDate')) {
|
||||
options.sinceDate = dateToValidIsoString(options.sinceDate);
|
||||
}
|
||||
|
||||
//change asc/desct
|
||||
if (options.orderDirection === "asc") {
|
||||
options.orderDirection = "Ascending";
|
||||
}
|
||||
else if (options.orderDirection === "desc") {
|
||||
} else if (options.orderDirection === "desc") {
|
||||
options.orderDirection = "Descending";
|
||||
}
|
||||
|
||||
@@ -45,7 +99,37 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
'Failed to retrieve log data for id');
|
||||
},
|
||||
|
||||
getPagedUserLog: function (options) {
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.logResource#getPagedUserLog
|
||||
* @methodOf umbraco.resources.logResource
|
||||
*
|
||||
* @description
|
||||
* Gets a paginated log history for the current user
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
* var options = {
|
||||
* pageSize : 10,
|
||||
* pageNumber : 1,
|
||||
* orderDirection : "Descending",
|
||||
* sinceDate : new Date(2018,0,1)
|
||||
* };
|
||||
* logResource.getPagedUserLog(options)
|
||||
* .then(function(log) {
|
||||
* alert('its here!');
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
* @param {Object} options options object
|
||||
* @param {Int} options.pageSize if paging data, number of nodes per page, default = 10, set to 0 to disable paging
|
||||
* @param {Int} options.pageNumber if paging data, current page index, default = 1
|
||||
* @param {String} options.orderDirection can be `Ascending` or `Descending` - Default: `Descending`
|
||||
* @param {Date} options.sinceDate if provided this will only get log entries going back to this date
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getPagedUserLog: function(options) {
|
||||
|
||||
var defaults = {
|
||||
pageSize: 10,
|
||||
@@ -59,11 +143,15 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
angular.extend(defaults, options);
|
||||
//now copy back to the options we will use
|
||||
options = defaults;
|
||||
|
||||
if (options.hasOwnProperty('sinceDate')) {
|
||||
options.sinceDate = dateToValidIsoString(options.sinceDate);
|
||||
}
|
||||
|
||||
//change asc/desct
|
||||
if (options.orderDirection === "asc") {
|
||||
options.orderDirection = "Ascending";
|
||||
}
|
||||
else if (options.orderDirection === "desc") {
|
||||
} else if (options.orderDirection === "desc") {
|
||||
options.orderDirection = "Descending";
|
||||
}
|
||||
|
||||
@@ -71,7 +159,7 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetPagedEntityLog",
|
||||
"GetPagedCurrentUserLog",
|
||||
options)),
|
||||
'Failed to retrieve log data for id');
|
||||
},
|
||||
@@ -82,6 +170,7 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
* @methodOf umbraco.resources.logResource
|
||||
*
|
||||
* @description
|
||||
* <strong>[OBSOLETE] use getPagedEntityLog instead</strong><br />
|
||||
* Gets the log history for a give entity id
|
||||
*
|
||||
* ##usage
|
||||
@@ -96,23 +185,24 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getEntityLog: function (id) {
|
||||
getEntityLog: function(id) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetEntityLog",
|
||||
[{ id: id }])),
|
||||
'Failed to retrieve user data for id ' + id);
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetEntityLog",
|
||||
[{ id: id }])),
|
||||
'Failed to retrieve user data for id ' + id);
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.resources.logResource#getUserLog
|
||||
* @methodOf umbraco.resources.logResource
|
||||
*
|
||||
* @description
|
||||
* Gets the current users' log history for a given type of log entry
|
||||
* <strong>[OBSOLETE] use getPagedUserLog instead</strong><br />
|
||||
* Gets the current user's log history for a given type of log entry
|
||||
*
|
||||
* ##usage
|
||||
* <pre>
|
||||
@@ -127,14 +217,14 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getUserLog: function (type, since) {
|
||||
getUserLog: function(type, since) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetCurrentUserLog",
|
||||
[{ logtype: type}, {sinceDate: since }])),
|
||||
'Failed to retrieve log data for current user of type ' + type + ' since ' + since);
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetCurrentUserLog",
|
||||
[{ logtype: type }, { sinceDate: dateToValidIsoString(since) }])),
|
||||
'Failed to retrieve log data for current user of type ' + type + ' since ' + since);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -158,16 +248,16 @@ function logResource($q, $http, umbRequestHelper) {
|
||||
* @returns {Promise} resourcePromise object containing the log.
|
||||
*
|
||||
*/
|
||||
getLog: function (type, since) {
|
||||
getLog: function(type, since) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetLog",
|
||||
[{ logtype: type}, {sinceDate: since }])),
|
||||
'Failed to retrieve log data of type ' + type + ' since ' + since);
|
||||
}
|
||||
};
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"logApiBaseUrl",
|
||||
"GetLog",
|
||||
[{ logtype: type }, { sinceDate: dateToValidIsoString(since) }])),
|
||||
'Failed to retrieve log data of type ' + type + ' since ' + since);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
angular.module('umbraco.resources').factory('logResource', logResource);
|
||||
|
||||
@@ -40,6 +40,32 @@
|
||||
{ searchTerm: searchTerm, page: pageIndex, pageSize: pageSize })),
|
||||
'Failed to retrieve data for searching redirect urls');
|
||||
}
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name umbraco.resources.redirectUrlResource#getRedirectsForContentItem
|
||||
* @methodOf umbraco.resources.redirectUrlResource
|
||||
* @function
|
||||
*
|
||||
* @description
|
||||
* Used to retrieve RedirectUrls for a specific item of content for Information tab
|
||||
* ##usage
|
||||
* <pre>
|
||||
* redirectUrlsResource.getRedirectsForContentItem("udi:123456")
|
||||
* .then(function(response) {
|
||||
*
|
||||
* });
|
||||
* </pre>
|
||||
* @param {String} contentUdi identifier for the content item to retrieve redirects for
|
||||
*/
|
||||
function getRedirectsForContentItem(contentUdi) {
|
||||
return umbRequestHelper.resourcePromise(
|
||||
$http.get(
|
||||
umbRequestHelper.getApiUrl(
|
||||
"redirectUrlManagementApiBaseUrl",
|
||||
"RedirectUrlsForContentItem",
|
||||
{ contentUdi: contentUdi })),
|
||||
'Failed to retrieve redirects for content: ' + contentUdi);
|
||||
}
|
||||
|
||||
function getEnableState() {
|
||||
|
||||
@@ -50,7 +76,7 @@
|
||||
"GetEnableState")),
|
||||
'Failed to retrieve data to check if the 301 redirect is enabled');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc function
|
||||
* @name umbraco.resources.redirectUrlResource#deleteRedirectUrl
|
||||
@@ -107,7 +133,8 @@
|
||||
searchRedirectUrls: searchRedirectUrls,
|
||||
deleteRedirectUrl: deleteRedirectUrl,
|
||||
toggleUrlTracker: toggleUrlTracker,
|
||||
getEnableState: getEnableState
|
||||
getEnableState: getEnableState,
|
||||
getRedirectsForContentItem: getRedirectsForContentItem
|
||||
};
|
||||
|
||||
return resource;
|
||||
|
||||
@@ -65,17 +65,14 @@ angular.module('umbraco.services')
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads in moment.js requirements during the _loadInitAssets call
|
||||
*/
|
||||
function loadMomentLocaleForCurrentUser() {
|
||||
function getMomentLocales(locales, supportedLocales) {
|
||||
|
||||
var self = this;
|
||||
|
||||
function loadLocales(currentUser, supportedLocales) {
|
||||
var locale = currentUser.locale.toLowerCase();
|
||||
var localeUrls = [];
|
||||
var locales = locales.split(',');
|
||||
for (var i = 0; i < locales.length; i++) {
|
||||
var locale = locales[i].toString().toLowerCase();
|
||||
if (locale !== 'en-us') {
|
||||
var localeUrls = [];
|
||||
|
||||
if (supportedLocales.indexOf(locale + '.js') > -1) {
|
||||
localeUrls.push('lib/moment/' + locale + '.js');
|
||||
}
|
||||
@@ -85,16 +82,35 @@ angular.module('umbraco.services')
|
||||
localeUrls.push('lib/moment/' + majorLocale);
|
||||
}
|
||||
}
|
||||
return self.load(localeUrls, $rootScope);
|
||||
}
|
||||
else {
|
||||
$q.when(true);
|
||||
}
|
||||
}
|
||||
|
||||
return localeUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads specific Moment.js Locales.
|
||||
* @param {any} locales
|
||||
* @param {any} supportedLocales
|
||||
*/
|
||||
function loadLocales(locales, supportedLocales) {
|
||||
var localeUrls = getMomentLocales(locales, supportedLocales);
|
||||
if (localeUrls.length >= 1) {
|
||||
return assetsService.load(localeUrls, $rootScope);
|
||||
}
|
||||
else {
|
||||
$q.when(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads in moment.js requirements during the _loadInitAssets call
|
||||
*/
|
||||
function loadMomentLocaleForCurrentUser() {
|
||||
|
||||
userService.getCurrentUser().then(function (currentUser) {
|
||||
return javascriptLibraryResource.getSupportedLocalesForMoment().then(function (supportedLocales) {
|
||||
return loadLocales(currentUser, supportedLocales);
|
||||
return loadLocales(currentUser.locale, supportedLocales);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -136,7 +152,9 @@ angular.module('umbraco.services')
|
||||
return $q.when(true);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
loadLocales: loadLocales,
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.assetsService#loadCss
|
||||
|
||||
@@ -146,7 +146,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
if (!args.methods) {
|
||||
throw "args.methods is not defined";
|
||||
}
|
||||
if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.save || !args.methods.unPublish) {
|
||||
if (!args.methods.saveAndPublish || !args.methods.sendToPublish || !args.methods.unPublish) {
|
||||
throw "args.methods does not contain all required defined methods";
|
||||
}
|
||||
|
||||
@@ -179,17 +179,6 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
alias: "sendToPublish",
|
||||
addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false"
|
||||
};
|
||||
case "A":
|
||||
//save
|
||||
return {
|
||||
letter: ch,
|
||||
labelKey: "buttons_save",
|
||||
handler: args.methods.save,
|
||||
hotKey: "ctrl+s",
|
||||
hotKeyWhenHidden: true,
|
||||
alias: "save",
|
||||
addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false"
|
||||
};
|
||||
case "Z":
|
||||
//unpublish
|
||||
return {
|
||||
@@ -210,8 +199,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
|
||||
buttons.subButtons = [];
|
||||
|
||||
//This is the ideal button order but depends on circumstance, we'll use this array to create the button list
|
||||
// Publish, SendToPublish, Save
|
||||
var buttonOrder = ["U", "H", "A"];
|
||||
// Publish, SendToPublish
|
||||
var buttonOrder = ["U", "H"];
|
||||
|
||||
//Create the first button (primary button)
|
||||
//We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item.
|
||||
|
||||
@@ -85,6 +85,7 @@ function navigationService($rootScope, $route, $routeParams, $log, $location, $q
|
||||
appState.setSectionState("showSearchResults", false);
|
||||
appState.setGlobalState("stickyNavigation", false);
|
||||
appState.setGlobalState("showTray", false);
|
||||
appState.setMenuState("currentNode", null);
|
||||
|
||||
if (appState.getGlobalState("isTablet") === true) {
|
||||
appState.setGlobalState("showNavigation", false);
|
||||
@@ -365,7 +366,8 @@ function navigationService($rootScope, $route, $routeParams, $log, $location, $q
|
||||
|
||||
if (appState.getGlobalState("isTablet") === true && !appState.getGlobalState("stickyNavigation")) {
|
||||
//reset it to whatever is in the url
|
||||
appState.setSectionState("currentSection", $routeParams.section);
|
||||
appState.setSectionState("currentSection", $routeParams.section);
|
||||
|
||||
setMode("default-hidesectiontree");
|
||||
}
|
||||
|
||||
|
||||
@@ -143,7 +143,6 @@ angular.module('umbraco.services')
|
||||
/** Called to update the current user's timeout */
|
||||
function setUserTimeoutInternal(newTimeout) {
|
||||
|
||||
|
||||
var asNumber = parseFloat(newTimeout);
|
||||
if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) {
|
||||
currentUser.remainingAuthSeconds = newTimeout;
|
||||
|
||||
@@ -138,6 +138,7 @@
|
||||
@import "components/umb-querybuilder.less";
|
||||
@import "components/umb-pagination.less";
|
||||
@import "components/umb-mini-list-view.less";
|
||||
@import "components/umb-multiple-textbox.less";
|
||||
@import "components/umb-badge.less";
|
||||
@import "components/umb-nested-content.less";
|
||||
@import "components/umb-checkmark.less";
|
||||
|
||||
@@ -227,13 +227,29 @@ input[type="button"] {
|
||||
font-size: 16px;
|
||||
border: none;
|
||||
background: @green;
|
||||
color: white;
|
||||
color: @white;
|
||||
font-weight: bold;
|
||||
&:hover {
|
||||
background: @green-d1;
|
||||
}
|
||||
}
|
||||
|
||||
// outlined
|
||||
.btn-outline {
|
||||
border: 1px solid @gray-8;
|
||||
background: @white;
|
||||
color: @black;
|
||||
padding: 5px 13px;
|
||||
}
|
||||
|
||||
.btn-outline:hover,
|
||||
.btn-outline:focus,
|
||||
.btn-outline:active {
|
||||
border-color: @gray-7;
|
||||
background: transparent;
|
||||
color: @black;
|
||||
}
|
||||
|
||||
// Cross-browser Jank
|
||||
// --------------------------------------------------
|
||||
|
||||
|
||||
@@ -790,13 +790,13 @@ h4.panel-title {
|
||||
}
|
||||
|
||||
.smartphone-portrait {
|
||||
width: 320px;
|
||||
height: 504px;
|
||||
width: 360px;
|
||||
height: 640px;
|
||||
}
|
||||
|
||||
.smartphone-landscape {
|
||||
width: 480px;
|
||||
height: 256px;
|
||||
width: 640px;
|
||||
height: 360px;
|
||||
}
|
||||
|
||||
.border {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
}
|
||||
|
||||
.umb-dashboard__content {
|
||||
padding: 30px 20px;
|
||||
padding: 20px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,12 +16,15 @@
|
||||
.umb-button-group {
|
||||
|
||||
.umb-button__button {
|
||||
border-radius: 3px 0px 0px 3px;
|
||||
border-radius: @baseBorderRadius;
|
||||
}
|
||||
|
||||
.umb-button-group__toggle {
|
||||
border-radius: 0px 3px 3px 0;
|
||||
border-radius: 0px @baseBorderRadius @baseBorderRadius 0;
|
||||
border-left: 1px solid rgba(0,0,0,0.09);
|
||||
margin-left: -2px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -96,7 +96,6 @@
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
width: 100px;
|
||||
height: 105px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
@@ -114,6 +113,7 @@
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
background: @gray-10;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: -20px;
|
||||
margin-top: -10px;
|
||||
position: relative;
|
||||
top: 0;
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
.umb-prevalues-multivalues__left {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.umb-prevalues-multivalues__right {
|
||||
|
||||
@@ -94,6 +94,10 @@ body.touch .umb-tree {
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
|
||||
&.active {
|
||||
background: @gray-10;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: @gray-10;
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
.umb-color-swatches {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
.umb-color-box {
|
||||
border: none;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
padding: 5px;
|
||||
padding: 1px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
display: inline-block;
|
||||
margin: 5px;
|
||||
border-radius: 3px;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
transition: box-shadow .3s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover, &:focus {
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
|
||||
@@ -28,4 +32,55 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.with-labels {
|
||||
|
||||
.umb-color-box {
|
||||
width: 120px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
.umb-color-box-inner {
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
flex: 0 0 100%;
|
||||
max-width: 100%;
|
||||
min-height: 80px;
|
||||
padding-top: 10px;
|
||||
|
||||
.umb-color-box__label {
|
||||
background: #fff;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
flex-flow: column wrap;
|
||||
flex: 0 0 100%;
|
||||
padding: 1px 5px;
|
||||
max-width: 100%;
|
||||
margin-top: auto;
|
||||
margin-bottom: -3px;
|
||||
margin-left: -1px;
|
||||
margin-right: -1px;
|
||||
text-indent: 0;
|
||||
text-align: left;
|
||||
border: 1px solid @gray-8;
|
||||
border-bottom-left-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
overflow: hidden;
|
||||
|
||||
.umb-color-box__name {
|
||||
color: @black;
|
||||
font-weight: bold;
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.umb-color-box__description {
|
||||
font-size: 12px;
|
||||
line-height: 1.5em;
|
||||
color: @gray-3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,18 @@
|
||||
background-color: @white;
|
||||
}
|
||||
|
||||
.umb-media-grid__item-file-icon > span {
|
||||
color: @white;
|
||||
background: @gray-4;
|
||||
padding: 1px 3px;
|
||||
font-size: 10px;
|
||||
line-height: 130%;
|
||||
display: block;
|
||||
margin-top: -30px;
|
||||
margin-left: -10px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.umb-media-grid__item.-selected {
|
||||
box-shadow: 0 2px 8px 0 rgba(0,0,0,.35);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
.umb-multiple-textbox .textbox-wrapper {
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .textbox-wrapper .umb-editor {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .textbox-wrapper i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .textbox-wrapper i.handle {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .textbox-wrapper a.remove {
|
||||
margin-left: 5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.umb-multiple-textbox .add-link {
|
||||
&:extend(.umb-node-preview-add);
|
||||
}
|
||||
|
||||
.umb-editor-wrapper .umb-multiple-textbox .add-link {
|
||||
&:extend(.umb-editor-wrapper .umb-node-preview);
|
||||
}
|
||||
|
||||
.umb-modal .umb-multiple-textbox .textbox-wrapper .umb-editor {
|
||||
flex: 1 1 auto;
|
||||
width: auto;
|
||||
}
|
||||
@@ -166,7 +166,7 @@ input.umb-table__input {
|
||||
|
||||
}
|
||||
|
||||
.-content .-unpublished {
|
||||
.-content :not(.with-unpublished-version).-unpublished {
|
||||
.umb-table__name > * {
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
@@ -133,27 +133,6 @@ div.umb-codeeditor .umb-btn-toolbar {
|
||||
//
|
||||
// Color picker
|
||||
// --------------------------------------------------
|
||||
ul.color-picker li {
|
||||
padding: 2px;
|
||||
margin: 3px;
|
||||
border: 2px solid transparent;
|
||||
width: 60px;
|
||||
|
||||
.thumbnail{
|
||||
min-width: auto;
|
||||
width: inherit;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
height: 50px;
|
||||
display:flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor:pointer;
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
/* pre-value editor */
|
||||
.control-group.color-picker-preval {
|
||||
@@ -174,7 +153,7 @@ ul.color-picker li {
|
||||
div.color-picker-prediv {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 80%;
|
||||
max-width: 85%;
|
||||
|
||||
pre {
|
||||
display: inline-flex;
|
||||
@@ -357,13 +336,30 @@ ul.color-picker li {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.umb-sortable-thumbnails .umb-icon-holder .icon{
|
||||
.umb-sortable-thumbnails .umb-icon-holder .icon {
|
||||
font-size: 40px;
|
||||
line-height: 50px;
|
||||
color: @gray-3;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.umb-sortable-thumbnails .umb-icon-holder .file-icon > span {
|
||||
color: @white;
|
||||
background: @gray-4;
|
||||
padding: 1px 3px;
|
||||
font-size: 10px;
|
||||
line-height: 130%;
|
||||
display: block;
|
||||
margin-top: -30px;
|
||||
width: 2em;
|
||||
}
|
||||
|
||||
.umb-sortable-thumbnails .umb-icon-holder .file-icon + small {
|
||||
display: block;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
|
||||
.umb-sortable-thumbnails .umb-sortable-thumbnails__wrapper {
|
||||
width: 124px;
|
||||
height: 124px;
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
function init() {
|
||||
// Check if it is a new user
|
||||
var inviteVal = $location.search().invite;
|
||||
//1 = enter password, 2 = password set, 3 = invalid token
|
||||
if (inviteVal && (inviteVal === "1" || inviteVal === "2")) {
|
||||
|
||||
$q.all([
|
||||
@@ -58,6 +59,8 @@
|
||||
$scope.inviteStep = Number(inviteVal);
|
||||
|
||||
});
|
||||
} else if (inviteVal && inviteVal === "3") {
|
||||
$scope.inviteStep = Number(inviteVal);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,10 +104,18 @@
|
||||
</umb-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div ng-show="invitedUser == null && inviteStep === 3" ng-if="inviteStep === 3" class="umb-login-container">
|
||||
<div class="form">
|
||||
<h1 style="margin-bottom: 10px; text-align: left;">Hi there</h1>
|
||||
<p style="line-height: 1.6; margin-bottom: 25px;">
|
||||
<localize key="user_userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</localize>
|
||||
</p>
|
||||
|
||||
<div ng-show="invitedUser == null" class="umb-login-container">
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="invitedUser == null && !inviteStep" class="umb-login-container">
|
||||
|
||||
<div class="form">
|
||||
<h1>{{greeting}}</h1>
|
||||
|
||||
+1
-1
@@ -313,7 +313,7 @@ angular.module("umbraco")
|
||||
|
||||
function searchMedia() {
|
||||
$scope.loading = true;
|
||||
entityResource.getPagedDescendants($scope.startNodeId, "Media", $scope.searchOptions)
|
||||
entityResource.getPagedDescendants($scope.currentFolder.id, "Media", $scope.searchOptions)
|
||||
.then(function(data) {
|
||||
// update image data to work with image grid
|
||||
angular.forEach(data.items, function(mediaItem) {
|
||||
|
||||
+26
-18
@@ -23,13 +23,19 @@
|
||||
|
||||
<div class="form-search">
|
||||
<i class="icon-search"></i>
|
||||
<input class="umb-search-field search-query -full-width-input"
|
||||
ng-model="searchOptions.filter"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_search"
|
||||
ng-change="changeSearch()"
|
||||
type="text"
|
||||
no-dirty-check />
|
||||
<input class="umb-search-field search-query -full-width-input"
|
||||
ng-model="searchOptions.filter"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_search"
|
||||
ng-change="changeSearch()"
|
||||
type="text"
|
||||
no-dirty-check />
|
||||
<br />
|
||||
<label>
|
||||
<localize key="general_includeFromsubFolders">Include subfolders in search</localize>
|
||||
<input type="checkbox" ng-model="showChilds"
|
||||
ng-change="changeSearch()">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="upload-button">
|
||||
@@ -81,17 +87,19 @@
|
||||
</umb-file-dropzone>
|
||||
|
||||
<umb-media-grid
|
||||
ng-if="!loading"
|
||||
items="images"
|
||||
on-click="clickHandler"
|
||||
on-click-name="clickItemName"
|
||||
on-click-edit="editMediaItem(item)"
|
||||
allow-on-click-edit="{{allowMediaEdit}}"
|
||||
item-max-width="150"
|
||||
item-max-height="150"
|
||||
item-min-width="100"
|
||||
item-min-height="100"
|
||||
only-images={{onlyImages}}>
|
||||
ng-if="!loading"
|
||||
items="images"
|
||||
on-click="clickHandler"
|
||||
on-click-name="clickItemName"
|
||||
on-click-edit="editMediaItem(item)"
|
||||
allow-on-click-edit="{{allowMediaEdit}}"
|
||||
item-max-width="150"
|
||||
item-max-height="150"
|
||||
item-min-width="100"
|
||||
item-min-height="100"
|
||||
only-images={{onlyImages}}
|
||||
include-sub-folders={{showChilds}}
|
||||
current-Folder-id="{{currentFolder.id}}">
|
||||
</umb-media-grid>
|
||||
|
||||
<div class="flex justify-center">
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
<!-- navigation container -->
|
||||
<div id="navigation" ng-show="showNavigation" class="fill umb-modalcolumn" ng-animate="'slide'" nav-resize>
|
||||
|
||||
<div ng-swipe-left="nav.hideNavigation()" class="navigation-inner-container">
|
||||
<div class="navigation-inner-container">
|
||||
|
||||
<div class="umb-language-picker" ng-if="currentSection === 'content' && languages.length > 1" on-outside-click="page.languageSelectorIsOpen = false">
|
||||
<div class="umb-language-picker__toggle" ng-click="toggleLanguageSelector()">
|
||||
<div>{{selectedLanguage.name}}</div>
|
||||
<ins class="umb-language-picker__expand" ng-class="{'icon-navigation-down': !page.languageSelectorIsOpen, 'icon-navigation-up': page.languageSelectorIsOpen}" class="icon-navigation-right"> </ins>
|
||||
<ins class="umb-language-picker__expand" ng-class="{'icon-navigation-down': !page.languageSelectorIsOpen, 'icon-navigation-up': page.languageSelectorIsOpen}" class="icon-navigation-right"> </ins>
|
||||
</div>
|
||||
<div class="umb-language-picker__dropdown" ng-if="page.languageSelectorIsOpen">
|
||||
<a class="umb-language-picker__dropdown-item" ng-class="{'umb-language-picker__dropdown-item--current': language.active}" ng-click="selectLanguage(language)" ng-repeat="language in languages" href="">{{language.name}}</a>
|
||||
@@ -27,7 +27,7 @@
|
||||
<div class="offset6" id="navOffset" style="z-index: 10">
|
||||
|
||||
<!-- The context menu -->
|
||||
<div id='contextMenu' class="umb-modalcolumn fill shadow" ng-swipe-left="nav.hideMenu()" ng-show="showContextMenu" ng-animate="'slide'">
|
||||
<div id='contextMenu' class="umb-modalcolumn fill shadow" ng-show="showContextMenu" ng-animate="'slide'">
|
||||
<umb-context-menu
|
||||
menu-dialog-title="{{menuDialogTitle}}"
|
||||
current-section="{{currentSection}}"
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
|
||||
<!-- Tree dialogs -->
|
||||
<div id="dialog" class='umb-modalcolumn fill shadow' ng-swipe-left="nav.hideDialog()" ng-show="showContextMenuDialog" ng-animate="'slide'">
|
||||
<div id="dialog" class='umb-modalcolumn fill shadow' ng-show="showContextMenuDialog" ng-animate="'slide'">
|
||||
<div class='umb-modalcolumn-header'>
|
||||
<h1>{{menuDialogTitle}}</h1>
|
||||
</div>
|
||||
|
||||
@@ -39,12 +39,23 @@
|
||||
<umb-button
|
||||
alias="preview"
|
||||
ng-if="!page.isNew && content.allowPreview && page.showPreviewButton"
|
||||
type="button"
|
||||
button-style="info"
|
||||
type="button"
|
||||
button-style="outline"
|
||||
action="preview(content)"
|
||||
label="Preview page"
|
||||
label-key="buttons_showPage">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
ng-if="page.showSaveButton"
|
||||
alias="save"
|
||||
type="button"
|
||||
button-style="info"
|
||||
state="page.saveButtonState"
|
||||
action="save(content)"
|
||||
label-key="buttons_save"
|
||||
shortcut="ctrl+s"
|
||||
add-ellipsis="{{page.saveButtonEllipsis}}">
|
||||
</umb-button>
|
||||
|
||||
<umb-button-group
|
||||
ng-if="defaultButton && !content.trashed && !infiniteModel.infiniteMode"
|
||||
|
||||
@@ -22,6 +22,23 @@
|
||||
</ul>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
<umb-box data-element="node-info-redirects" style="display:none;" ng-cloak ng-show="!urlTrackerDisabled && hasRedirects">
|
||||
<umb-box-header title-key="redirectUrls_redirectUrlManagement"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
<div style="position: relative;">
|
||||
<div ng-if="loadingRedirectUrls" style="background: rgba(255, 255, 255, 0.8); position: absolute; top: 0; left: 0; right: 0; bottom: 0;"></div>
|
||||
<umb-load-indicator ng-if="loadingRedirectUrls"></umb-load-indicator>
|
||||
<div ng-show="hasRedirects">
|
||||
<p><localize key="redirectUrls_panelInformation" class="ng-isolate-scope ng-scope">The following URLs redirect to this content item:</localize></p>
|
||||
<ul class="nav nav-stacked" style="margin-bottom: 0;">
|
||||
<li ng-repeat="redirectUrl in redirectUrls">
|
||||
<a href="{{redirectUrl.originalUrl}}" target="_blank"><i ng-class="value.icon" class="icon-out"></i> {{redirectUrl.originalUrl}}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box data-element="node-info-history">
|
||||
<umb-box-header title-key="general_history"></umb-box-header>
|
||||
@@ -67,6 +84,7 @@
|
||||
class="history-item__badge"
|
||||
size="xs"
|
||||
color="{{item.logTypeColor}}">
|
||||
|
||||
<localize key="auditTrails_small{{ item.logType }}">{{ item.logType }}</localize>
|
||||
</umb-badge>
|
||||
<span>
|
||||
@@ -179,7 +197,7 @@
|
||||
</div>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group data-element="node-info-create-date" label="@template_createdDate">
|
||||
<umb-control-group ng-if="node.id !== 0" data-element="node-info-create-date" label="@template_createdDate">
|
||||
{{node.createDateFormatted}} <localize key="general_by">by</localize> {{ node.owner.name }}
|
||||
</umb-control-group>
|
||||
|
||||
@@ -209,7 +227,7 @@
|
||||
</div>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group data-element="node-info-id" label="Id">
|
||||
<umb-control-group ng-if="node.id !== 0" data-element="node-info-id" label="Id">
|
||||
<div>{{ node.id }}</div>
|
||||
<small>{{ node.key }}</small>
|
||||
</umb-control-group>
|
||||
|
||||
@@ -9,12 +9,15 @@
|
||||
<div ng-if="notification.view">
|
||||
<div ng-include="notification.view"></div>
|
||||
</div>
|
||||
|
||||
<div ng-if="notification.headline">
|
||||
<a ng-href="{{notification.url}}" target="_blank">
|
||||
<div ng-if="notification.headline" ng-switch on="{{notification}}">
|
||||
<a ng-href="{{notification.url}}" ng-switch-when="{{notification.url && notification.url.trim() != ''}}" target="_blank">
|
||||
<strong>{{notification.headline}}</strong>
|
||||
<span ng-bind-html="notification.message"></span>
|
||||
</a>
|
||||
<div ng-switch-default>
|
||||
<strong>{{notification.headline}}</strong>
|
||||
<span ng-bind-html="notification.message"></span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
<div class="umb-color-swatches">
|
||||
<div class="umb-color-swatches" ng-class="{ 'with-labels': useLabel }">
|
||||
|
||||
<button class="umb-color-box umb-color-box--{{size}} btn-{{color.value}}" ng-repeat="color in colors" title="{{color.name}}" ng-click="setColor(color.value)">
|
||||
<div class="check_circle" ng-if="color.value === selectedColor">
|
||||
<i class="icon icon-check small"></i>
|
||||
<button type="button" class="umb-color-box umb-color-box--{{size}} btn-{{color.value}}" ng-repeat="color in colors" title="{{color.label}}" hex-bg-inline="{{useColorClass === false}}" hex-bg-color="{{color.value}}" ng-class="{ 'active': color.value === selectedColor }" ng-click="setColor(color.value)">
|
||||
<div class="umb-color-box-inner">
|
||||
<div class="check_circle" ng-if="color.value === selectedColor">
|
||||
<i class="icon icon-check small"></i>
|
||||
</div>
|
||||
<div class="umb-color-box__label" ng-if="useLabel">
|
||||
<div class="umb-color-box__name truncate">{{ color.label }}</div>
|
||||
<div class="umb-color-box__description">#{{ color.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -5,23 +5,26 @@
|
||||
<umb-editor-sub-header-content-right>
|
||||
|
||||
<umb-button
|
||||
style="margin-right: 5px;"
|
||||
alias="compositions"
|
||||
ng-if="compositions !== false"
|
||||
type="button"
|
||||
button-style="link"
|
||||
button-style="outline"
|
||||
label-key="contentTypeEditor_compositions"
|
||||
icon="icon-merge"
|
||||
action="openCompositionsDialog()">
|
||||
action="openCompositionsDialog()"
|
||||
size="xs">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
alias="reorder"
|
||||
ng-if="sorting !== false"
|
||||
type="button"
|
||||
button-style="link"
|
||||
button-style="outline"
|
||||
label-key="{{sortingButtonKey}}"
|
||||
icon="icon-navigation"
|
||||
action="toggleSortingMode();">
|
||||
action="toggleSortingMode();"
|
||||
size="xs">
|
||||
</umb-button>
|
||||
|
||||
</umb-editor-sub-header-content-right>
|
||||
|
||||
@@ -22,8 +22,10 @@
|
||||
<img class="umb-media-grid__item-image-placeholder" ng-if="!item.thumbnail && item.extension != 'svg'" src="assets/img/transparent.png" alt="{{item.name}}" draggable="false" />
|
||||
|
||||
<!-- Icon for files -->
|
||||
<i class="umb-media-grid__item-icon {{item.icon}}" ng-if="!item.thumbnail && item.extension != 'svg'"></i>
|
||||
|
||||
<span class="umb-media-grid__item-file-icon" ng-if="!item.thumbnail && item.extension != 'svg'">
|
||||
<i class="umb-media-grid__item-icon {{item.icon}}"></i>
|
||||
<span>.{{item.extension}}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +35,8 @@
|
||||
ng-class="{
|
||||
'-selected':item.selected,
|
||||
'-published':item.published,
|
||||
'-unpublished':!item.published
|
||||
'-unpublished':!item.published,
|
||||
'with-unpublished-version':!item.published && item.hasPublishedVersion
|
||||
}"
|
||||
ng-click="vm.selectItem(item, $index, $event)">
|
||||
|
||||
|
||||
@@ -1,24 +1,70 @@
|
||||
<div ng-controller="Umbraco.Editors.Content.AssignDomainController as vm">
|
||||
<div class="umb-dialog-body">
|
||||
<umb-pane>
|
||||
<p>UI magic to be done...</p>
|
||||
</umb-pane>
|
||||
</div>
|
||||
<div ng-controller="Umbraco.Editors.Content.AssignDomainController as vm" ng-cloak>
|
||||
<div class="umb-dialog-body">
|
||||
<umb-pane>
|
||||
<h5 class="umb-pane-title"><localize key="assignDomain_setLanguage">Culture</localize></h5>
|
||||
<label for="assignDomain_language" class="control-label"><localize key="general_language"></localize></label>
|
||||
<select class="umb-property-editor umb-dropdown" name="language" id="assignDomain_language" ng-model="vm.language" ng-options="lang.name for lang in vm.languages">
|
||||
<option value="">{{vm.inherit}}</option>
|
||||
</select>
|
||||
</umb-pane>
|
||||
<umb-pane>
|
||||
<h5 class="umb-pane-title"><localize key="assignDomain_setDomains">Domains</localize></h5>
|
||||
<form name="vm.domainForm" novalidate="" ng-submit="vm.save()" class="control-group umb-control-group">
|
||||
<small class="help-inline">
|
||||
<localize key="assignDomain_domainHelp"></localize>
|
||||
</small>
|
||||
<div class="umb-el-wrap hidelabel">
|
||||
<table class="table domains" ng-if="vm.domains.length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><localize key="assignDomain_domain"></localize></th>
|
||||
<th><localize key="assignDomain_language"></localize></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="domain in vm.domains">
|
||||
<td>
|
||||
<input type="text" ng-model="domain.Name" multi="vm.validateDomain" ng-pattern="vm.domainPattern" name="domain_name_{{$index}}" />
|
||||
<span ng-if="vm.domainForm.$submitted" ng-messages="vm.domainForm['domain_name_' + $index].$error">
|
||||
<span class="help-inline" ng-message="multi"><localize key="assignDomain_duplicateDomain"></localize></span>
|
||||
<span class="help-inline" ng-message="pattern"><localize key="assignDomain_invalidDomain"></localize></span>
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<select name="domain_language_{{$index}}" class="language" ng-model="domain.Lang" ng-options="lang.name for lang in vm.languages"></select>
|
||||
</td>
|
||||
<td>
|
||||
<umb-button icon="icon-trash"
|
||||
action="vm.removeDomain($index)"
|
||||
type="button"
|
||||
button-style="danger">
|
||||
</umb-button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<umb-button label-key="assignDomain_addNew"
|
||||
action="vm.addDomain()"
|
||||
type="button">
|
||||
</umb-button>
|
||||
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
|
||||
|
||||
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
|
||||
<umb-button label-key="general_cancel"
|
||||
action="vm.closeDialog()"
|
||||
type="button"
|
||||
button-style="link">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
label-key="general_cancel"
|
||||
action="vm.closeDialog()"
|
||||
type="button"
|
||||
button-style="link">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
label-key="buttons_save"
|
||||
type="button"
|
||||
button-style="success">
|
||||
</umb-button>
|
||||
<umb-button label-key="buttons_save"
|
||||
type="submit"
|
||||
button-style="success">
|
||||
</umb-button>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</umb-pane>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,128 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function AssignDomainController($scope) {
|
||||
|
||||
function AssignDomainController($scope, localizationService, languageResource, contentResource) {
|
||||
var vm = this;
|
||||
vm.closeDialog = closeDialog;
|
||||
|
||||
function closeDialog() {
|
||||
$scope.nav.hideDialog();
|
||||
vm.addDomain = addDomain;
|
||||
vm.removeDomain = removeDomain;
|
||||
vm.save = save;
|
||||
vm.validateDomain = validateDomain;
|
||||
vm.languages = [];
|
||||
vm.domains = [];
|
||||
vm.language = null;
|
||||
vm.domainPattern = /^(http[s]?:\/\/)?([-\w]+(\.[-\w]+)*)(:\d+)?(\/[-\w]*|-)?$/gi; //TODO: This regex is not working as it should.
|
||||
function activate() {
|
||||
languageResource.getAll().then(function (langs) {
|
||||
vm.languages = langs;
|
||||
var defLang = langs.filter(function (l) {
|
||||
return l.isDefault;
|
||||
});
|
||||
|
||||
if (defLang.length > 0) {
|
||||
vm.defaultLanguage = defLang[0];
|
||||
}
|
||||
else {
|
||||
vm.defaultLanguage = langs[0];
|
||||
}
|
||||
getCultureAndDomains();
|
||||
});
|
||||
|
||||
localizationService.localize("assignDomain_inherit").then(function (value) {
|
||||
vm.inherit = value;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function getCultureAndDomains () {
|
||||
contentResource.getCultureAndDomains($scope.currentNode.id)
|
||||
.then(function (data) {
|
||||
if (data.Language !== "undefined") {
|
||||
var lang = vm.languages.filter(function (l) {
|
||||
return matchLanguageById(l, data.Language.Id);
|
||||
|
||||
});
|
||||
if (lang.length > 0) {
|
||||
vm.language = lang[0];
|
||||
}
|
||||
}
|
||||
|
||||
vm.domains = data.Domains.map(function (d) {
|
||||
var matchedLangs = vm.languages.filter(function (lng) {
|
||||
return matchLanguageById(lng, d.Lang);
|
||||
});
|
||||
return {
|
||||
Name: d.Name,
|
||||
Lang: matchedLangs.length > 0 ? matchedLangs[0] : vm.defaultLanguage
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function matchLanguageById(language, id) {
|
||||
var langId = parseInt(language.id);
|
||||
var comparisonId = parseInt(id);
|
||||
return langId === comparisonId;
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
$scope.nav.hideDialog();
|
||||
}
|
||||
|
||||
function addDomain() {
|
||||
vm.domains.push({
|
||||
Name: '',
|
||||
Lang: vm.defaultLanguage
|
||||
});
|
||||
}
|
||||
|
||||
function removeDomain(index) {
|
||||
vm.domains.splice(index, 1);
|
||||
}
|
||||
|
||||
function validateDomain() {
|
||||
var valid = true, duplicateTest = {};
|
||||
if (vm.domains.length > 1) {
|
||||
|
||||
vm.domains.map(function (d, index) {
|
||||
if (d.Name in duplicateTest) {
|
||||
valid = false;
|
||||
}
|
||||
else {
|
||||
duplicateTest[d.Name] = index;
|
||||
}
|
||||
});
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
||||
function save() {
|
||||
|
||||
if (vm.domainForm.$valid) {
|
||||
var data = {
|
||||
NodeId: $scope.currentNode.id,
|
||||
Domains: vm.domains.map(function (d) {
|
||||
return {
|
||||
Name: d.Name,
|
||||
Lang: d.Lang.id
|
||||
};
|
||||
}),
|
||||
Language: vm.language != null ? vm.language.id : 0
|
||||
};
|
||||
console.log(data);
|
||||
contentResource.saveLanguageAndDomains(data).then(function () {
|
||||
closeDialog();
|
||||
}, function (e) {
|
||||
console.log(e); //TODO: not sure how best to handle this case
|
||||
});
|
||||
}
|
||||
else {
|
||||
console.log('not valid');
|
||||
}
|
||||
}
|
||||
|
||||
activate();
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.Content.AssignDomainController", AssignDomainController);
|
||||
})();
|
||||
|
||||
})();
|
||||
@@ -1,20 +1,15 @@
|
||||
<div ng-controller="Umbraco.PropertyEditors.ColorPickerController">
|
||||
|
||||
<div ng-if="!isConfigured" >
|
||||
<localize key="colorpicker_noColors">You haven't defined any colors</localize>
|
||||
</div>
|
||||
<div ng-if="!isConfigured">
|
||||
<localize key="colorpicker_noColors">You haven't defined any colors</localize>
|
||||
</div>
|
||||
|
||||
<ul class="thumbnails color-picker">
|
||||
<li ng-repeat="(key, val) in model.config.items">
|
||||
<a ng-click="toggleItem(val)" class="thumbnail" hex-bg-color="{{val.value}}">
|
||||
<div class="check_circle" ng-if="isActiveColor(val)">
|
||||
<i class="icon icon-check small"></i>
|
||||
</div>
|
||||
</a>
|
||||
<span class="color-label" ng-if="model.useLabel" ng-bind="val.label"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<umb-color-swatches colors="model.config.items"
|
||||
selected-color="model.value.value"
|
||||
size="m"
|
||||
use-label="model.useLabel">
|
||||
</umb-color-swatches>
|
||||
|
||||
<input type="hidden" name="modelValue" ng-model="model.value" val-property-validator="validateMandatory"/>
|
||||
<input type="hidden" name="modelValue" ng-model="model.value" val-property-validator="validateMandatory" />
|
||||
|
||||
</div>
|
||||
|
||||
+2
@@ -33,6 +33,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
/** Performs validation based on the renderModel data */
|
||||
function validate() {
|
||||
if ($scope.contentPickerForm) {
|
||||
angularHelper.getCurrentForm($scope).$setDirty();
|
||||
//Validate!
|
||||
if ($scope.model.config && $scope.model.config.minNumber && parseInt($scope.model.config.minNumber) > $scope.renderModel.length) {
|
||||
$scope.contentPickerForm.minCount.$setValidity("minCount", false);
|
||||
@@ -199,6 +200,7 @@ function contentPickerController($scope, entityResource, editorState, iconHelper
|
||||
_.each(model.selection, function (item, i) {
|
||||
$scope.add(item);
|
||||
});
|
||||
angularHelper.getCurrentForm($scope).$setDirty();
|
||||
}
|
||||
angularHelper.getCurrentForm($scope).$setDirty();
|
||||
editorService.close();
|
||||
|
||||
@@ -42,8 +42,9 @@
|
||||
</umb-image-gravity>
|
||||
<a href class="btn btn-link btn-crop-delete" ng-click="clear()"><i class="icon-delete red"></i> <localize key="content_uploadClear">Remove file</localize></a>
|
||||
</div>
|
||||
|
||||
<ul class="umb-sortable-thumbnails cropList clearfix">
|
||||
|
||||
|
||||
<ul ng-if="!imageSrc.endsWith('.svg')" class="umb-sortable-thumbnails cropList clearfix">
|
||||
<li ng-repeat=" (key,value) in model.value.crops" ng-class="{'current':currentCrop.alias === value.alias}" ng-click="crop(value)">
|
||||
|
||||
<umb-image-thumbnail center="model.value.focalPoint"
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
</umb-empty-state>
|
||||
|
||||
<umb-empty-state
|
||||
ng-if="vm.itemsWithoutFolders.length === 0 && options.filter.length > 0"
|
||||
ng-if="vm.itemsWithoutFolders.length === 0 && folders.length === 0 && options.filter.length > 0"
|
||||
position="center">
|
||||
<localize key="general_searchNoResult"></localize>
|
||||
</umb-empty-state>
|
||||
|
||||
+11
-21
@@ -227,10 +227,6 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
|
||||
},
|
||||
500);
|
||||
|
||||
if (reload === true) {
|
||||
$scope.reloadView($scope.contentId, true);
|
||||
}
|
||||
|
||||
if (successMsg) {
|
||||
localizationService.localize("bulk_done")
|
||||
.then(function (v) {
|
||||
@@ -260,12 +256,13 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
|
||||
with simple values */
|
||||
|
||||
$scope.getContent = function (contentId) {
|
||||
$scope.reloadView($scope.contentId, true);
|
||||
|
||||
$scope.reloadView($scope.contentId);
|
||||
}
|
||||
|
||||
$scope.reloadView = function (id, reloadFolders) {
|
||||
|
||||
$scope.reloadView = function (id) {
|
||||
$scope.viewLoaded = false;
|
||||
$scope.folders = [];
|
||||
|
||||
listViewHelper.clearSelection($scope.listViewResultSet.items, $scope.folders, $scope.selection);
|
||||
|
||||
@@ -278,20 +275,13 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
|
||||
if ($scope.listViewResultSet.items) {
|
||||
_.each($scope.listViewResultSet.items, function (e, index) {
|
||||
setPropertyValues(e);
|
||||
});
|
||||
}
|
||||
if (e.contentTypeAlias === 'Folder') {
|
||||
$scope.folders.push(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (reloadFolders && $scope.entityType === 'media') {
|
||||
//The folders aren't loaded - we only need to do this once since we're never changing node ids
|
||||
mediaResource.getChildFolders($scope.contentId)
|
||||
.then(function (page) {
|
||||
$scope.folders = page.items;
|
||||
$scope.viewLoaded = true;
|
||||
});
|
||||
|
||||
} else {
|
||||
$scope.viewLoaded = true;
|
||||
}
|
||||
$scope.viewLoaded = true;
|
||||
|
||||
//NOTE: This might occur if we are requesting a higher page number than what is actually available, for example
|
||||
// if you have more than one page and you delete all items on the last page. In this case, we need to reset to the last
|
||||
@@ -609,7 +599,7 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
|
||||
$scope.options.allowBulkMove ||
|
||||
$scope.options.allowBulkDelete;
|
||||
|
||||
$scope.reloadView($scope.contentId, true);
|
||||
$scope.reloadView($scope.contentId);
|
||||
}
|
||||
|
||||
function getLocalizedKey(alias) {
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
|
||||
<!-- FILE -->
|
||||
<span class="umb-icon-holder" ng-hide="image.thumbnail || image.metaData.umbracoExtension.Value === 'svg' || image.extension === 'svg'">
|
||||
<i class="icon {{image.icon}} large"></i>
|
||||
<span class="file-icon">
|
||||
<i class="icon {{image.icon}} large"></i>
|
||||
<span>.{{image.extension}}</span>
|
||||
</span>
|
||||
<small>{{image.name}}</small>
|
||||
</span>
|
||||
|
||||
|
||||
+2
-1
@@ -279,7 +279,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
|
||||
_.each($scope.model.config.contentTypes, function (contentType) {
|
||||
contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
|
||||
// remove all tabs except the specified tab
|
||||
var tab = _.find(scaffold.tabs, function (tab) {
|
||||
var tabs = scaffold.variants[0].tabs;
|
||||
var tab = _.find(tabs, function (tab) {
|
||||
return tab.id != 0 && (tab.alias.toLowerCase() == contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias == "");
|
||||
});
|
||||
scaffold.tabs = [];
|
||||
|
||||
@@ -395,7 +395,9 @@ angular.module("umbraco")
|
||||
var unsubscribe = $scope.$on("formSubmitting", function () {
|
||||
//TODO: Here we should parse out the macro rendered content so we can save on a lot of bytes in data xfer
|
||||
// we do parse it out on the server side but would be nice to do that on the client side before as well.
|
||||
$scope.model.value = tinyMceEditor ? tinyMceEditor.getContent() : null;
|
||||
if (tinyMceEditor !== undefined && tinyMceEditor != null && !$scope.isLoading) {
|
||||
$scope.model.value = tinyMceEditor.getContent();
|
||||
}
|
||||
});
|
||||
|
||||
//when the element is disposed we need to unsubscribe!
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<div ng-controller="Umbraco.PropertyEditors.textAreaController">
|
||||
<ng-form name="textareaFieldForm">
|
||||
<textarea ng-model="model.value" id="{{model.alias}}" name="textarea" rows="10" class="umb-property-editor umb-textarea textstring" val-server="value" ng-keyup="model.change()"></textarea>
|
||||
<textarea ng-model="model.value" id="{{model.alias}}" name="textarea" rows="{{model.config.rows || 10}}" class="umb-property-editor umb-textarea textstring" val-server="value" ng-keyup="model.change()"></textarea>
|
||||
|
||||
<span ng-messages="textareaFieldForm.textarea.$error" show-validation-on-submit >
|
||||
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
<umb-editor-sub-header-section>
|
||||
<umb-button
|
||||
type="button"
|
||||
label="Clear selection"
|
||||
size="xs"
|
||||
button-style="outline"
|
||||
label-key="buttons_clearSelection"
|
||||
action="vm.clearSelection()"
|
||||
disabled="actionInProgress">
|
||||
@@ -62,48 +62,52 @@
|
||||
</umb-editor-sub-header-content-left>
|
||||
|
||||
<umb-editor-sub-header-content-right ng-if="vm.selection.length > 0">
|
||||
<div style="margin-right: 5px;">
|
||||
<umb-button
|
||||
ng-if="vm.allowSetUserGroup"
|
||||
type="button" size="xs"
|
||||
label-key="actions_setGroup"
|
||||
icon="icon-users"
|
||||
action="vm.openBulkUserGroupPicker()">
|
||||
</umb-button>
|
||||
</div>
|
||||
<div style="margin-right: 5px;">
|
||||
<umb-button
|
||||
ng-if="vm.allowEnableUser"
|
||||
type="button"
|
||||
size="xs"
|
||||
state="vm.enableUserButtonState"
|
||||
label-key="actions_enable"
|
||||
icon="icon-check"
|
||||
action="vm.enableUsers()">
|
||||
</umb-button>
|
||||
</div>
|
||||
<div style="margin-right: 5px;">
|
||||
<umb-button
|
||||
ng-if="vm.allowUnlockUser"
|
||||
type="button"
|
||||
size="xs"
|
||||
state="vm.unlockUserButtonState"
|
||||
label-key="actions_unlock"
|
||||
icon="icon-unlocked"
|
||||
action="vm.unlockUsers()">
|
||||
</umb-button>
|
||||
</div>
|
||||
<div>
|
||||
<umb-button
|
||||
ng-if="vm.allowDisableUser"
|
||||
type="button"
|
||||
size="xs"
|
||||
state="vm.disableUserButtonState"
|
||||
label-key="actions_disable"
|
||||
icon="icon-block"
|
||||
action="vm.disableUsers()">
|
||||
</umb-button>
|
||||
</div>
|
||||
<umb-button
|
||||
style="margin-right: 5px;"
|
||||
ng-if="vm.allowSetUserGroup"
|
||||
type="button"
|
||||
size="xs"
|
||||
button-style="outline"
|
||||
label-key="actions_setGroup"
|
||||
icon="icon-users"
|
||||
action="vm.openBulkUserGroupPicker()">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
style="margin-right: 5px;"
|
||||
ng-if="vm.allowEnableUser"
|
||||
type="button"
|
||||
size="xs"
|
||||
button-style="outline"
|
||||
state="vm.enableUserButtonState"
|
||||
label-key="actions_enable"
|
||||
icon="icon-check"
|
||||
action="vm.enableUsers()">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
style="margin-right: 5px;"
|
||||
ng-if="vm.allowUnlockUser"
|
||||
type="button"
|
||||
size="xs"
|
||||
button-style="outline"
|
||||
state="vm.unlockUserButtonState"
|
||||
label-key="actions_unlock"
|
||||
icon="icon-unlocked"
|
||||
action="vm.unlockUsers()">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
ng-if="vm.allowDisableUser"
|
||||
type="button"
|
||||
size="xs"
|
||||
button-style="outline"
|
||||
state="vm.disableUserButtonState"
|
||||
label-key="actions_disable"
|
||||
icon="icon-block"
|
||||
action="vm.disableUsers()">
|
||||
</umb-button>
|
||||
|
||||
</umb-editor-sub-header-content-right>
|
||||
|
||||
</umb-editor-sub-header>
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AutoMapper" Version="7.0.1" />
|
||||
<PackageReference Include="CSharpTest.Net.Collections" Version="14.906.1403.1082" />
|
||||
<PackageReference Include="ClientDependency" Version="1.9.6" />
|
||||
<PackageReference Include="ClientDependency" Version="1.9.7" />
|
||||
<PackageReference Include="ClientDependency-Mvc5" Version="1.8.0.0" />
|
||||
<PackageReference Include="Examine" Version="1.0.0-beta025" />
|
||||
<PackageReference Include="ImageProcessor.Web" Version="4.9.3.25" />
|
||||
@@ -333,7 +333,6 @@
|
||||
<Content Include="Umbraco\Developer\RelationTypes\RelationTypesWebService.asmx" />
|
||||
<Content Include="Umbraco\Developer\RelationTypes\TreeMenu\ActionDeleteRelationType.js" />
|
||||
<Content Include="Umbraco\Developer\RelationTypes\TreeMenu\ActionNewRelationType.js" />
|
||||
<Content Include="Umbraco\Dialogs\AssignDomain2.aspx" />
|
||||
<Content Include="Umbraco\Dialogs\ChangeDocType.aspx">
|
||||
<SubType>ASPXCodeBehind</SubType>
|
||||
</Content>
|
||||
|
||||
@@ -687,6 +687,7 @@
|
||||
<key alias="embed">Embed</key>
|
||||
<key alias="retrieve">Retrieve</key>
|
||||
<key alias="selected">selected</key>
|
||||
<key alias="includeFromsubFolders">Include subfolders in search</key>
|
||||
</area>
|
||||
|
||||
<area alias="colors">
|
||||
@@ -1688,8 +1689,8 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
|
||||
<key alias="tabHasNoSortOrder">tab has no sort order</key>
|
||||
|
||||
<key alias="compositionUsageHeading">Where is this composition used?</key>
|
||||
<key alias="compositionUsageSpecification">This composition is currently used in the composition of the following content types:</key>
|
||||
<key alias="compositionUsageHeading">Where is this composition used?</key>
|
||||
<key alias="compositionUsageSpecification">This composition is currently used in the composition of the following content types:</key>
|
||||
</area>
|
||||
|
||||
<area alias="modelsBuilder">
|
||||
@@ -1863,7 +1864,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="goToProfile">Go to user profile</key>
|
||||
<key alias="groupsHelp">Add groups to assign access and permissions</key>
|
||||
<key alias="inviteAnotherUser">Invite another user</key>
|
||||
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco.</key>
|
||||
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours.</key>
|
||||
<key alias="language">Language</key>
|
||||
<key alias="languageHelp">Set the language you will see in menus and dialogs</key>
|
||||
<key alias="lastLockoutDate">Last lockout date</key>
|
||||
@@ -1927,6 +1928,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="userInvited">has been invited</key>
|
||||
<key alias="userInvitedSuccessHelp">An invitation has been sent to the new user with details about how to log in to Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar.</key>
|
||||
<key alias="userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</key>
|
||||
<key alias="userinviteAvatarMessage">Upload a picture to make it easy for other users to recognize you.</key>
|
||||
<key alias="writer">Writer</key>
|
||||
<key alias="translator">Translator</key>
|
||||
@@ -2185,6 +2187,8 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="enableUrlTracker">Enable URL tracker</key>
|
||||
<key alias="originalUrl">Original URL</key>
|
||||
<key alias="redirectedTo">Redirected To</key>
|
||||
<key alias="redirectUrlManagement">Redirect Url Management</key>
|
||||
<key alias="panelInformation">The following URLs redirect to this content item:</key>
|
||||
<key alias="noRedirects">No redirects have been made</key>
|
||||
<key alias="noRedirectsDescription">When a published page gets renamed or moved a redirect will automatically be made to the new page.</key>
|
||||
<key alias="removeButton">Remove</key>
|
||||
|
||||
@@ -702,6 +702,7 @@
|
||||
<key alias="embed">Embed</key>
|
||||
<key alias="retrieve">Retrieve</key>
|
||||
<key alias="selected">selected</key>
|
||||
<key alias="includeFromsubFolders">Include subfolders in search</key>
|
||||
</area>
|
||||
<area alias="colors">
|
||||
<key alias="black">Black</key>
|
||||
@@ -1702,8 +1703,8 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
|
||||
<key alias="tabHasNoSortOrder">tab has no sort order</key>
|
||||
|
||||
<key alias="compositionUsageHeading">Where is this composition used?</key>
|
||||
<key alias="compositionUsageSpecification">This composition is currently used in the composition of the following content types:</key>
|
||||
<key alias="compositionUsageHeading">Where is this composition used?</key>
|
||||
<key alias="compositionUsageSpecification">This composition is currently used in the composition of the following content types:</key>
|
||||
</area>
|
||||
|
||||
<area alias="languages">
|
||||
@@ -1886,7 +1887,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="goToProfile">Go to user profile</key>
|
||||
<key alias="groupsHelp">Add groups to assign access and permissions</key>
|
||||
<key alias="inviteAnotherUser">Invite another user</key>
|
||||
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco.</key>
|
||||
<key alias="inviteUserHelp">Invite new users to give them access to Umbraco. An invite email will be sent to the user with information on how to log in to Umbraco. Invites last for 72 hours.</key>
|
||||
<key alias="language">Language</key>
|
||||
<key alias="languageHelp">Set the language you will see in menus and dialogs</key>
|
||||
<key alias="lastLockoutDate">Last lockout date</key>
|
||||
@@ -1950,6 +1951,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="userInvited">has been invited</key>
|
||||
<key alias="userInvitedSuccessHelp">An invitation has been sent to the new user with details about how to log in to Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">Hello there and welcome to Umbraco! In just 1 minute you’ll be good to go, we just need you to setup a password and add a picture for your avatar.</key>
|
||||
<key alias="userinviteExpiredMessage">Welcome to Umbraco! Unfortunately your invite has expired. Please contact your administrator and ask them to resend it.</key>
|
||||
<key alias="userinviteAvatarMessage">Upload a picture to make it easy for other users to recognize you.</key>
|
||||
<key alias="writer">Writer</key>
|
||||
<key alias="translator">Translator</key>
|
||||
@@ -2210,6 +2212,8 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="enableUrlTracker">Enable URL tracker</key>
|
||||
<key alias="originalUrl">Original URL</key>
|
||||
<key alias="redirectedTo">Redirected To</key>
|
||||
<key alias="redirectUrlManagement">Redirect Url Management</key>
|
||||
<key alias="panelInformation">The following URLs redirect to this content item:</key>
|
||||
<key alias="noRedirects">No redirects have been made</key>
|
||||
<key alias="noRedirectsDescription">When a published page gets renamed or moved a redirect will automatically be made to the new page.</key>
|
||||
<key alias="removeButton">Remove</key>
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
<%@ Page Language="c#" MasterPageFile="../masterpages/umbracoDialog.Master" Codebehind="AssignDomain2.aspx.cs" AutoEventWireup="True" Inherits="umbraco.dialogs.AssignDomain2" %>
|
||||
<%@ Import Namespace="Umbraco.Web" %>
|
||||
<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %>
|
||||
<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %>
|
||||
|
||||
<asp:Content ContentPlaceHolderID="head" runat="server">
|
||||
<umb:JsInclude runat="server" FilePath="Dialogs/AssignDomain2.js" PathNameAlias="UmbracoClient" />
|
||||
<umb:JsInclude runat="server" FilePath="PunyCode/punycode.min.js" PathNameAlias="UmbracoClient" />
|
||||
<umb:JsInclude runat="server" FilePath="/Umbraco/lib/jquery-validate/jquery.validate.min.js" PathNameAlias="UmbracoClient" />
|
||||
<umb:CssInclude runat="server" FilePath="Dialogs/AssignDomain2.css" PathNameAlias="UmbracoClient" />
|
||||
<script type="text/javascript">
|
||||
(function ($) {
|
||||
$(document).ready(function () {
|
||||
var dialog = new Umbraco.Dialogs.AssignDomain2({
|
||||
nodeId: <%=GetNodeId()%>,
|
||||
restServiceLocation: '<%=GetRestServicePath() %>',
|
||||
invalidDomain: '<%=Services.TextService.Localize("assignDomain/invalidDomain") %>',
|
||||
duplicateDomain: '<%=Services.TextService.Localize("assignDomain/duplicateDomain") %>',
|
||||
<asp:Literal runat="server" ID="data" />
|
||||
});
|
||||
dialog.init();
|
||||
});
|
||||
})(jQuery);
|
||||
</script>
|
||||
</asp:Content>
|
||||
|
||||
<asp:Content ContentPlaceHolderID="body" runat="server">
|
||||
|
||||
<div class="umb-dialog-body">
|
||||
|
||||
<cc1:Feedback ID="feedback" runat="server" />
|
||||
<div id="komask"></div>
|
||||
|
||||
<cc1:Pane runat="server" ID="pane_language" cssClass="hide">
|
||||
<cc1:PropertyPanel runat="server" ID="prop_language">
|
||||
<select class="umb-property-editor umb-dropdown" name="language" data-bind="options: languages, optionsText: 'Code', optionsValue: 'Id', value: language, optionsCaption: '<%=Services.TextService.Localize("assignDomain/inherit") %> '"></select>
|
||||
<!-- <small class="help-inline"><%=Services.TextService.Localize("assignDomain/setLanguageHelp") %></small>-->
|
||||
</cc1:PropertyPanel>
|
||||
</cc1:Pane>
|
||||
|
||||
|
||||
<cc1:Pane runat="server" ID="pane_domains">
|
||||
<small class="help-inline"><%=Services.TextService.Localize("assignDomain/domainHelp") %></small>
|
||||
<cc1:PropertyPanel runat="server">
|
||||
<table class="table domains" data-bind="visible: domains().length > 0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><%=Services.TextService.Localize("assignDomain/domain") %></th>
|
||||
<th><%=Services.TextService.Localize("assignDomain/language") %></th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody data-bind="foreach: domains">
|
||||
<tr>
|
||||
<td valign="top"><input class="domain duplicate" data-bind="value: Name, uniqueName: true" /><input type="hidden" value="" data-bind="uniqueName: true"/></td>
|
||||
<td valign="top"><select class="language" data-bind="options: $parent.languages, optionsText: 'Code', optionsValue: 'Id', value: Lang, uniqueName: true"></select></td>
|
||||
<td valign="top"><a href="#" class="btn btn-danger" data-bind="click: $parent.removeDomain"><i class="icon icon-trash"></i></a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</cc1:PropertyPanel>
|
||||
|
||||
<cc1:PropertyPanel runat="server">
|
||||
<button class="btn" data-bind="click: addDomain"><%=Services.TextService.Localize("assignDomain/addNew") %></button>
|
||||
</cc1:PropertyPanel>
|
||||
|
||||
|
||||
</cc1:Pane>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div runat="server" ID="p_buttons" class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
|
||||
<a href="#" class="btn btn-link" onclick="UmbClientMgr.closeModalWindow()"><%=Services.TextService.Localize("general/cancel")%></a>
|
||||
<button class="btn btn-primary" id="btnSave"><%=Services.TextService.Localize("buttons/save") %></button>
|
||||
</div>
|
||||
|
||||
</asp:Content>
|
||||
@@ -53,7 +53,7 @@
|
||||
<MacroErrors>throw</MacroErrors>
|
||||
|
||||
<!-- These file types will not be allowed to be uploaded via the upload control for media and content -->
|
||||
<disallowedUploadFiles>ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,svg,php,htaccess</disallowedUploadFiles>
|
||||
<disallowedUploadFiles>ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess</disallowedUploadFiles>
|
||||
|
||||
<!-- Defines the default document type property used when adding properties in the back-office (if missing or empty, defaults to Textstring -->
|
||||
<defaultDocumentTypeProperty>Textstring</defaultDocumentTypeProperty>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<content>
|
||||
<imaging>
|
||||
<!-- what file extension that should cause umbraco to create thumbnails -->
|
||||
<imageFileTypes>jpeg,jpg,gif,bmp,png,tiff,tif</imageFileTypes>
|
||||
<imageFileTypes>jpeg,jpg,gif,bmp,png,tiff,tif,svg</imageFileTypes>
|
||||
<!-- what attributes that are allowed in the editor on an img tag -->
|
||||
<allowedAttributes>src,alt,border,class,style,align,id,name,onclick,usemap</allowedAttributes>
|
||||
<!-- automatically updates dimension, filesize and extension attributes on upload -->
|
||||
@@ -94,7 +94,7 @@
|
||||
<MacroErrors>throw</MacroErrors>
|
||||
|
||||
<!-- These file types will not be allowed to be uploaded via the upload control for media and content -->
|
||||
<disallowedUploadFiles>ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,svg,php,htaccess</disallowedUploadFiles>
|
||||
<disallowedUploadFiles>ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,swf,xml,xhtml,html,htm,php,htaccess</disallowedUploadFiles>
|
||||
|
||||
<!-- If completed, only the file extensions listed below will be allowed to be uploaded. If empty, disallowedUploadFiles will apply to prevent upload of specific file extensions. -->
|
||||
<allowedUploadFiles></allowedUploadFiles>
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
<add verb="*" path="DependencyHandler.axd" type="ClientDependency.Core.CompositeFiles.CompositeDependencyHandler, ClientDependency.Core " />
|
||||
</httpHandlers>
|
||||
|
||||
<compilation defaultLanguage="c#" debug="false" batch="false" targetFramework="4.7.2" numRecompilesBeforeAppRestart="50" />
|
||||
<compilation defaultLanguage="c#" debug="false" batch="true" targetFramework="4.7.2" numRecompilesBeforeAppRestart="50" />
|
||||
|
||||
<authentication mode="Forms">
|
||||
<forms name="yourAuthCookie" loginUrl="login.aspx" protection="All" path="/" />
|
||||
|
||||
@@ -421,6 +421,34 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
}
|
||||
|
||||
//They've successfully set their password, we can now update their user account to be confirmed
|
||||
//if user was only invited, then they have not been approved
|
||||
//but a successful forgot password flow (e.g. if their token had expired and they did a forgot password instead of request new invite)
|
||||
//means we have verified their email
|
||||
if (!UserManager.IsEmailConfirmed(model.UserId))
|
||||
{
|
||||
await UserManager.ConfirmEmailAsync(model.UserId, model.ResetCode);
|
||||
}
|
||||
|
||||
//if the user is invited, enable their account on forgot password
|
||||
var identityUser = await UserManager.FindByIdAsync(model.UserId);
|
||||
//invited is not approved, never logged in, invited date present
|
||||
/*
|
||||
if (LastLoginDate == default && IsApproved == false && InvitedDate != null)
|
||||
return UserState.Invited;
|
||||
*/
|
||||
if (identityUser != null && !identityUser.IsApproved)
|
||||
{
|
||||
var user = Services.UserService.GetByUsername(identityUser.UserName);
|
||||
//also check InvitedDate and never logged in, otherwise this would allow a disabled user to reactivate their account with a forgot password
|
||||
if (user.LastLoginDate == default && user.InvitedDate != null)
|
||||
{
|
||||
user.IsApproved = true;
|
||||
user.InvitedDate = null;
|
||||
Services.UserService.Save(user);
|
||||
}
|
||||
}
|
||||
|
||||
UserManager.RaiseForgotPasswordChangedSuccessEvent(model.UserId);
|
||||
return Request.CreateResponse(HttpStatusCode.OK);
|
||||
}
|
||||
|
||||
@@ -82,6 +82,16 @@ namespace Umbraco.Web.Editors
|
||||
[HttpGet]
|
||||
public async Task<ActionResult> VerifyInvite(string invite)
|
||||
{
|
||||
//if you are hitting VerifyInvite, you're already signed in as a different user, and the token is invalid
|
||||
//you'll exit on one of the return RedirectToAction("Default") but you're still logged in so you just get
|
||||
//dumped at the default admin view with no detail
|
||||
if(Security.IsAuthenticated())
|
||||
{
|
||||
AuthenticationManager.SignOut(
|
||||
Core.Constants.Security.BackOfficeAuthenticationType,
|
||||
Core.Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
}
|
||||
|
||||
if (invite == null)
|
||||
{
|
||||
Logger.Warn<BackOfficeController>("VerifyUser endpoint reached with invalid token: NULL");
|
||||
@@ -125,16 +135,15 @@ namespace Umbraco.Web.Editors
|
||||
if (result.Succeeded == false)
|
||||
{
|
||||
Logger.Warn<BackOfficeController>("Could not verify email, Error: " + string.Join(",", result.Errors) + ", Token: " + invite);
|
||||
return RedirectToAction("Default");
|
||||
return new RedirectResult(Url.Action("Default") + "#/login/false?invite=3");
|
||||
}
|
||||
|
||||
//sign the user in
|
||||
|
||||
AuthenticationManager.SignOut(
|
||||
Core.Constants.Security.BackOfficeAuthenticationType,
|
||||
Core.Constants.Security.BackOfficeExternalAuthenticationType);
|
||||
|
||||
DateTime? previousLastLoginDate = identityUser.LastLoginDateUtc;
|
||||
await SignInManager.SignInAsync(identityUser, false, false);
|
||||
//reset the lastlogindate back to previous as the user hasn't actually logged in, to add a flag or similar to SignInManager would be a breaking change
|
||||
identityUser.LastLoginDateUtc = previousLastLoginDate;
|
||||
await UserManager.UpdateAsync(identityUser);
|
||||
|
||||
return new RedirectResult(Url.Action("Default") + "#/login/false?invite=1");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
@@ -56,6 +57,8 @@ namespace Umbraco.Web.Editors
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly Lazy<IDictionary<string, ILanguage>> _allLangs;
|
||||
|
||||
public object Domains { get; private set; }
|
||||
|
||||
public ContentController(IPublishedSnapshotService publishedSnapshotService, PropertyEditorCollection propertyEditors)
|
||||
{
|
||||
if (publishedSnapshotService == null) throw new ArgumentNullException(nameof(publishedSnapshotService));
|
||||
@@ -583,6 +586,17 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
private ContentItemDisplay PostSaveInternal(ContentItemSave contentItem, Func<IContent, OperationResult> saveMethod)
|
||||
{
|
||||
//Recent versions of IE/Edge may send in the full clientside file path instead of just the file name.
|
||||
//To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all
|
||||
//uploaded files to being *only* the actual file name (as it should be).
|
||||
if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any())
|
||||
{
|
||||
foreach (var file in contentItem.UploadedFiles)
|
||||
{
|
||||
file.FileName = Path.GetFileName(file.FileName);
|
||||
}
|
||||
}
|
||||
|
||||
//If we've reached here it means:
|
||||
// * Our model has been bound
|
||||
// * and validated
|
||||
@@ -1146,6 +1160,18 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
}
|
||||
|
||||
public ContentDomainsAndCulture GetCultureAndDomains(int id)
|
||||
{
|
||||
var nodeDomains = Services.DomainService.GetAssignedDomains(id, true).ToArray();
|
||||
var wildcard = nodeDomains.FirstOrDefault(d => d.IsWildcard);
|
||||
var domains = nodeDomains.Where(d => !d.IsWildcard).Select(d => new DomainDisplay(d.DomainName, d.LanguageId.GetValueOrDefault(0)));
|
||||
return new ContentDomainsAndCulture
|
||||
{
|
||||
Domains = domains,
|
||||
Language = wildcard == null || !wildcard.LanguageId.HasValue ? "undefined" : wildcard.LanguageId.ToString()
|
||||
};
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
public DomainSave PostSaveLanguageAndDomains(DomainSave model)
|
||||
{
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace Umbraco.Web.Editors
|
||||
[WebApi.UmbracoAuthorize]
|
||||
public class DashboardController : UmbracoApiController
|
||||
{
|
||||
//we have just one instance of HttpClient shared for the entire application
|
||||
private static readonly HttpClient HttpClient = new HttpClient();
|
||||
//we have baseurl as a param to make previewing easier, so we can test with a dev domain from client side
|
||||
[ValidateAngularAntiForgeryToken]
|
||||
public async Task<JObject> GetRemoteDashboardContent(string section, string baseUrl = "https://dashboard.umbraco.org/")
|
||||
@@ -54,13 +56,10 @@ namespace Umbraco.Web.Editors
|
||||
//content is null, go get it
|
||||
try
|
||||
{
|
||||
using (var web = new HttpClient())
|
||||
{
|
||||
//fetch dashboard json and parse to JObject
|
||||
var json = await web.GetStringAsync(url);
|
||||
content = JObject.Parse(json);
|
||||
result = content;
|
||||
}
|
||||
//fetch dashboard json and parse to JObject
|
||||
var json = await HttpClient.GetStringAsync(url);
|
||||
content = JObject.Parse(json);
|
||||
result = content;
|
||||
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<JObject>(key, () => result, new TimeSpan(0, 30, 0));
|
||||
}
|
||||
@@ -93,17 +92,14 @@ namespace Umbraco.Web.Editors
|
||||
//content is null, go get it
|
||||
try
|
||||
{
|
||||
using (var web = new HttpClient())
|
||||
{
|
||||
//fetch remote css
|
||||
content = await web.GetStringAsync(url);
|
||||
//fetch remote css
|
||||
content = await HttpClient.GetStringAsync(url);
|
||||
|
||||
//can't use content directly, modified closure problem
|
||||
result = content;
|
||||
//can't use content directly, modified closure problem
|
||||
result = content;
|
||||
|
||||
//save server content for 30 mins
|
||||
//save server content for 30 mins
|
||||
ApplicationCache.RuntimeCache.InsertCacheItem<string>(key, () => result, new TimeSpan(0, 30, 0));
|
||||
}
|
||||
}
|
||||
catch (HttpRequestException ex)
|
||||
{
|
||||
|
||||
@@ -10,15 +10,18 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Formatting;
|
||||
using Umbraco.Core.Models;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Xml;
|
||||
using Umbraco.Web.Models.Mapping;
|
||||
using Umbraco.Web.Search;
|
||||
using Umbraco.Web.Trees;
|
||||
using Umbraco.Web.WebApi;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
@@ -211,7 +214,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
}
|
||||
|
||||
var ancestors = GetAncestors(id, type);
|
||||
var ancestors = GetResultForAncestors(id, type);
|
||||
|
||||
//if content, skip the first node for replicating NiceUrl defaults
|
||||
if(type == UmbracoEntityTypes.Document) {
|
||||
@@ -582,9 +585,10 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<EntityBasic> GetAncestors(int id, UmbracoEntityTypes type)
|
||||
[HttpQueryStringFilter("queryStrings")]
|
||||
public IEnumerable<EntityBasic> GetAncestors(int id, UmbracoEntityTypes type, FormDataCollection queryStrings)
|
||||
{
|
||||
return GetResultForAncestors(id, type);
|
||||
return GetResultForAncestors(id, type, queryStrings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -626,7 +630,7 @@ namespace Umbraco.Web.Editors
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<EntityBasic> GetResultForAncestors(int id, UmbracoEntityTypes entityType)
|
||||
private IEnumerable<EntityBasic> GetResultForAncestors(int id, UmbracoEntityTypes entityType, FormDataCollection queryStrings = null)
|
||||
{
|
||||
var objectType = ConvertToObjectType(entityType);
|
||||
if (objectType.HasValue)
|
||||
@@ -666,12 +670,14 @@ namespace Umbraco.Web.Editors
|
||||
ids = lids.ToArray();
|
||||
}
|
||||
|
||||
var culture = queryStrings?.GetValue<string>("culture");
|
||||
|
||||
return ids.Length == 0
|
||||
? Enumerable.Empty<EntityBasic>()
|
||||
: Services.EntityService.GetAll(objectType.Value, ids)
|
||||
.WhereNotNull()
|
||||
.OrderBy(x => x.Level)
|
||||
.Select(Mapper.Map<EntityBasic>);
|
||||
.Select(x => Mapper.Map<EntityBasic>(x, opts => { opts.SetCulture(culture);}));
|
||||
}
|
||||
//now we need to convert the unknown ones
|
||||
switch (entityType)
|
||||
|
||||
@@ -7,20 +7,22 @@ using System.Threading.Tasks;
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
public class HelpController : UmbracoAuthorizedJsonController
|
||||
{
|
||||
{
|
||||
private static HttpClient _httpClient;
|
||||
public async Task<List<HelpPage>> GetContextHelpForPage(string section, string tree, string baseUrl = "https://our.umbraco.com")
|
||||
{
|
||||
var url = string.Format(baseUrl + "/Umbraco/Documentation/Lessons/GetContextHelpDocs?sectionAlias={0}&treeAlias={1}", section, tree);
|
||||
using (var web = new HttpClient())
|
||||
{
|
||||
//fetch dashboard json and parse to JObject
|
||||
var json = await web.GetStringAsync(url);
|
||||
var result = JsonConvert.DeserializeObject<List<HelpPage>>(json);
|
||||
if (result != null)
|
||||
return result;
|
||||
|
||||
return new List<HelpPage>();
|
||||
}
|
||||
if (_httpClient == null)
|
||||
_httpClient = new HttpClient();
|
||||
|
||||
//fetch dashboard json and parse to JObject
|
||||
var json = await _httpClient.GetStringAsync(url);
|
||||
var result = JsonConvert.DeserializeObject<List<HelpPage>>(json);
|
||||
if (result != null)
|
||||
return result;
|
||||
|
||||
return new List<HelpPage>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
using System;
|
||||
using AutoMapper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AutoMapper;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
@@ -45,7 +46,7 @@ namespace Umbraco.Web.Editors
|
||||
var userId = Security.GetUserId().ResultOr(0);
|
||||
var result = Services.AuditService.GetPagedItemsByUser(userId, pageNumber - 1, pageSize, out totalRecords, orderDirection, customFilter:dateQuery);
|
||||
var mapped = Mapper.Map<IEnumerable<AuditLog>>(result);
|
||||
return new PagedResult<AuditLog>(totalRecords, pageNumber + 1, pageSize)
|
||||
return new PagedResult<AuditLog>(totalRecords, pageNumber, pageSize)
|
||||
{
|
||||
Items = MapAvatarsAndNames(mapped)
|
||||
};
|
||||
|
||||
@@ -439,6 +439,17 @@ namespace Umbraco.Web.Editors
|
||||
[ModelBinder(typeof(MediaItemBinder))]
|
||||
MediaItemSave contentItem)
|
||||
{
|
||||
//Recent versions of IE/Edge may send in the full clientside file path instead of just the file name.
|
||||
//To ensure similar behavior across all browsers no matter what they do - we strip the FileName property of all
|
||||
//uploaded files to being *only* the actual file name (as it should be).
|
||||
if (contentItem.UploadedFiles != null && contentItem.UploadedFiles.Any())
|
||||
{
|
||||
foreach (var file in contentItem.UploadedFiles)
|
||||
{
|
||||
file.FileName = Path.GetFileName(file.FileName);
|
||||
}
|
||||
}
|
||||
|
||||
//If we've reached here it means:
|
||||
// * Our model has been bound
|
||||
// * and validated
|
||||
@@ -688,15 +699,7 @@ namespace Umbraco.Web.Editors
|
||||
mediaType = result.FormData["contentTypeAlias"];
|
||||
}
|
||||
|
||||
//TODO: make the media item name "nice" since file names could be pretty ugly, we have
|
||||
// string extensions to do much of this but we'll need:
|
||||
// * Pascalcase the name (use string extensions)
|
||||
// * strip the file extension
|
||||
// * underscores to spaces
|
||||
// * probably remove 'ugly' characters - let's discuss
|
||||
// All of this logic should exist in a string extensions method and be unit tested
|
||||
// http://issues.umbraco.org/issue/U4-5572
|
||||
var mediaItemName = fileName;
|
||||
var mediaItemName = fileName.ToFriendlyName();
|
||||
|
||||
var f = mediaService.CreateMedia(mediaItemName, parentId, mediaType, Security.CurrentUser.Id);
|
||||
|
||||
@@ -910,4 +913,4 @@ namespace Umbraco.Web.Editors
|
||||
return hasPathAccess;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ using Umbraco.Web.Models.ContentEditing;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi;
|
||||
using File = System.IO.File;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
@@ -63,7 +64,28 @@ namespace Umbraco.Web.Editors
|
||||
return searchResult;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This lists the RedirectUrls for a particular content item
|
||||
/// Do we need to consider paging here?
|
||||
/// </summary>
|
||||
/// <param name="contentUdi">Udi of content item to retrieve RedirectUrls for</param>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
public RedirectUrlSearchResult RedirectUrlsForContentItem(string contentUdi)
|
||||
{
|
||||
var redirectsResult = new RedirectUrlSearchResult();
|
||||
if (GuidUdi.TryParse(contentUdi, out var guidIdi))
|
||||
{
|
||||
var redirectUrlService = Services.RedirectUrlService;
|
||||
var redirects = redirectUrlService.GetContentRedirectUrls(guidIdi.Guid);
|
||||
redirectsResult.SearchResults = Mapper.Map<IEnumerable<ContentRedirectUrl>>(redirects).ToArray();
|
||||
//not doing paging 'yet'
|
||||
redirectsResult.TotalCount = redirects.Count();
|
||||
redirectsResult.CurrentPage = 1;
|
||||
redirectsResult.PageCount = 1;
|
||||
}
|
||||
return redirectsResult;
|
||||
}
|
||||
[HttpPost]
|
||||
public IHttpActionResult DeleteRedirectUrl(Guid id)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Umbraco.Web.Install
|
||||
{
|
||||
public sealed class InstallHelper
|
||||
{
|
||||
private static HttpClient _httpClient;
|
||||
private readonly DatabaseBuilder _databaseBuilder;
|
||||
private readonly HttpContextBase _httpContext;
|
||||
private readonly ILogger _logger;
|
||||
@@ -168,16 +169,17 @@ namespace Umbraco.Web.Install
|
||||
|
||||
internal IEnumerable<Package> GetStarterKits()
|
||||
{
|
||||
var packages = new List<Package>();
|
||||
if (_httpClient == null)
|
||||
_httpClient = new HttpClient();
|
||||
|
||||
var packages = new List<Package>();
|
||||
try
|
||||
{
|
||||
var requestUri = $"https://our.umbraco.com/webapi/StarterKit/Get/?umbracoVersion={UmbracoVersion.Current}";
|
||||
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Get, requestUri))
|
||||
using (var httpClient = new HttpClient())
|
||||
using (var response = httpClient.SendAsync(request).Result)
|
||||
{
|
||||
var response = _httpClient.SendAsync(request).Result;
|
||||
packages = response.Content.ReadAsAsync<IEnumerable<Package>>().Result.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Configuration;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Security;
|
||||
using Newtonsoft.Json;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Migrations.Install;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Install.Models;
|
||||
|
||||
@@ -27,6 +29,7 @@ namespace Umbraco.Web.Install.InstallSteps
|
||||
private readonly HttpContextBase _http;
|
||||
private readonly IUserService _userService;
|
||||
private readonly DatabaseBuilder _databaseBuilder;
|
||||
private static HttpClient _httpClient;
|
||||
private readonly IGlobalSettings _globalSettings;
|
||||
|
||||
public NewInstallStep(HttpContextBase http, IUserService userService, DatabaseBuilder databaseBuilder, IGlobalSettings globalSettings)
|
||||
@@ -79,15 +82,18 @@ namespace Umbraco.Web.Install.InstallSteps
|
||||
admin.Username = user.Email.Trim();
|
||||
|
||||
_userService.Save(admin);
|
||||
|
||||
|
||||
|
||||
if (user.SubscribeToNewsLetter)
|
||||
{
|
||||
if (_httpClient == null)
|
||||
_httpClient = new HttpClient();
|
||||
|
||||
var values = new NameValueCollection { { "name", admin.Name }, { "email", admin.Email } };
|
||||
var content = new StringContent(JsonConvert.SerializeObject(values), Encoding.UTF8, "application/json");
|
||||
|
||||
try
|
||||
{
|
||||
var client = new System.Net.WebClient();
|
||||
var values = new NameValueCollection { { "name", admin.Name }, { "email", admin.Email} };
|
||||
client.UploadValues("https://shop.umbraco.com/base/Ecom/SubmitEmail/installer.aspx", values);
|
||||
var response = _httpClient.PostAsync("https://shop.umbraco.com/base/Ecom/SubmitEmail/installer.aspx", content).Result;
|
||||
}
|
||||
catch { /* fail in silence */ }
|
||||
}
|
||||
@@ -114,11 +120,14 @@ namespace Umbraco.Web.Install.InstallSteps
|
||||
|
||||
public override string View
|
||||
{
|
||||
get { return RequiresExecution(null)
|
||||
//the user UI
|
||||
get
|
||||
{
|
||||
return RequiresExecution(null)
|
||||
//the user UI
|
||||
? "user"
|
||||
//the continue install UI
|
||||
: "continueinstall"; }
|
||||
//the continue install UI
|
||||
: "continueinstall";
|
||||
}
|
||||
}
|
||||
|
||||
public override bool RequiresExecution(UserModel model)
|
||||
|
||||
@@ -12,18 +12,20 @@ namespace Umbraco.Web.Media.EmbedProviders
|
||||
{
|
||||
//TODO: Make all Http calls async
|
||||
|
||||
public abstract class AbstractOEmbedProvider: IEmbedProvider
|
||||
public abstract class AbstractOEmbedProvider : IEmbedProvider
|
||||
{
|
||||
private static HttpClient _httpClient;
|
||||
|
||||
public virtual bool SupportsDimensions
|
||||
{
|
||||
get { return true; }
|
||||
}
|
||||
|
||||
[ProviderSetting]
|
||||
public string APIEndpoint{ get;set; }
|
||||
public string APIEndpoint { get; set; }
|
||||
|
||||
[ProviderSetting]
|
||||
public Dictionary<string, string> RequestParams{ get;set; }
|
||||
public Dictionary<string, string> RequestParams { get; set; }
|
||||
|
||||
public abstract string GetMarkup(string url, int maxWidth, int maxHeight);
|
||||
|
||||
@@ -51,9 +53,13 @@ namespace Umbraco.Web.Media.EmbedProviders
|
||||
|
||||
public virtual string DownloadResponse(string url)
|
||||
{
|
||||
using (var webClient = new WebClient())
|
||||
if (_httpClient == null)
|
||||
_httpClient = new HttpClient();
|
||||
|
||||
using (var request = new HttpRequestMessage(HttpMethod.Get, url))
|
||||
{
|
||||
return webClient.DownloadString(url);
|
||||
var response = _httpClient.SendAsync(request).Result;
|
||||
return response.Content.ReadAsStringAsync().Result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user