Merge branch temp8 into temp8-11502

This commit is contained in:
Stephan
2018-10-03 15:08:12 +02:00
155 changed files with 7853 additions and 3879 deletions
+2 -1
View File
@@ -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).
+1 -1
View File
@@ -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>
+1
View File
@@ -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";
}
@@ -307,7 +307,7 @@ namespace Umbraco.Core.Events
// fixme see notes above
// delete event args does NOT superceedes 'unpublished' event
if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "UnPublished")
if (argType.IsGenericType && argType.GetGenericTypeDefinition() == typeof(PublishEventArgs<>) && infos.EventDefinition.EventName == "Unpublished")
return false;
// found occurences, need to determine if this event args is superceded
+1 -1
View File
@@ -32,7 +32,7 @@
/// <summary>
/// Used when nodes are unpublished
/// </summary>
UnPublish,
Unpublish,
/// <summary>
/// Used when nodes are moved
/// </summary>
@@ -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
}
}
}
@@ -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)
@@ -988,14 +988,14 @@ namespace Umbraco.Core.Services.Implement
UnpublishResultType result;
if (culture == "*" || culture == null)
{
Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id);
Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id);
result = UnpublishResultType.Success;
}
else
{
Audit(AuditType.UnPublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id);
Audit(AuditType.Unpublish, $"Culture \"{culture}\" unpublished by user", userId, content.Id);
if (!content.Published)
Audit(AuditType.UnPublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id);
Audit(AuditType.Unpublish, $"Unpublished (culture \"{culture}\" is mandatory) by user", userId, content.Id);
result = content.Published ? UnpublishResultType.SuccessCulture : UnpublishResultType.SuccessMandatoryCulture;
}
scope.Complete();
@@ -1120,9 +1120,9 @@ namespace Umbraco.Core.Services.Implement
if (unpublishResult.Success) // and succeeded, trigger events
{
// events and audit
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), "UnPublished");
scope.Events.Dispatch(Unpublished, this, new PublishEventArgs<IContent>(content, false, false), "Unpublished");
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>(content, TreeChangeTypes.RefreshBranch).ToEventArgs());
Audit(AuditType.UnPublish, "Unpublished by user", userId, content.Id);
Audit(AuditType.Unpublish, "Unpublished by user", userId, content.Id);
scope.Complete();
return new PublishResult(PublishResultType.Success, evtMsgs, content);
}
@@ -1348,10 +1348,10 @@ namespace Umbraco.Core.Services.Implement
scope.WriteLock(Constants.Locks.ContentTree);
// if it's not trashed yet, and published, we should unpublish
// but... UnPublishing event makes no sense (not going to cancel?) and no need to save
// but... Unpublishing event makes no sense (not going to cancel?) and no need to save
// just raise the event
if (content.Trashed == false && content.Published)
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(UnPublished));
scope.Events.Dispatch(Unpublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(Unpublished));
DeleteLocked(scope, content);
@@ -2098,12 +2098,12 @@ namespace Umbraco.Core.Services.Implement
/// <summary>
/// Occurs before unpublish
/// </summary>
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> UnPublishing;
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Unpublishing;
/// <summary>
/// Occurs after unpublish
/// </summary>
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> UnPublished;
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Unpublished;
/// <summary>
/// Occurs after change.
@@ -2197,8 +2197,8 @@ namespace Umbraco.Core.Services.Implement
// ensures that a document can be unpublished
internal UnpublishResult StrategyCanUnpublish(IScope scope, IContent content, int userId, EventMessages evtMsgs)
{
// raise UnPublishing event
if (scope.Events.DispatchCancelable(UnPublishing, this, new PublishEventArgs<IContent>(content, evtMsgs)))
// raise Unpublishing event
if (scope.Events.DispatchCancelable(Unpublishing, this, new PublishEventArgs<IContent>(content, evtMsgs)))
{
Logger.Info<ContentService>("Document {ContentName} (id={ContentId}) cannot be unpublished: unpublishing was cancelled.", content.Name, content.Id);
return new UnpublishResult(UnpublishResultType.FailedCancelledByEvent, evtMsgs, content);
@@ -2282,10 +2282,10 @@ namespace Umbraco.Core.Services.Implement
foreach (var content in contents.OrderByDescending(x => x.ParentId))
{
// if it's not trashed yet, and published, we should unpublish
// but... UnPublishing event makes no sense (not going to cancel?) and no need to save
// but... Unpublishing event makes no sense (not going to cancel?) and no need to save
// just raise the event
if (content.Trashed == false && content.Published)
scope.Events.Dispatch(UnPublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(UnPublished));
scope.Events.Dispatch(Unpublished, this, new PublishEventArgs<IContent>(content, false, false), nameof(Unpublished));
// if current content has children, move them to trash
var c = content;
@@ -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;
+24
View File
@@ -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>(() =>
+1 -1
View File
@@ -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,
-1
View File
@@ -729,7 +729,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" />
@@ -99,7 +99,7 @@ namespace Umbraco.Tests.Cache
new EventDefinition<IContentService, MoveEventArgs<IContent>>(null, serviceContext.ContentService, new MoveEventArgs<IContent>(new MoveEventInfo<IContent>(null, "", -1)), "Trashed"),
new EventDefinition<IContentService, RecycleBinEventArgs>(null, serviceContext.ContentService, new RecycleBinEventArgs(Guid.NewGuid())),
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, serviceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "Published"),
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, serviceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "UnPublished"),
new EventDefinition<IContentService, PublishEventArgs<IContent>>(null, serviceContext.ContentService, new PublishEventArgs<IContent>(Enumerable.Empty<IContent>()), "Unpublished"),
new EventDefinition<IPublicAccessService, SaveEventArgs<PublicAccessEntry>>(null, serviceContext.PublicAccessService, new SaveEventArgs<PublicAccessEntry>(Enumerable.Empty<PublicAccessEntry>())),
new EventDefinition<IPublicAccessService, DeleteEventArgs<PublicAccessEntry>>(null, serviceContext.PublicAccessService, new DeleteEventArgs<PublicAccessEntry>(Enumerable.Empty<PublicAccessEntry>())),
@@ -463,7 +463,7 @@ namespace Umbraco.Tests.Integration
#endregion
#region Save, Publish & UnPublish single content
#region Save, Publish & Unpublish single content
[Test]
public void SaveUnpublishedContent()
@@ -721,7 +721,7 @@ namespace Umbraco.Tests.Integration
#endregion
#region Publish & UnPublish branch
#region Publish & Unpublish branch
[Test]
public void UnpublishContentBranch()
@@ -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;
}
}
}
@@ -169,7 +169,7 @@ namespace Umbraco.Tests.Scoping
[Test]
public void SupersededEvents2()
{
Test_UnPublished += OnDoThingFail;
Test_Unpublished += OnDoThingFail;
Test_Deleted += OnDoThingFail;
var contentService = Mock.Of<IContentService>();
@@ -178,7 +178,7 @@ namespace Umbraco.Tests.Scoping
var scopeProvider = _testObjects.GetScopeProvider(Mock.Of<ILogger>());
using (var scope = scopeProvider.CreateScope(eventDispatcher: new PassiveEventDispatcher()))
{
scope.Events.Dispatch(Test_UnPublished, contentService, new PublishEventArgs<IContent>(new [] { content }), "UnPublished");
scope.Events.Dispatch(Test_Unpublished, contentService, new PublishEventArgs<IContent>(new [] { content }), "Unpublished");
scope.Events.Dispatch(Test_Deleted, contentService, new DeleteEventArgs<IContent>(new [] { content }), "Deleted");
// see U4-10764
@@ -395,7 +395,7 @@ namespace Umbraco.Tests.Scoping
public static event TypedEventHandler<ScopeEventDispatcherTests, SaveEventArgs<decimal>> DoThing3;
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Test_UnPublished;
public static event TypedEventHandler<IContentService, PublishEventArgs<IContent>> Test_Unpublished;
public static event TypedEventHandler<IContentService, DeleteEventArgs<IContent>> Test_Deleted;
public class TestEventArgs : CancellableObjectEventArgs
@@ -1208,7 +1208,7 @@ namespace Umbraco.Tests.Services
}
[Test]
public void Can_UnPublish_Content()
public void Can_Unpublish_Content()
{
// Arrange
var contentService = ServiceContext.ContentService;
@@ -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
+3
View File
@@ -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);
}
}
}
+6012 -3181
View File
File diff suppressed because it is too large Load Diff
+3 -16
View File
@@ -1,25 +1,12 @@
{
"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",
"build": "gulp"
},
"dependencies": {},
"dependencies": {
"npm": "^6.4.1"
},
"devDependencies": {
"autoprefixer": "^6.5.0",
"bower-installer": "^1.2.0",
@@ -157,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,
@@ -168,8 +177,7 @@
methods: {
saveAndPublish: $scope.saveAndPublish,
sendToPublish: $scope.sendToPublish,
save: $scope.save,
unPublish: $scope.unPublish
unpublish: $scope.unpublish
}
});
@@ -225,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({
@@ -242,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);
@@ -257,8 +261,6 @@
editorState.set($scope.content);
}
$scope.page.buttonGroupState = "error";
return $q.reject(err);
});
}
@@ -332,52 +334,45 @@
});
}
$scope.unPublish = function () {
$scope.unpublish = function() {
clearNotifications($scope.content);
if (formHelper.submitForm({ scope: $scope, action: "unpublish", skipValidation: true })) {
var dialog = {
parentScope: $scope,
view: "views/content/overlays/unpublish.html",
variants: $scope.content.variants, //set a model property for the dialog
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
submitButtonLabelKey: "content_unpublish",
submit: function (model) {
//if there's any variants than we need to set the language and include the variants to publish
var culture = null;
if ($scope.content.variants.length > 0) {
_.each($scope.content.variants,
function (d) {
//set the culture if this is active
if (d.active === true) {
culture = d.language.culture;
}
});
model.submitButtonState = "busy";
var selectedVariants = _.filter(model.variants, function(variant) { return variant.save; });
var culturesForUnpublishing = _.map(selectedVariants, function(variant) { return variant.language.culture; });
contentResource.unpublish($scope.content.id, culturesForUnpublishing)
.then(function (data) {
formHelper.resetForm({ scope: $scope });
contentEditingHelper.reBindChangedProperties($scope.content, data);
init($scope.content);
syncTreeNode($scope.content, data.path);
$scope.page.buttonGroupState = "success";
eventsService.emit("content.unpublished", { content: $scope.content });
overlayService.close();
}, function (err) {
$scope.page.buttonGroupState = 'error';
});
},
close: function () {
overlayService.close();
}
};
overlayService.open(dialog);
}
if (formHelper.submitForm({ scope: $scope, skipValidation: true })) {
$scope.page.buttonGroupState = "busy";
eventsService.emit("content.unpublishing", { content: $scope.content });
contentResource.unPublish($scope.content.id, culture)
.then(function (data) {
formHelper.resetForm({ scope: $scope });
contentEditingHelper.handleSuccessfulSave({
scope: $scope,
savedContent: data,
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, data)
});
init($scope.content);
syncTreeNode($scope.content, data.path);
$scope.page.buttonGroupState = "success";
eventsService.emit("content.unpublished", { content: $scope.content });
}, function (err) {
$scope.page.buttonGroupState = 'error';
});
}
};
$scope.sendToPublish = function () {
clearNotifications($scope.content);
if (showSaveOrPublishDialog()) {
@@ -405,13 +400,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" })) {
@@ -457,7 +459,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";
});;
}
};
@@ -488,15 +498,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();
@@ -507,7 +516,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";
});
}
};
@@ -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) {
@@ -164,7 +186,7 @@
case "Publish":
item.logTypeColor = "success";
break;
case "UnPublish":
case "Unpublish":
case "Delete":
item.logTypeColor = "danger";
break;
@@ -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);
}
@@ -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);
@@ -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;
};
@@ -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(" ");
};
@@ -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
};
@@ -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
};
@@ -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) {
@@ -145,7 +145,7 @@ angular.module('umbraco.mocks').
"content_statistics": "Statistics",
"content_titleOptional": "Title (optional)",
"content_type": "Type",
"content_unPublish": "Unpublish",
"content_unpublish": "Unpublish",
"content_updateDate": "Last edited",
"content_updateDateDesc": "Date/time this document was created",
"content_uploadClear": "Remove file",
@@ -206,7 +206,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
/**
* @ngdoc method
* @name umbraco.resources.contentResource#unPublish
* @name umbraco.resources.contentResource#unpublish
* @methodOf umbraco.resources.contentResource
*
* @description
@@ -214,7 +214,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
*
* ##usage
* <pre>
* contentResource.unPublish(1234)
* contentResource.unpublish(1234)
* .then(function() {
* alert("node was unpulished");
* }, function(err){
@@ -225,21 +225,20 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
* @returns {Promise} resourcePromise object.
*
*/
unPublish: function (id, culture) {
unpublish: function (id, cultures) {
if (!id) {
throw "id cannot be null";
}
if (!culture) {
culture = null;
if (!cultures) {
cultures = [];
}
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"contentApiBaseUrl",
"PostUnPublish",
{ id: id, culture: culture })),
"PostUnpublish"), { id: id, cultures: cultures }),
'Failed to publish content with 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,26 +179,16 @@ 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 {
letter: ch,
labelKey: "content_unPublish",
handler: args.methods.unPublish,
labelKey: "content_unpublish",
handler: args.methods.unpublish,
hotKey: "ctrl+u",
hotKeyWhenHidden: true,
alias: "unpublish"
alias: "unpublish",
addEllipsis: args.content.variants && args.content.variants.length > 1 ? "true" : "false"
};
default:
return null;
@@ -209,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.
@@ -252,7 +242,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
// so long as it's already published and if the user has access to publish
// and the user has access to unpublish (may have been removed via Event)
if (!args.create) {
if (args.content.publishDate && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) {
var hasPublishedVariant = args.content.variants.filter(function(variant) { return (variant.state === "Published" || variant.state === "PublishedPendingChanges"); }).length > 0;
if (hasPublishedVariant && _.contains(args.content.allowedActions, "U") && _.contains(args.content.allowedActions, "Z")) {
buttons.subButtons.push(createButtonDefinition("Z"));
}
}
@@ -412,8 +403,8 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
case "Z":
return {
letter: ch,
labelKey: "content_unPublish",
handler: "unPublish"
labelKey: "content_unpublish",
handler: "unpublish"
};
default:
@@ -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;
}
@@ -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;
}
@@ -27,5 +27,5 @@
.umb-permission__description {
font-size: 13px;
color: @gray-5;
color: @gray-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>
@@ -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) {
@@ -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">
@@ -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)">
@@ -21,13 +21,13 @@
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}">
<span style="margin-bottom: 2px;">{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
</label>
<div ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-permission__description" variant="variant"></umb-variant-state>
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</div>
<div ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
@@ -21,8 +21,8 @@
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}">
<span style="margin-bottom: 2px;">{{ variant.language.name }}</span>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</label>
@@ -21,8 +21,8 @@
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}">
<span style="margin-bottom: 2px;">{{ variant.language.name }}</span>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</label>
@@ -0,0 +1,120 @@
(function () {
"use strict";
function UnpublishController($scope, localizationService) {
var vm = this;
var autoSelectedVariants = [];
vm.changeSelection = changeSelection;
vm.publishedVariantFilter = publishedVariantFilter;
vm.unpublishedVariantFilter = unpublishedVariantFilter;
function onInit() {
vm.variants = $scope.model.variants;
// set dialog title
if (!$scope.model.title) {
localizationService.localize("content_unpublish").then(function (value) {
$scope.model.title = value;
});
}
// node has variants
if (vm.variants.length !== 1) {
//now sort it so that the current one is at the top
vm.variants = _.sortBy(vm.variants, function (v) {
return v.active ? 0 : 1;
});
var active = _.find(vm.variants, function (v) {
return v.active;
});
if (active) {
//ensure that the current one is selected
active.save = true;
}
// autoselect other variants if needed
changeSelection(active);
}
}
function changeSelection(selectedVariant) {
// disable submit button if nothing is selected
var firstSelected = _.find(vm.variants, function (v) {
return v.save;
});
$scope.model.disableSubmitButton = !firstSelected; //disable submit button if there is none selected
// if a mandatory variant is selected we want to selet all other variants
// and disable selection for the others
if(selectedVariant.save && selectedVariant.language.isMandatory) {
angular.forEach(vm.variants, function(variant){
if(!variant.save && publishedVariantFilter(variant)) {
// keep track of the variants we automaically select
// so we can remove the selection again
autoSelectedVariants.push(variant.language.culture);
variant.save = true;
}
variant.disabled = true;
});
// make sure the mandatory isn't disabled so we can deselect again
selectedVariant.disabled = false;
}
// if a mandatory variant is deselected we want to deselet all the variants
// that was automatically selected so it goes back to the state before the mandatory language was selected.
// We also want to enable all checkboxes again
if(!selectedVariant.save && selectedVariant.language.isMandatory) {
angular.forEach(vm.variants, function(variant){
// check if variant was auto selected, then deselect
if(_.contains(autoSelectedVariants, variant.language.culture)) {
variant.save = false;
};
variant.disabled = false;
});
autoSelectedVariants = [];
}
}
function publishedVariantFilter(variant) {
//determine a variant is 'published' (meaning it will show up as able unpublish)
// * it has been published
// * it has been published with pending changes
return (variant.state === "Published" || variant.state === "PublishedPendingChanges");
}
function unpublishedVariantFilter(variant) {
//determine a variant is 'modified' (meaning it will NOT show up as able to unpublish)
// * it's editor is in a $dirty state
// * it is published with pending changes
return (variant.state !== "Published" && variant.state !== "PublishedPendingChanges");
}
//when this dialog is closed, remove all unpublish and disabled flags
$scope.$on('$destroy', function () {
for (var i = 0; i < vm.variants.length; i++) {
vm.variants[i].save = false;
vm.variants[i].disabled = false;
}
});
onInit();
}
angular.module("umbraco").controller("Umbraco.Overlays.UnpublishController", UnpublishController);
})();
@@ -0,0 +1,66 @@
<div ng-controller="Umbraco.Overlays.UnpublishController as vm">
<!-- 1 language -->
<div ng-if="vm.variants.length === 1">
<p><localize key="prompt_confirmUnpublish"></localize></p>
</div>
<!-- Multiple languages -->
<div ng-if="vm.variants.length > 1">
<div style="margin-bottom: 15px;">
<p><localize key="content_languagesToUnpublish"></localize></p>
</div>
<div class="umb-list umb-list--condensed">
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.publishedVariantFilter">
<ng-form name="unpublishVariantSelectorForm">
<div class="flex">
<input id="{{variant.htmlId}}"
name="unpublishVariantSelector"
type="checkbox"
ng-model="variant.save"
ng-change="vm.changeSelection(variant)"
ng-disabled="variant.disabled"
style="margin-right: 8px;"
val-server-field="{{variant.htmlId}}" />
<div>
<label for="{{variant.htmlId}}" style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
</label>
<div class="umb-permission__description">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</div>
</div>
</div>
</ng-form>
</div>
<br />
</div>
<div class="umb-list umb-list--condensed" ng-if="!vm.loading && (vm.variants | filter:vm.unpublishedVariantFilter).length > 0">
<div style="margin-bottom: 15px; font-weight: bold;">
<p><localize key="content_unpublishedLanguages"></localize></p>
</div>
<div class="umb-list-item" ng-repeat="variant in vm.variants | filter:vm.unpublishedVariantFilter">
<div>
<div style="margin-bottom: 2px;">
<span>{{ variant.language.name }}</span>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
</div>
<div>
<umb-variant-state class="umb-permission__description" variant="variant"></umb-variant-state>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -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>
@@ -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>
@@ -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
@@ -421,7 +411,7 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
$scope.unpublish = function () {
applySelected(
function (selected, index) { return contentResource.unPublish(getIdCallback(selected[index])); },
function (selected, index) { return contentResource.unpublish(getIdCallback(selected[index])); },
function (count, total) {
var key = (total === 1 ? "bulk_unpublishedItemOfItem" : "bulk_unpublishedItemOfItems");
return localizationService.localize(key, [count, total]);
@@ -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>
@@ -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>
+1 -1
View File
@@ -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" />
@@ -147,7 +147,7 @@
<key alias="statistics">Statistika</key>
<key alias="titleOptional">Titulek (volitelně)</key>
<key alias="type">Typ</key>
<key alias="unPublish">Nepublikovat</key>
<key alias="unpublish">Nepublikovat</key>
<key alias="updateDate">Naposledy změněno</key>
<key alias="updateDateDesc" version="7.0">Datum/čas poslední změny dokumentu</key>
<key alias="uploadClear">Odebrat soubor(y)</key>
@@ -154,7 +154,7 @@
<key alias="smallMove">Flyttet</key>
<key alias="smallSave">Gemt</key>
<key alias="smallDelete">Slettet</key>
<key alias="smallUnPublish">Afpubliceret</key>
<key alias="smallUnpublish">Afpubliceret</key>
<key alias="smallRollBack">Indhold tilbagerullet</key>
<key alias="smallSendToPublish">Sendt til udgivelse</key>
<key alias="smallSendToTranslate">Sendt til oversættelse</key>
@@ -229,7 +229,7 @@
<key alias="titleOptional">Titel (valgfri)</key>
<key alias="altTextOptional">Alternativ tekst (valgfri)</key>
<key alias="type">Type</key>
<key alias="unPublish">Afpublicér</key>
<key alias="unpublish">Afpublicér</key>
<key alias="unpublished">Afpubliceret</key>
<key alias="updateDate">Sidst redigeret</key>
<key alias="updateDateDesc" version="7.0">Tidspunkt for seneste redigering</key>
@@ -1619,4 +1619,4 @@ Mange hilsner fra Umbraco robotten
<key alias="noRestoreRelation">Der findes ikke nogen "Genopret" relation for dette dokument/medie. Brug "Flyt" muligheden fra menuen for at flytte det manuelt.</key>
<key alias="restoreUnderRecycled">Det dokument/medie du ønsker at genoprette under ('%0%') er i skraldespanden. Brug "Flyt" muligheden fra menuen for at flytte det manuelt.</key>
</area>
</language>
</language>
@@ -153,7 +153,7 @@
<key alias="titleOptional">Titel (optional)</key>
<key alias="altTextOptional">Alternativtext (optional)</key>
<key alias="type">Typ</key>
<key alias="unPublish">Veröffentlichung widerrufen</key>
<key alias="unpublish">Veröffentlichung widerrufen</key>
<key alias="updateDate">Zuletzt bearbeitet am</key>
<key alias="updateDateDesc" version="7.0">Letzter Änderungszeitpunkt des Dokuments</key>
<key alias="uploadClear">Datei entfernen</key>
+10 -6
View File
@@ -146,7 +146,7 @@
<area alias="auditTrails">
<key alias="atViewingFor">Viewing for</key>
<key alias="delete">Delete Content performed by user</key>
<key alias="unpublish">UnPublish performed by user</key>
<key alias="unpublish">Unpublish performed by user</key>
<key alias="publish">Save and Publish performed by user</key>
<key alias="save">Save Content performed by user</key>
<key alias="move">Move Content performed by user</key>
@@ -159,7 +159,7 @@
<key alias="smallMove">Move</key>
<key alias="smallSave">Save</key>
<key alias="smallDelete">Delete</key>
<key alias="smallUnPublish">Unpublish</key>
<key alias="smallUnpublish">Unpublish</key>
<key alias="smallRollBack">Rollback</key>
<key alias="smallSendToPublish">Send To Publish</key>
<key alias="smallSendToTranslate">Send To Translation</key>
@@ -238,7 +238,7 @@
<key alias="titleOptional">Title (optional)</key>
<key alias="altTextOptional">Alternative text (optional)</key>
<key alias="type">Type</key>
<key alias="unPublish">Unpublish</key>
<key alias="unpublish">Unpublish</key>
<key alias="unpublished">Unpublished</key>
<key alias="updateDate">Last edited</key>
<key alias="updateDateDesc" version="7.0">Date/time this document was edited</key>
@@ -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 youll 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>
@@ -149,7 +149,7 @@
<area alias="auditTrails">
<key alias="atViewingFor">Viewing for</key>
<key alias="delete">Delete Content performed by user</key>
<key alias="unpublish">UnPublish performed by user</key>
<key alias="unpublish">Unpublish performed by user</key>
<key alias="publish">Save and Publish performed by user</key>
<key alias="save">Save Content performed by user</key>
<key alias="move">Move Content performed by user</key>
@@ -162,7 +162,7 @@
<key alias="smallMove">Move</key>
<key alias="smallSave">Save</key>
<key alias="smallDelete">Delete</key>
<key alias="smallUnPublish">Unpublish</key>
<key alias="smallUnpublish">Unpublish</key>
<key alias="smallRollBack">Rollback</key>
<key alias="smallSendToPublish">Send To Publish</key>
<key alias="smallSendToTranslate">Send To Translation</key>
@@ -243,7 +243,7 @@
<key alias="titleOptional">Title (optional)</key>
<key alias="altTextOptional">Alternative text (optional)</key>
<key alias="type">Type</key>
<key alias="unPublish">Unpublish</key>
<key alias="unpublish">Unpublish</key>
<key alias="unpublished">Draft</key>
<key alias="notCreated">Not created</key>
<key alias="updateDate">Last edited</key>
@@ -266,7 +266,9 @@
<key alias="languagesToPublish">What languages would you like to publish?</key>
<key alias="languagesToSave">What languages would you like to save?</key>
<key alias="languagesToSendForApproval">What languages would you like to send for approval?</key>
<key alias="languagesToUnpublish">Select the languages to unpublish. Unpublishing a mandatory language will unpublish all languages.</key>
<key alias="publishedLanguages">Published Languages</key>
<key alias="unpublishedLanguages">Unpublished Languages</key>
<key alias="unmodifiedLanguages">Unmodified Languages</key>
<key alias="readyToPublish">Ready to Publish?</key>
<key alias="readyToSave">Ready to Save?</key>
@@ -700,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>
@@ -1435,7 +1438,8 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="templateSavedHeader">Template saved</key>
<key alias="templateSavedText">Template saved without any errors!</key>
<key alias="contentUnpublished">Content unpublished</key>
<key alias="contentVariationUnpublished">Content variation %0% unpublished</key>
<key alias="contentCultureUnpublished">Content variation %0% unpublished</key>
<key alias="contentMandatoryCultureUnpublished">The mandatory language '%0%' was unpublished. All languages for this content item are now unpublished.</key>
<key alias="partialViewSavedHeader">Partial view saved</key>
<key alias="partialViewSavedText">Partial view saved without any errors!</key>
<key alias="partialViewErrorHeader">Partial view not saved</key>
@@ -1700,13 +1704,13 @@ 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">
<key alias="addLanguage">Add language</key>
<key alias="mandatoryLanguage">Mandatory</key>
<key alias="mandatoryLanguage">Mandatory language</key>
<key alias="mandatoryLanguageHelp">Properties on this language have to be filled out before the node can be published.</key>
<key alias="defaultLanguage">Default language</key>
<key alias="defaultLanguageHelp">An Umbraco site can only have one default language set.</key>
@@ -1888,7 +1892,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>
@@ -1952,6 +1956,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 youll 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>
@@ -2212,6 +2217,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>
@@ -203,7 +203,7 @@
<key alias="titleOptional">Título (opcional)</key>
<key alias="altTextOptional">Texto alternativo (opcional)</key>
<key alias="type">Tipo</key>
<key alias="unPublish">Ocultar</key>
<key alias="unpublish">Ocultar</key>
<key alias="updateDate">Última actualización</key>
<key alias="updateDateDesc" version="7.0">Fecha/hora este documento fue modificado</key>
<key alias="uploadClear">Eliminar archivo</key>
@@ -159,7 +159,7 @@
<key alias="smallMove">Déplacement</key>
<key alias="smallSave">Sauvegarde</key>
<key alias="smallDelete">Suppression</key>
<key alias="smallUnPublish">Dépublication</key>
<key alias="smallUnpublish">Dépublication</key>
<key alias="smallRollBack">Récupération</key>
<key alias="smallSendToPublish">Envoi pour publication</key>
<key alias="smallSendToTranslate">Envoi pour traduction</key>
@@ -234,7 +234,7 @@
<key alias="titleOptional">Titre (optionnel)</key>
<key alias="altTextOptional">Texte alternatif (optionnel)</key>
<key alias="type">Type</key>
<key alias="unPublish">Dépublier</key>
<key alias="unpublish">Dépublier</key>
<key alias="unpublished">Dépublié</key>
<key alias="updateDate">Dernière édition</key>
<key alias="updateDateDesc" version="7.0">Date/heure à laquelle ce document a été édité</key>
@@ -105,7 +105,7 @@
<key alias="statistics">סטטיסטיקות</key>
<key alias="titleOptional">כותרת (לא חובה)</key>
<key alias="type">סוג</key>
<key alias="unPublish">ממתין לפירסום</key>
<key alias="unpublish">ממתין לפירסום</key>
<key alias="updateDate">נערך לאחרונה</key>
<key alias="uploadClear">הסר קובץ</key>
<key alias="urls">קשר למסמך</key>
@@ -108,7 +108,7 @@
<key alias="statistics">Statistiche</key>
<key alias="titleOptional">Titolo (opzionale)</key>
<key alias="type">Tipo</key>
<key alias="unPublish">Non pubblicare</key>
<key alias="unpublish">Non pubblicare</key>
<key alias="updateDate">Ultima modifica</key>
<key alias="uploadClear">Rimuovi il file</key>
<key alias="urls">Link al documento</key>
@@ -155,7 +155,7 @@
<key alias="titleOptional">タイトル (オプション)</key>
<key alias="altTextOptional">代替テキスト (オプション)</key>
<key alias="type"></key>
<key alias="unPublish">非公開</key>
<key alias="unpublish">非公開</key>
<key alias="updateDate">最終更新日時</key>
<key alias="updateDateDesc" version="7.0">このドキュメントが最後に更新された日時</key>
<key alias="uploadClear">ファイルの消去</key>
@@ -104,7 +104,7 @@
<key alias="statistics">통계</key>
<key alias="titleOptional">제목(옵션)</key>
<key alias="type">유형</key>
<key alias="unPublish">발행취소</key>
<key alias="unpublish">발행취소</key>
<key alias="updateDate">마지막 수정일</key>
<key alias="uploadClear">파일 삭제</key>
<key alias="urls">문서에 링크</key>
@@ -151,7 +151,7 @@
<key alias="titleOptional">Tittel (valgfri)</key>
<key alias="altTextOptional">Alternativ tekst (valgfri)</key>
<key alias="type">Type</key>
<key alias="unPublish">Avpubliser</key>
<key alias="unpublish">Avpubliser</key>
<key alias="updateDate">Sist endret</key>
<key alias="updateDateDesc" version="7.0">Tidspunkt for siste endring</key>
<key alias="uploadClear">Fjern fil</key>

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