Merge remote-tracking branch 'origin/v8/dev' into v8/bugfix/AB3323-SqlMainDom

This commit is contained in:
Shannon
2019-12-23 18:21:03 +11:00
135 changed files with 2237 additions and 1956 deletions
-4
View File
@@ -313,10 +313,6 @@
$this.CopyFiles("$src\Umbraco.Web.UI\umbraco\js", "*", "$tmp\WebApp\umbraco\js")
$this.CopyFiles("$src\Umbraco.Web.UI\umbraco\lib", "*", "$tmp\WebApp\umbraco\lib")
$this.CopyFiles("$src\Umbraco.Web.UI\umbraco\views", "*", "$tmp\WebApp\umbraco\views")
# copied too much - delete the LESS map directory
# that was copied over into "$tmp\WebApp\umbraco\assets\css\maps"
Remove-Item "$tmp\WebApp\umbraco\assets\css\maps" -Force -Recurse -ErrorAction SilentlyContinue
})
$ubuild.DefineMethod("PackageZip",
+46 -17
View File
@@ -18,19 +18,52 @@ namespace Umbraco.Core.Composing
private readonly Composition _composition;
private readonly IProfilingLogger _logger;
private readonly IEnumerable<Type> _composerTypes;
private readonly IEnumerable<Attribute> _enableDisableAttributes;
private const int LogThresholdMilliseconds = 100;
/// <summary>
/// Initializes a new instance of the <see cref="Composers"/> class.
/// Initializes a new instance of the <see cref="Composers" /> class.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="composerTypes">The composer types.</param>
/// <param name="logger">A profiling logger.</param>
/// <param name="composerTypes">The <see cref="IComposer" /> types.</param>
/// <param name="logger">The profiling logger.</param>
[Obsolete("This overload only gets the EnableComposer/DisableComposer attributes from the composerTypes assemblies.")]
public Composers(Composition composition, IEnumerable<Type> composerTypes, IProfilingLogger logger)
: this(composition, composerTypes, Enumerable.Empty<Attribute>(), logger)
{
var enableDisableAttributes = new List<Attribute>();
var assemblies = composerTypes.Select(t => t.Assembly).Distinct();
foreach (var assembly in assemblies)
{
enableDisableAttributes.AddRange(assembly.GetCustomAttributes(typeof(EnableComposerAttribute)));
enableDisableAttributes.AddRange(assembly.GetCustomAttributes(typeof(DisableComposerAttribute)));
}
_enableDisableAttributes = enableDisableAttributes;
}
/// <summary>
/// Initializes a new instance of the <see cref="Composers" /> class.
/// </summary>
/// <param name="composition">The composition.</param>
/// <param name="composerTypes">The <see cref="IComposer" /> types.</param>
/// <param name="enableDisableAttributes">The <see cref="EnableComposerAttribute" /> and/or <see cref="DisableComposerAttribute" /> attributes.</param>
/// <param name="logger">The profiling logger.</param>
/// <exception cref="ArgumentNullException">composition
/// or
/// composerTypes
/// or
/// enableDisableAttributes
/// or
/// logger</exception>
public Composers(Composition composition, IEnumerable<Type> composerTypes, IEnumerable<Attribute> enableDisableAttributes, IProfilingLogger logger)
{
_composition = composition ?? throw new ArgumentNullException(nameof(composition));
_composerTypes = composerTypes ?? throw new ArgumentNullException(nameof(composerTypes));
_enableDisableAttributes = enableDisableAttributes ?? throw new ArgumentNullException(nameof(enableDisableAttributes));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -103,7 +136,7 @@ namespace Umbraco.Core.Composing
.ToList();
// enable or disable composers
EnableDisableComposers(composerTypeList);
EnableDisableComposers(_enableDisableAttributes, composerTypeList);
void GatherInterfaces<TAttribute>(Type type, Func<TAttribute, Type> getTypeInAttribute, HashSet<Type> iset, List<Type> set2)
where TAttribute : Attribute
@@ -218,7 +251,7 @@ namespace Umbraco.Core.Composing
return text.ToString();
}
private static void EnableDisableComposers(ICollection<Type> types)
private static void EnableDisableComposers(IEnumerable<Attribute> enableDisableAttributes, ICollection<Type> types)
{
var enabled = new Dictionary<Type, EnableInfo>();
@@ -240,20 +273,16 @@ namespace Umbraco.Core.Composing
enableInfo.Weight = weight2;
}
var assemblies = types.Select(x => x.Assembly).Distinct();
foreach (var assembly in assemblies)
foreach (var attr in enableDisableAttributes.OfType<EnableComposerAttribute>())
{
foreach (var attr in assembly.GetCustomAttributes<EnableComposerAttribute>())
{
var type = attr.EnabledType;
UpdateEnableInfo(type, 2, enabled, true);
}
var type = attr.EnabledType;
UpdateEnableInfo(type, 2, enabled, true);
}
foreach (var attr in assembly.GetCustomAttributes<DisableComposerAttribute>())
{
var type = attr.DisabledType;
UpdateEnableInfo(type, 2, enabled, false);
}
foreach (var attr in enableDisableAttributes.OfType<DisableComposerAttribute>())
{
var type = attr.DisabledType;
UpdateEnableInfo(type, 2, enabled, false);
}
foreach (var composerType in types)
+43
View File
@@ -506,6 +506,49 @@ namespace Umbraco.Core.Composing
#endregion
#region Get Assembly Attributes
/// <summary>
/// Gets the assembly attributes of the specified type <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The attribute type.</typeparam>
/// <returns>
/// The assembly attributes of the specified type <typeparamref name="T" />.
/// </returns>
public IEnumerable<T> GetAssemblyAttributes<T>()
where T : Attribute
{
return AssembliesToScan.SelectMany(a => a.GetCustomAttributes<T>()).ToList();
}
/// <summary>
/// Gets all the assembly attributes.
/// </summary>
/// <returns>
/// All assembly attributes.
/// </returns>
public IEnumerable<Attribute> GetAssemblyAttributes()
{
return AssembliesToScan.SelectMany(a => a.GetCustomAttributes()).ToList();
}
/// <summary>
/// Gets the assembly attributes of the specified <paramref name="attributeTypes" />.
/// </summary>
/// <param name="attributeTypes">The attribute types.</param>
/// <returns>
/// The assembly attributes of the specified types.
/// </returns>
/// <exception cref="ArgumentNullException">attributeTypes</exception>
public IEnumerable<Attribute> GetAssemblyAttributes(params Type[] attributeTypes)
{
if (attributeTypes == null) throw new ArgumentNullException(nameof(attributeTypes));
return AssembliesToScan.SelectMany(a => attributeTypes.SelectMany(at => a.GetCustomAttributes(at))).ToList();
}
#endregion
#region Get Types
/// <summary>
@@ -0,0 +1,8 @@
namespace Umbraco.Core.Configuration.UmbracoSettings
{
public interface IKeepAliveSection : IUmbracoConfigurationSection
{
bool DisableKeepAliveTask { get; }
string KeepAlivePingUrl { get; }
}
}
@@ -15,5 +15,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
ILoggingSection Logging { get; }
IWebRoutingSection WebRouting { get; }
IKeepAliveSection KeepAlive { get; }
}
}
@@ -0,0 +1,13 @@
using System.Configuration;
namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class KeepAliveElement : ConfigurationElement, IKeepAliveSection
{
[ConfigurationProperty("disableKeepAliveTask", DefaultValue = "false")]
public bool DisableKeepAliveTask => (bool)base["disableKeepAliveTask"];
[ConfigurationProperty("keepAlivePingUrl", DefaultValue = "{umbracoApplicationUrl}/api/keepalive/ping")]
public string KeepAlivePingUrl => (string)base["keepAlivePingUrl"];
}
}
@@ -19,10 +19,12 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
[ConfigurationProperty("logging")]
internal LoggingElement Logging => (LoggingElement)this["logging"];
[ConfigurationProperty("web.routing")]
internal WebRoutingElement WebRouting => (WebRoutingElement)this["web.routing"];
[ConfigurationProperty("keepAlive")]
internal KeepAliveElement KeepAlive => (KeepAliveElement)this["keepAlive"];
IContentSection IUmbracoSettingsSection.Content => Content;
ISecuritySection IUmbracoSettingsSection.Security => Security;
@@ -34,5 +36,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
ILoggingSection IUmbracoSettingsSection.Logging => Logging;
IWebRoutingSection IUmbracoSettingsSection.WebRouting => WebRouting;
IKeepAliveSection IUmbracoSettingsSection.KeepAlive => KeepAlive;
}
}
+8 -7
View File
@@ -13,6 +13,7 @@ using Umbraco.Core.IO;
using Umbraco.Core.Models;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Core.Services.Implement;
@@ -23,6 +24,8 @@ namespace Umbraco.Core
// this ain't pretty
private static IMediaFileSystem _mediaFileSystem;
private static IMediaFileSystem MediaFileSystem => _mediaFileSystem ?? (_mediaFileSystem = Current.MediaFileSystem);
private static readonly PropertyEditorCollection _propertyEditors;
private static PropertyEditorCollection PropertyEditors = _propertyEditors ?? (_propertyEditors = Current.PropertyEditors);
#region IContent
@@ -162,14 +165,12 @@ namespace Umbraco.Core
// Fixes https://github.com/umbraco/Umbraco-CMS/issues/3937 - Assigning a new file to an
// existing IMedia with extension SetValue causes exception 'Illegal characters in path'
string oldpath = null;
if (property.GetValue(culture, segment) is string svalue)
var value = property.GetValue(culture, segment);
if (PropertyEditors.TryGet(propertyTypeAlias, out var editor)
&& editor is IDataEditorWithMediaPath dataEditor)
{
if (svalue.DetectIsJson())
{
// the property value is a JSON serialized image crop data set - grab the "src" property as the file source
var jObject = JsonConvert.DeserializeObject<JObject>(svalue);
svalue = jObject != null ? jObject.GetValueAsString("src") : svalue;
}
var svalue = dataEditor.GetMediaPath(value);
oldpath = MediaFileSystem.GetRelativePath(svalue);
}
@@ -19,6 +19,8 @@ namespace Umbraco.Core.Manifest
// '-content/foo', // hide for content type 'foo'
// '+content/*', // show for all other content types
// '+media/*', // show for all media types
// '-member/foo' // hide for member type 'foo'
// '+member/*' // show for all member types
// '+role/admin' // show for admin users. Role based permissions will override others.
// ]
// },
@@ -56,6 +58,10 @@ namespace Umbraco.Core.Manifest
partA = "media";
partB = media.ContentType.Alias;
break;
case IMember member:
partA = "member";
partB = member.ContentType.Alias;
break;
default:
return null;
+8 -27
View File
@@ -1,10 +1,8 @@
using System;
using System.Linq;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.Logging;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Models
{
@@ -18,29 +16,12 @@ namespace Umbraco.Core.Models
if (!media.Properties.TryGetValue(propertyAlias, out var property))
return string.Empty;
// TODO: would need to be adjusted to variations, when media become variants
if (!(property.GetValue() is string jsonString))
return string.Empty;
if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.UploadField)
return jsonString;
if (property.PropertyType.PropertyEditorAlias == Constants.PropertyEditors.Aliases.ImageCropper)
if (Current.PropertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor)
&& editor is IDataEditorWithMediaPath dataEditor)
{
if (jsonString.DetectIsJson() == false)
return jsonString;
try
{
var json = JsonConvert.DeserializeObject<JObject>(jsonString);
if (json["src"] != null)
return json["src"].Value<string>();
}
catch (Exception ex)
{
logger.Error<ImageCropperValueConverter>(ex, "Could not parse the string '{JsonString}' to a json object", jsonString);
return string.Empty;
}
// TODO: would need to be adjusted to variations, when media become variants
var value = property.GetValue();
return dataEditor.GetMediaPath(value);
}
// Without knowing what it is, just adding a string here might not be very nice
@@ -1,17 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence.Dtos;
using Umbraco.Core.Persistence.Repositories;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Core.Persistence.Factories
{
internal class ContentBaseFactory
{
private static readonly Regex MediaPathPattern = new Regex(@"(/media/.+?)(?:['""]|$)", RegexOptions.Compiled);
/// <summary>
/// Builds an IContent item from a dto and content type.
/// </summary>
@@ -189,7 +187,7 @@ namespace Umbraco.Core.Persistence.Factories
/// <summary>
/// Builds a dto from an IMedia item.
/// </summary>
public static MediaDto BuildDto(IMedia entity)
public static MediaDto BuildDto(PropertyEditorCollection propertyEditors, IMedia entity)
{
var contentDto = BuildContentDto(entity, Constants.ObjectTypes.Media);
@@ -197,7 +195,7 @@ namespace Umbraco.Core.Persistence.Factories
{
NodeId = entity.Id,
ContentDto = contentDto,
MediaVersionDto = BuildMediaVersionDto(entity, contentDto)
MediaVersionDto = BuildMediaVersionDto(propertyEditors, entity, contentDto)
};
return dto;
@@ -291,12 +289,20 @@ namespace Umbraco.Core.Persistence.Factories
return dto;
}
private static MediaVersionDto BuildMediaVersionDto(IMedia entity, ContentDto contentDto)
private static MediaVersionDto BuildMediaVersionDto(PropertyEditorCollection propertyEditors, IMedia entity, ContentDto contentDto)
{
// try to get a path from the string being stored for media
// TODO: only considering umbracoFile
TryMatch(entity.GetValue<string>("umbracoFile"), out var path);
string path = null;
if (entity.Properties.TryGetValue(Constants.Conventions.Media.File, out var property)
&& propertyEditors.TryGet(property.PropertyType.PropertyEditorAlias, out var editor)
&& editor is IDataEditorWithMediaPath dataEditor)
{
var value = property.GetValue();
path = dataEditor.GetMediaPath(value);
}
var dto = new MediaVersionDto
{
@@ -308,22 +314,5 @@ namespace Umbraco.Core.Persistence.Factories
return dto;
}
// TODO: this should NOT be here?!
// more dark magic ;-(
internal static bool TryMatch(string text, out string path)
{
// In v8 we should allow exposing this via the property editor in a much nicer way so that the property editor
// can tell us directly what any URL is for a given property if it contains an asset
path = null;
if (string.IsNullOrWhiteSpace(text)) return false;
var m = MediaPathPattern.Match(text);
if (!m.Success || m.Groups.Count != 2) return false;
path = m.Groups[1].Value;
return true;
}
}
}
@@ -228,7 +228,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
entity.SanitizeEntityPropertiesForXmlStorage();
// create the dto
var dto = ContentBaseFactory.BuildDto(entity);
var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity);
// derive path and level from parent
var parent = GetParentNodeDto(entity.ParentId);
@@ -317,7 +317,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
}
// create the dto
var dto = ContentBaseFactory.BuildDto(entity);
var dto = ContentBaseFactory.BuildDto(PropertyEditors, entity);
// update the node dto
var nodeDto = dto.ContentDto.NodeDto;
@@ -542,6 +542,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
media.ResetDirtyProperties(false);
return media;
}
}
}
@@ -0,0 +1,19 @@
namespace Umbraco.Core.PropertyEditors
{
/// <summary>
/// Must be implemented by property editors that store media and return media paths
/// </summary>
/// <remarks>
/// Currently there are only 2x core editors that do this: upload and image cropper.
/// It would be possible for developers to know implement their own media property editors whereas previously this was not possible.
/// </remarks>
public interface IDataEditorWithMediaPath
{
/// <summary>
/// Returns the media path for the value stored for a property
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
string GetMediaPath(object value);
}
}
+8 -2
View File
@@ -170,8 +170,14 @@ namespace Umbraco.Core.Runtime
// get composers, and compose
var composerTypes = ResolveComposerTypes(typeLoader);
composition.WithCollectionBuilder<ComponentCollectionBuilder>();
var composers = new Composers(composition, composerTypes, ProfilingLogger);
IEnumerable<Attribute> enableDisableAttributes;
using (ProfilingLogger.DebugDuration<CoreRuntime>("Scanning enable/disable composer attributes"))
{
enableDisableAttributes = typeLoader.GetAssemblyAttributes(typeof(EnableComposerAttribute), typeof(DisableComposerAttribute));
}
var composers = new Composers(composition, composerTypes, enableDisableAttributes, ProfilingLogger);
composers.Compose();
// create the factory
@@ -104,5 +104,17 @@ namespace Umbraco.Core.Services
return found;
}
/// <summary>
/// Gets the concrete assigned permissions for the provided user and node
/// </summary>
/// <param name="userService"></param>
/// <param name="user"></param>
/// <param name="nodeId"></param>
internal static string[] GetAssignedPermissions(this IUserService userService, IUser user, int nodeId)
{
var permissionCollection = userService.GetPermissions(user, nodeId);
return permissionCollection.SelectMany(c => c.AssignedPermissions).Distinct().ToArray();
}
}
}
+3
View File
@@ -284,6 +284,7 @@
<Compile Include="Models\PublishedContent\IPublishedContentType.cs" />
<Compile Include="Models\PublishedContent\IPublishedPropertyType.cs" />
<Compile Include="PropertyEditors\ConfigurationFieldsExtensions.cs" />
<Compile Include="PropertyEditors\IDataEditorWithMediaPath.cs" />
<Compile Include="PropertyEditors\IIgnoreUserStartNodesConfig.cs" />
<Compile Include="PublishedContentExtensions.cs" />
<Compile Include="Models\PublishedContent\UrlMode.cs" />
@@ -355,6 +356,7 @@
<Compile Include="Configuration\UmbracoSettings\ITourSection.cs" />
<Compile Include="Configuration\UmbracoSettings\IUmbracoSettingsSection.cs" />
<Compile Include="Configuration\UmbracoSettings\IWebRoutingSection.cs" />
<Compile Include="Configuration\UmbracoSettings\IKeepAliveSection.cs" />
<Compile Include="Configuration\UmbracoSettings\LoggingElement.cs" />
<Compile Include="Configuration\UmbracoSettings\NotificationsElement.cs" />
<Compile Include="Configuration\UmbracoSettings\RequestHandlerElement.cs" />
@@ -364,6 +366,7 @@
<Compile Include="Configuration\UmbracoSettings\UmbracoSettingsSection.cs" />
<Compile Include="Configuration\UmbracoSettings\UrlReplacingElement.cs" />
<Compile Include="Configuration\UmbracoSettings\WebRoutingElement.cs" />
<Compile Include="Configuration\UmbracoSettings\KeepAliveElement.cs" />
<Compile Include="Configuration\UmbracoVersion.cs" />
<Compile Include="Constants-Applications.cs" />
<Compile Include="Constants-Conventions.cs" />
+48 -38
View File
@@ -5,7 +5,6 @@ using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Compose;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
@@ -13,8 +12,6 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Mappers;
using Umbraco.Core.Scoping;
[assembly:DisableComposer(typeof(Umbraco.Tests.Components.ComponentTests.Composer26))]
namespace Umbraco.Tests.Components
{
[TestFixture]
@@ -65,10 +62,11 @@ namespace Umbraco.Tests.Components
public void Boot1A()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = TypeArray<Composer1, Composer2, Composer3, Composer4>();
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
// 2 is Core and requires 4
// 3 is User - goes away with RuntimeLevel.Unknown
@@ -104,10 +102,11 @@ namespace Umbraco.Tests.Components
public void Boot1B()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Run));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Run));
var types = TypeArray<Composer1, Composer2, Composer3, Composer4>();
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
// 2 is Core and requires 4
// 3 is User - stays with RuntimeLevel.Run
@@ -120,10 +119,11 @@ namespace Umbraco.Tests.Components
public void Boot2()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = TypeArray<Composer20, Composer21>();
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
// 21 is required by 20
// => reorder components accordingly
@@ -135,10 +135,11 @@ namespace Umbraco.Tests.Components
public void Boot3()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = TypeArray<Composer22, Composer24, Composer25>();
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
// i23 requires 22
// 24, 25 implement i23
@@ -152,10 +153,11 @@ namespace Umbraco.Tests.Components
public void BrokenRequire()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = TypeArray<Composer1, Composer2, Composer3>();
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
try
{
@@ -175,10 +177,11 @@ namespace Umbraco.Tests.Components
public void BrokenRequired()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = TypeArray<Composer2, Composer4, Composer13>();
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
// 2 is Core and requires 4
// 13 is required by 1
@@ -196,6 +199,7 @@ namespace Umbraco.Tests.Components
Terminated.Clear();
var register = MockRegister();
var typeLoader = MockTypeLoader();
var factory = MockFactory(m =>
{
m.Setup(x => x.TryGetInstance(It.Is<Type>(t => t == typeof (ISomeResource)))).Returns(() => new SomeResource());
@@ -210,10 +214,10 @@ namespace Umbraco.Tests.Components
throw new NotSupportedException(type.FullName);
});
});
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = new[] { typeof(Composer1), typeof(Composer5), typeof(Composer5a) };
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Assert.IsEmpty(Composed);
composers.Compose();
@@ -236,10 +240,11 @@ namespace Umbraco.Tests.Components
public void Requires1()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = new[] { typeof(Composer6), typeof(Composer7), typeof(Composer8) };
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
composers.Compose();
Assert.AreEqual(2, Composed.Count);
@@ -251,10 +256,11 @@ namespace Umbraco.Tests.Components
public void Requires2A()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) };
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
composers.Compose();
Assert.AreEqual(2, Composed.Count);
@@ -267,11 +273,12 @@ namespace Umbraco.Tests.Components
public void Requires2B()
{
var register = MockRegister();
var typeLoader = MockTypeLoader();
var factory = MockFactory();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Run));
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Run));
var types = new[] { typeof(Composer9), typeof(Composer2), typeof(Composer4) };
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
composers.Compose();
var builder = composition.WithCollectionBuilder<ComponentCollectionBuilder>();
@@ -287,35 +294,36 @@ namespace Umbraco.Tests.Components
public void WeakDependencies()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = new[] { typeof(Composer10) };
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
composers.Compose();
Assert.AreEqual(1, Composed.Count);
Assert.AreEqual(typeof(Composer10), Composed[0]);
types = new[] { typeof(Composer11) };
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
Assert.Throws<Exception>(() => composers.Compose());
Console.WriteLine("throws:");
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
var requirements = composers.GetRequirements(false);
Console.WriteLine(Composers.GetComposersReport(requirements));
types = new[] { typeof(Composer2) };
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
Assert.Throws<Exception>(() => composers.Compose());
Console.WriteLine("throws:");
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
requirements = composers.GetRequirements(false);
Console.WriteLine(Composers.GetComposersReport(requirements));
types = new[] { typeof(Composer12) };
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
composers.Compose();
Assert.AreEqual(1, Composed.Count);
@@ -326,10 +334,11 @@ namespace Umbraco.Tests.Components
public void DisableMissing()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = new[] { typeof(Composer6), typeof(Composer8) }; // 8 disables 7 which is not in the list
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
Composed.Clear();
composers.Compose();
Assert.AreEqual(2, Composed.Count);
@@ -341,16 +350,18 @@ namespace Umbraco.Tests.Components
public void AttributesPriorities()
{
var register = MockRegister();
var composition = new Composition(register, MockTypeLoader(), Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var typeLoader = MockTypeLoader();
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Unknown));
var types = new[] { typeof(Composer26) }; // 26 disabled by assembly attribute
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var types = new[] { typeof(Composer26) };
var enableDisableAttributes = new[] { new DisableComposerAttribute(typeof(Composer26)) };
var composers = new Composers(composition, types, enableDisableAttributes, Mock.Of<IProfilingLogger>());
Composed.Clear();
composers.Compose();
Assert.AreEqual(0, Composed.Count); // 26 gone
types = new[] { typeof(Composer26), typeof(Composer27) }; // 26 disabled by assembly attribute, enabled by 27
composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
composers = new Composers(composition, types, enableDisableAttributes, Mock.Of<IProfilingLogger>());
Composed.Clear();
composers.Compose();
Assert.AreEqual(2, Composed.Count); // both
@@ -367,7 +378,7 @@ namespace Umbraco.Tests.Components
var composition = new Composition(register, typeLoader, Mock.Of<IProfilingLogger>(), MockRuntimeState(RuntimeLevel.Run));
var types = typeLoader.GetTypes<IComposer>().Where(x => x.FullName.StartsWith("Umbraco.Core.") || x.FullName.StartsWith("Umbraco.Web"));
var composers = new Composers(composition, types, Mock.Of<IProfilingLogger>());
var composers = new Composers(composition, types, Enumerable.Empty<Attribute>(), Mock.Of<IProfilingLogger>());
var requirements = composers.GetRequirements();
var report = Composers.GetComposersReport(requirements);
Console.WriteLine(report);
@@ -506,7 +517,6 @@ namespace Umbraco.Tests.Components
public class Composer25 : TestComposerBase, IComposer23
{ }
// disabled by assembly attribute
public class Composer26 : TestComposerBase
{ }
@@ -4,13 +4,18 @@ using Moq;
using Newtonsoft.Json;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Configuration.UmbracoSettings;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.PropertyEditors.ValueConverters;
using Umbraco.Core.Services;
using Umbraco.Tests.PublishedContent;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.Testing;
using Umbraco.Web.PropertyEditors;
using Umbraco.Web.Routing;
namespace Umbraco.Tests.Routing
@@ -25,7 +30,17 @@ namespace Umbraco.Tests.Routing
{
base.SetUp();
_mediaUrlProvider = new DefaultMediaUrlProvider();
var logger = Mock.Of<ILogger>();
var mediaFileSystemMock = Mock.Of<IMediaFileSystem>();
var contentSection = Mock.Of<IContentSection>();
var dataTypeService = Mock.Of<IDataTypeService>();
var propertyEditors = new PropertyEditorCollection(new DataEditorCollection(new IDataEditor[]
{
new FileUploadPropertyEditor(logger, mediaFileSystemMock, contentSection),
new ImageCropperPropertyEditor(logger, mediaFileSystemMock, contentSection, dataTypeService),
}));
_mediaUrlProvider = new DefaultMediaUrlProvider(propertyEditors);
}
public override void TearDown()
@@ -54,10 +69,10 @@ namespace Umbraco.Tests.Routing
const string expected = "/media/rfeiw584/test.jpg";
var configuration = new ImageCropperConfiguration();
var imageCropperValue = new ImageCropperValue
var imageCropperValue = JsonConvert.SerializeObject(new ImageCropperValue
{
Src = expected
};
});
var umbracoContext = GetUmbracoContext("/", mediaUrlProviders: new[] { _mediaUrlProvider });
var publishedContent = CreatePublishedContent(Constants.PropertyEditors.Aliases.ImageCropper, imageCropperValue, configuration);
@@ -121,8 +136,8 @@ namespace Umbraco.Tests.Routing
PropertyType = umbracoFilePropertyType,
};
property.SetValue("en", enMediaUrl, true);
property.SetValue("da", daMediaUrl);
property.SetSourceValue("en", enMediaUrl, true);
property.SetSourceValue("da", daMediaUrl);
var contentType = new PublishedContentType(666, "alias", PublishedItemType.Content, Enumerable.Empty<string>(), new [] { umbracoFilePropertyType }, ContentVariation.Culture);
var publishedContent = new SolidPublishedContent(contentType) {Properties = new[] {property}};
@@ -131,7 +146,7 @@ namespace Umbraco.Tests.Routing
Assert.AreEqual(daMediaUrl, resolvedUrl);
}
private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, object propertyValue, object dataTypeConfiguration)
private static IPublishedContent CreatePublishedContent(string propertyEditorAlias, string propertyValue, object dataTypeConfiguration)
{
var umbracoFilePropertyType = CreatePropertyType(propertyEditorAlias, dataTypeConfiguration, ContentVariation.Nothing);
@@ -147,7 +162,7 @@ namespace Umbraco.Tests.Routing
new SolidPublishedProperty
{
Alias = "umbracoFile",
SolidValue = propertyValue,
SolidSourceValue = propertyValue,
SolidHasValue = true,
PropertyType = umbracoFilePropertyType
}
@@ -83,7 +83,7 @@ namespace Umbraco.Tests.Runtimes
var composerTypes = typeLoader.GetTypes<IComposer>() // all of them
.Where(x => !x.FullName.StartsWith("Umbraco.Tests.")) // exclude test components
.Where(x => x != typeof(WebInitialComposer) && x != typeof(WebFinalComposer)); // exclude web runtime
var composers = new Composers(composition, composerTypes, profilingLogger);
var composers = new Composers(composition, composerTypes, Enumerable.Empty<Attribute>(), profilingLogger);
composers.Compose();
// must registers stuff that WebRuntimeComponent would register otherwise
@@ -272,7 +272,7 @@ namespace Umbraco.Tests.Runtimes
.Where(x => !x.FullName.StartsWith("Umbraco.Tests"));
// single?
//var componentTypes = new[] { typeof(CoreRuntimeComponent) };
var composers = new Composers(composition, composerTypes, profilingLogger);
var composers = new Composers(composition, composerTypes, Enumerable.Empty<Attribute>(), profilingLogger);
// get components to compose themselves
composers.Compose();
@@ -184,7 +184,7 @@ namespace Umbraco.Tests.Services
public void Can_Get_Media_With_Crop_By_Path()
{
var mediaService = ServiceContext.MediaService;
var mediaType = MockedContentTypes.CreateImageMediaType("Image2");
var mediaType = MockedContentTypes.CreateImageMediaTypeWithCrop("Image2");
ServiceContext.MediaTypeService.Save(mediaType);
var media = MockedMedia.CreateMediaImageWithCrop(mediaType, -1);
@@ -420,10 +420,39 @@ namespace Umbraco.Tests.TestHelpers.Entities
var contentCollection = new PropertyTypeCollection(false);
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.UploadField, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = -90 });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = -90 });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId });
mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 });
//ensure that nothing is marked as dirty
mediaType.ResetDirtyProperties(false);
return mediaType;
}
public static MediaType CreateImageMediaTypeWithCrop(string alias = Constants.Conventions.MediaTypes.Image)
{
var mediaType = new MediaType(-1)
{
Alias = alias,
Name = "Image",
Description = "ContentType used for images",
Icon = ".sprTreeDoc3",
Thumbnail = "doc.png",
SortOrder = 1,
CreatorId = 0,
Trashed = false
};
var contentCollection = new PropertyTypeCollection(false);
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.ImageCropper, ValueStorageType.Ntext) { Alias = Constants.Conventions.Media.File, Name = "File", Description = "", Mandatory = false, SortOrder = 1, DataTypeId = 1043 });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Width, Name = "Width", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Height, Name = "Height", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Integer) { Alias = Constants.Conventions.Media.Bytes, Name = "Bytes", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId });
contentCollection.Add(new PropertyType(Constants.PropertyEditors.Aliases.Label, ValueStorageType.Nvarchar) { Alias = Constants.Conventions.Media.Extension, Name = "File Extension", Description = "", Mandatory = false, SortOrder = 2, DataTypeId = Constants.System.DefaultLabelDataTypeId });
mediaType.PropertyGroups.Add(new PropertyGroup(contentCollection) { Name = "Media", SortOrder = 1 });
+8
View File
@@ -1,6 +1,14 @@
'use strict';
module.exports = {
compile: {
build: {
sourcemaps: false
},
dev: {
sourcemaps: true
}
},
sources: {
// less files used by backoffice and preview
+13
View File
@@ -0,0 +1,13 @@
'use strict';
var config = require('./config');
var gulp = require('gulp');
function setDevelopmentMode(cb) {
config.compile.current = config.compile.dev;
return cb();
};
module.exports = { setDevelopmentMode: setDevelopmentMode };
@@ -19,14 +19,22 @@ module.exports = function(files, out) {
console.log("LESS: ", files, " -> ", config.root + config.targets.css + out)
var task = gulp.src(files)
.pipe(sourcemaps.init())
.pipe(less())
.pipe(cleanCss())
.pipe(postcss(processors))
.pipe(rename(out))
.pipe(sourcemaps.write('./maps'))
.pipe(gulp.dest(config.root + config.targets.css));
var task = gulp.src(files);
if(config.compile.current.sourcemaps === true) {
task = task.pipe(sourcemaps.init());
}
task = task.pipe(less());
task = task.pipe(cleanCss());
task = task.pipe(postcss(processors));
task = task.pipe(rename(out));
if(config.compile.current.sourcemaps === true) {
task = task.pipe(sourcemaps.write('./maps'));
}
task = task.pipe(gulp.dest(config.root + config.targets.css));
return task;
+5 -14
View File
@@ -12,6 +12,8 @@
const { src, dest, series, parallel, lastRun } = require('gulp');
const config = require('./gulp/config');
const { setDevelopmentMode } = require('./gulp/modes');
const { dependencies } = require('./gulp/tasks/dependencies');
const { js } = require('./gulp/tasks/js');
const { less } = require('./gulp/tasks/less');
@@ -19,25 +21,14 @@ const { testE2e, testUnit } = require('./gulp/tasks/test');
const { views } = require('./gulp/tasks/views');
const { watchTask } = require('./gulp/tasks/watchTask');
// Load local overwrites, can be used to overwrite paths in your local setup.
var fs = require('fs');
var onlyScripts = require('./gulp/util/scriptFilter');
try {
if (fs.existsSync('./gulp/overwrites/')) {
var overwrites = fs.readdirSync('./gulp/overwrites/').filter(onlyScripts);
overwrites.forEach(function(overwrite) {
require('./gulp/overwrites/' + overwrite);
});
}
} catch (err) {
console.error(err)
}
// set default current compile mode:
config.compile.current = config.compile.build;
// ***********************************************************
// These Exports are the new way of defining Tasks in Gulp 4.x
// ***********************************************************
exports.build = series(parallel(dependencies, js, less, views), testUnit);
exports.dev = series(parallel(dependencies, js, less, views), watchTask);
exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), watchTask);
exports.watch = series(watchTask);
//
exports.runTests = series(js, testUnit);
+1 -1
View File
@@ -38,7 +38,7 @@
"lazyload-js": "1.0.0",
"moment": "2.22.2",
"ng-file-upload": "12.2.13",
"nouislider": "14.0.2",
"nouislider": "14.1.1",
"npm": "6.12.0",
"signalr": "2.4.0",
"spectrum-colorpicker": "1.8.0",
@@ -76,7 +76,6 @@
// Check if it is a new user
const inviteVal = $location.search().invite;
vm.baseTitle = $scope.$root.locationTitle;
//1 = enter password, 2 = password set, 3 = invalid token
if (inviteVal && (inviteVal === "1" || inviteVal === "2")) {
@@ -457,9 +456,7 @@
break;
}
if (title != null) {
$scope.$root.locationTitle = title + " - " + vm.baseTitle;
}
$scope.$emit("$changeTitle", title);
}
}
@@ -224,7 +224,8 @@ Use this directive to construct a header inside the main editor window.
if (editorState.current) {
//to do make work for user create/edit
// to do make it work for user group create/ edit
// to make it work for language edit/create
// to do make it work for language edit/create
// to do make it work for log viewer
scope.isNew = editorState.current.id === 0 ||
editorState.current.id === "0" ||
editorState.current.id === -1 ||
@@ -80,7 +80,8 @@
disabled: "<",
required: "<",
onChange: "&?",
cssClass: "@?"
cssClass: "@?",
iconClass: "@?"
}
};
@@ -0,0 +1,73 @@
(function () {
'use strict';
function MemberNodeInfoDirective($timeout, $location, eventsService, userService, dateHelper, editorService) {
function link(scope, element, attrs, ctrl) {
var evts = [];
//TODO: Infinite editing is not working yet.
scope.allowChangeMemberType = false;
function onInit() {
// make sure dates are formatted to the user's locale
formatDatesToLocal();
}
function formatDatesToLocal() {
// get current backoffice user and format dates
userService.getCurrentUser().then(function (currentUser) {
scope.node.createDateFormatted = dateHelper.getLocalDate(scope.node.createDate, currentUser.locale, 'LLL');
scope.node.updateDateFormatted = dateHelper.getLocalDate(scope.node.updateDate, currentUser.locale, 'LLL');
});
}
scope.openMemberType = function (memberType) {
var editor = {
id: memberType.id,
submit: function (model) {
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.memberTypeEditor(editor);
};
// watch for content updates - reload content when node is saved, published etc.
scope.$watch('node.updateDate', function (newValue, oldValue) {
if (!newValue) { return; }
if (newValue === oldValue) { return; }
// Update the create and update dates
formatDatesToLocal();
});
//ensure to unregister from all events!
scope.$on('$destroy', function () {
for (var e in evts) {
eventsService.unsubscribe(evts[e]);
}
});
onInit();
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/member/umb-member-node-info.html',
scope: {
node: "="
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbMemberNodeInfo', MemberNodeInfoDirective);
})();
@@ -90,7 +90,9 @@ angular.module("umbraco.directives")
css.push("umb-tree-item--deleted");
}
if (actionNode) {
// checking the nodeType to ensure that this node and actionNode is from the same treeAlias
if (actionNode && actionNode.nodeType === node.nodeType) {
if (actionNode.id === node.id && String(node.id) !== "-1") {
css.push("active");
}
@@ -10,7 +10,8 @@
templateUrl: 'views/components/umb-checkmark.html',
scope: {
size: "@?",
checked: "="
checked: "=",
readonly: "@?"
}
};
@@ -554,6 +554,7 @@
property.validation.patternMessage = propertyModel.validation.patternMessage;
property.showOnMemberProfile = propertyModel.showOnMemberProfile;
property.memberCanEdit = propertyModel.memberCanEdit;
property.isSensitiveData = propertyModel.isSensitiveData;
property.isSensitiveValue = propertyModel.isSensitiveValue;
property.allowCultureVariant = propertyModel.allowCultureVariant;
@@ -359,6 +359,7 @@ When building a custom infinite editor view you can use the same components as a
*
* @param {Object} editor rendering options
* @param {Boolean} editor.multiPicker Pick one or multiple items
* @param {Int} editor.startNodeId Set the startnode of the picker (optional)
* @param {Function} editor.submit Callback function when the submit button is clicked. Returns the editor model object
* @param {Function} editor.close Callback function when the close button is clicked.
*
@@ -564,6 +565,7 @@ When building a custom infinite editor view you can use the same components as a
* @description
* Opens a media picker in infinite editing, the submit callback returns an array of selected media items
* @param {Object} editor rendering options
* @param {Int} editor.startNodeId Set the startnode of the picker (optional)
* @param {Boolean} editor.multiPicker Pick one or multiple items
* @param {Boolean} editor.onlyImages Only display files that have an image file-extension
* @param {Boolean} editor.disableFolderSelect Disable folder selection
@@ -608,8 +610,11 @@ When building a custom infinite editor view you can use the same components as a
* @description
* Opens the document type editor in infinite editing, the submit callback returns the alias of the saved document type.
* @param {Object} editor rendering options
* @param {Callback} editor.id Indicates the ID of the document type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the document type editor for creating a new document type.
* @param {Callback} editor.create Set to `true` to open the document type editor for creating a new document type.
* @param {Number} editor.id Indicates the ID of the document type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the document type editor for creating a new document type.
* @param {Boolean} editor.create Set to `true` to open the document type editor for creating a new document type.
* @param {Boolean} editor.noTemplate If `true` and in combination with `create` being set to `true`, the document type editor will not create a corresponding template by default. This is similar to selecting the "Document Type without a template" in the Create dialog.
* @param {Boolean} editor.isElement If `true` and in combination with `create` being set to `true`, the "Is an Element type" option will be selected by default in the document type editor.
* @param {Boolean} editor.allowVaryByCulture If `true` and in combination with `create`, the "Allow varying by culture" option will be selected by default in the document type editor.
* @param {Callback} editor.submit Submits the editor.
* @param {Callback} editor.close Closes the editor.
* @returns {Object} editor object
@@ -636,6 +641,23 @@ When building a custom infinite editor view you can use the same components as a
open(editor);
}
/**
* @ngdoc method
* @name umbraco.services.editorService#memberTypeEditor
* @methodOf umbraco.services.editorService
*
* @description
* Opens the member type editor in infinite editing, the submit callback returns the saved member type
* @param {Object} editor rendering options
* @param {Callback} editor.submit Submits the editor
* @param {Callback} editor.close Closes the editor
* @returns {Object} editor object
*/
function memberTypeEditor(editor) {
editor.view = "views/membertypes/edit.html";
open(editor);
}
/**
* @ngdoc method
* @name umbraco.services.editorService#queryBuilder
@@ -16,7 +16,7 @@ function fileManager($rootScope) {
var mgr = {
/**
* @ngdoc function
* @name umbraco.services.fileManager#addFiles
* @name umbraco.services.fileManager#setFiles
* @methodOf umbraco.services.fileManager
* @function
*
@@ -1,323 +1,325 @@
// This service was based on OpenJS library available in BSD License
// http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php
function keyboardService($window, $timeout) {
var keyboardManagerService = {};
var defaultOpt = {
'type': 'keydown',
'propagate': false,
'inputDisabled': false,
'target': $window.document,
'keyCode': false
};
// Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
var shift_nums = {
"`": "~",
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"0": ")",
"-": "_",
"=": "+",
";": ":",
"'": "\"",
",": "<",
".": ">",
"/": "?",
"\\": "|"
};
// Special Keys - and their codes
var special_keys = {
'esc': 27,
'escape': 27,
'tab': 9,
'space': 32,
'return': 13,
'enter': 13,
'backspace': 8,
'scrolllock': 145,
'scroll_lock': 145,
'scroll': 145,
'capslock': 20,
'caps_lock': 20,
'caps': 20,
'numlock': 144,
'num_lock': 144,
'num': 144,
'pause': 19,
'break': 19,
'insert': 45,
'home': 36,
'delete': 46,
'end': 35,
'pageup': 33,
'page_up': 33,
'pu': 33,
'pagedown': 34,
'page_down': 34,
'pd': 34,
'left': 37,
'up': 38,
'right': 39,
'down': 40,
'f1': 112,
'f2': 113,
'f3': 114,
'f4': 115,
'f5': 116,
'f6': 117,
'f7': 118,
'f8': 119,
'f9': 120,
'f10': 121,
'f11': 122,
'f12': 123
};
var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0;
// The event handler for bound element events
function eventHandler(e) {
e = e || $window.event;
var code, k;
// Find out which key is pressed
if (e.keyCode)
{
code = e.keyCode;
}
else if (e.which) {
code = e.which;
}
var character = String.fromCharCode(code).toLowerCase();
if (code === 188){character = ",";} // If the user presses , when the type is onkeydown
if (code === 190){character = ".";} // If the user presses , when the type is onkeydown
var propagate = true;
//Now we need to determine which shortcut this event is for, we'll do this by iterating over each
//registered shortcut to find the match. We use Find here so that the loop exits as soon
//as we've found the one we're looking for
_.find(_.keys(keyboardManagerService.keyboardEvent), function(key) {
var shortcutLabel = key;
var shortcutVal = keyboardManagerService.keyboardEvent[key];
// Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
var kp = 0;
// Some modifiers key
var modifiers = {
shift: {
wanted: false,
pressed: e.shiftKey ? true : false
},
ctrl: {
wanted: false,
pressed: e.ctrlKey ? true : false
},
alt: {
wanted: false,
pressed: e.altKey ? true : false
},
meta: { //Meta is Mac specific
wanted: false,
pressed: e.metaKey ? true : false
}
};
var keys = shortcutLabel.split("+");
var opt = shortcutVal.opt;
var callback = shortcutVal.callback;
// Foreach keys in label (split on +)
var l = keys.length;
for (var i = 0; i < l; i++) {
var k = keys[i];
switch (k) {
case 'ctrl':
case 'control':
kp++;
modifiers.ctrl.wanted = true;
break;
case 'shift':
case 'alt':
case 'meta':
kp++;
modifiers[k].wanted = true;
break;
}
if (k.length > 1) { // If it is a special key
if (special_keys[k] === code) {
kp++;
}
}
else if (opt['keyCode']) { // If a specific key is set into the config
if (opt['keyCode'] === code) {
kp++;
}
}
else { // The special keys did not match
if (character === k) {
kp++;
}
else {
if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase
character = shift_nums[character];
if (character === k) {
kp++;
}
}
}
}
} //for end
if (kp === keys.length &&
modifiers.ctrl.pressed === modifiers.ctrl.wanted &&
modifiers.shift.pressed === modifiers.shift.wanted &&
modifiers.alt.pressed === modifiers.alt.wanted &&
modifiers.meta.pressed === modifiers.meta.wanted) {
//found the right callback!
// Disable event handler when focus input and textarea
if (opt['inputDisabled']) {
var elt;
if (e.target) {
elt = e.target;
} else if (e.srcElement) {
elt = e.srcElement;
}
if (elt.nodeType === 3) { elt = elt.parentNode; }
if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA') {
//This exits the Find loop
return true;
}
}
$timeout(function () {
callback(e);
}, 1);
if (!opt['propagate']) { // Stop the event
propagate = false;
}
//This exits the Find loop
return true;
}
//we haven't found one so continue looking
return false;
});
// Stop the event if required
if (!propagate) {
// e.cancelBubble is supported by IE - this will kill the bubbling process.
e.cancelBubble = true;
e.returnValue = false;
// e.stopPropagation works in Firefox.
if (e.stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
return false;
}
}
// Store all keyboard combination shortcuts
keyboardManagerService.keyboardEvent = {};
// Add a new keyboard combination shortcut
keyboardManagerService.bind = function (label, callback, opt) {
//replace ctrl key with meta key
if(isMac && label !== "ctrl+space"){
label = label.replace("ctrl","meta");
}
var elt;
// Initialize opt object
opt = angular.extend({}, defaultOpt, opt);
label = label.toLowerCase();
elt = opt.target;
if(typeof opt.target === 'string'){
elt = document.getElementById(opt.target);
}
//Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding
// and raising events for now reason. So here we'll check if the event is already registered for the element
var boundValues = _.values(keyboardManagerService.keyboardEvent);
var found = _.find(boundValues, function (i) {
return i.target === elt && i.event === opt['type'];
});
// Store shortcut
keyboardManagerService.keyboardEvent[label] = {
'callback': callback,
'target': elt,
'opt': opt
};
if (!found) {
//Attach the function with the event
if (elt.addEventListener) {
elt.addEventListener(opt['type'], eventHandler, false);
} else if (elt.attachEvent) {
elt.attachEvent('on' + opt['type'], eventHandler);
} else {
elt['on' + opt['type']] = eventHandler;
}
}
};
// Remove the shortcut - just specify the shortcut and I will remove the binding
keyboardManagerService.unbind = function (label) {
label = label.toLowerCase();
var binding = keyboardManagerService.keyboardEvent[label];
delete(keyboardManagerService.keyboardEvent[label]);
if(!binding){return;}
var type = binding['event'],
elt = binding['target'],
callback = binding['callback'];
if(elt.detachEvent){
elt.detachEvent('on' + type, callback);
}else if(elt.removeEventListener){
elt.removeEventListener(type, callback, false);
}else{
elt['on'+type] = false;
}
};
//
return keyboardManagerService;
}
// This service was based on OpenJS library available in BSD License
// http://www.openjs.com/scripts/events/keyboard_shortcuts/index.php
function keyboardService($window, $timeout) {
var keyboardManagerService = {};
var defaultOpt = {
'type': 'keydown',
'propagate': false,
'inputDisabled': false,
'target': $window.document,
'keyCode': false
};
// Work around for stupid Shift key bug created by using lowercase - as a result the shift+num combination was broken
var shift_nums = {
"`": "~",
"1": "!",
"2": "@",
"3": "#",
"4": "$",
"5": "%",
"6": "^",
"7": "&",
"8": "*",
"9": "(",
"0": ")",
"-": "_",
"=": "+",
";": ":",
"'": "\"",
",": "<",
".": ">",
"/": "?",
"\\": "|"
};
// Special Keys - and their codes
var special_keys = {
'esc': 27,
'escape': 27,
'tab': 9,
'space': 32,
'return': 13,
'enter': 13,
'backspace': 8,
'scrolllock': 145,
'scroll_lock': 145,
'scroll': 145,
'capslock': 20,
'caps_lock': 20,
'caps': 20,
'numlock': 144,
'num_lock': 144,
'num': 144,
'pause': 19,
'break': 19,
'insert': 45,
'home': 36,
'delete': 46,
'end': 35,
'pageup': 33,
'page_up': 33,
'pu': 33,
'pagedown': 34,
'page_down': 34,
'pd': 34,
'left': 37,
'up': 38,
'right': 39,
'down': 40,
'f1': 112,
'f2': 113,
'f3': 114,
'f4': 115,
'f5': 116,
'f6': 117,
'f7': 118,
'f8': 119,
'f9': 120,
'f10': 121,
'f11': 122,
'f12': 123
};
var isMac = navigator.platform.toUpperCase().indexOf('MAC')>=0;
// The event handler for bound element events
function eventHandler(e) {
e = e || $window.event;
var code, k;
// Find out which key is pressed
if (e.keyCode)
{
code = e.keyCode;
}
else if (e.which) {
code = e.which;
}
var character = String.fromCharCode(code).toLowerCase();
if (code === 188){character = ",";} // If the user presses , when the type is onkeydown
if (code === 190){character = ".";} // If the user presses , when the type is onkeydown
var propagate = true;
//Now we need to determine which shortcut this event is for, we'll do this by iterating over each
//registered shortcut to find the match. We use Find here so that the loop exits as soon
//as we've found the one we're looking for
_.find(_.keys(keyboardManagerService.keyboardEvent), function(key) {
var shortcutLabel = key;
var shortcutVal = keyboardManagerService.keyboardEvent[key];
// Key Pressed - counts the number of valid keypresses - if it is same as the number of keys, the shortcut function is invoked
var kp = 0;
// Some modifiers key
var modifiers = {
shift: {
wanted: false,
pressed: e.shiftKey ? true : false
},
ctrl: {
wanted: false,
pressed: e.ctrlKey ? true : false
},
alt: {
wanted: false,
pressed: e.altKey ? true : false
},
meta: { //Meta is Mac specific
wanted: false,
pressed: e.metaKey ? true : false
}
};
var keys = shortcutLabel.split("+");
var opt = shortcutVal.opt;
var callback = shortcutVal.callback;
// Foreach keys in label (split on +)
var l = keys.length;
for (var i = 0; i < l; i++) {
var k = keys[i];
switch (k) {
case 'ctrl':
case 'control':
kp++;
modifiers.ctrl.wanted = true;
break;
case 'shift':
case 'alt':
case 'meta':
kp++;
modifiers[k].wanted = true;
break;
}
if (k.length > 1) { // If it is a special key
if (special_keys[k] === code) {
kp++;
}
}
else if (opt['keyCode']) { // If a specific key is set into the config
if (opt['keyCode'] === code) {
kp++;
}
}
else { // The special keys did not match
if (character === k) {
kp++;
}
else {
if (shift_nums[character] && e.shiftKey) { // Stupid Shift key bug created by using lowercase
character = shift_nums[character];
if (character === k) {
kp++;
}
}
}
}
} //for end
if (kp === keys.length &&
modifiers.ctrl.pressed === modifiers.ctrl.wanted &&
modifiers.shift.pressed === modifiers.shift.wanted &&
modifiers.alt.pressed === modifiers.alt.wanted &&
modifiers.meta.pressed === modifiers.meta.wanted) {
//found the right callback!
// Disable event handler when focus input and textarea
if (opt['inputDisabled']) {
var elt;
if (e.target) {
elt = e.target;
} else if (e.srcElement) {
elt = e.srcElement;
}
if (elt.nodeType === 3) { elt = elt.parentNode; }
if (elt.tagName === 'INPUT' || elt.tagName === 'TEXTAREA' || elt.hasAttribute('disable-hotkeys')) {
//This exits the Find loop
return true;
}
}
$timeout(function () {
callback(e);
}, 1);
if (!opt['propagate']) { // Stop the event
propagate = false;
}
//This exits the Find loop
return true;
}
//we haven't found one so continue looking
return false;
});
// Stop the event if required
if (!propagate) {
// e.cancelBubble is supported by IE - this will kill the bubbling process.
e.cancelBubble = true;
e.returnValue = false;
// e.stopPropagation works in Firefox.
if (e.stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
return false;
}
}
// Store all keyboard combination shortcuts
keyboardManagerService.keyboardEvent = {};
// Add a new keyboard combination shortcut
keyboardManagerService.bind = function (label, callback, opt) {
//replace ctrl key with meta key
if(isMac && label !== "ctrl+space"){
label = label.replace("ctrl","meta");
}
var elt;
// Initialize opt object
opt = angular.extend({}, defaultOpt, opt);
label = label.toLowerCase();
elt = opt.target;
if(typeof opt.target === 'string'){
elt = document.getElementById(opt.target);
}
//Ensure we aren't double binding to the same element + type otherwise we'll end up multi-binding
// and raising events for now reason. So here we'll check if the event is already registered for the element
var boundValues = _.values(keyboardManagerService.keyboardEvent);
var found = _.find(boundValues, function (i) {
return i.target === elt && i.event === opt['type'];
});
// Store shortcut
keyboardManagerService.keyboardEvent[label] = {
'callback': callback,
'target': elt,
'opt': opt
};
if (!found) {
//Attach the function with the event
if (elt.addEventListener) {
elt.addEventListener(opt['type'], eventHandler, false);
} else if (elt.attachEvent) {
elt.attachEvent('on' + opt['type'], eventHandler);
} else {
elt['on' + opt['type']] = eventHandler;
}
}
};
// Remove the shortcut - just specify the shortcut and I will remove the binding
keyboardManagerService.unbind = function (label) {
label = label.toLowerCase();
var binding = keyboardManagerService.keyboardEvent[label];
delete(keyboardManagerService.keyboardEvent[label]);
if(!binding){return;}
var type = binding['event'],
elt = binding['target'],
callback = binding['callback'];
if(elt.detachEvent){
elt.detachEvent('on' + type, callback);
}else if(elt.removeEventListener){
elt.removeEventListener(type, callback, false);
}else{
elt['on'+type] = false;
}
};
//
return keyboardManagerService;
}
angular.module('umbraco.services').factory('keyboardService', ['$window', '$timeout', keyboardService]);
@@ -15,11 +15,11 @@
</div>
<div class="controls" ng-if="installer.current.model.dbType == 0">
<p>Great!, no need to configure anything then, you simply click the <strong>continue</strong> button below to continue to the next step</p>
<p>Great! No need to configure anything, you can simply click the <strong>continue</strong> button below to continue to the next step</p>
</div>
<div ng-if="installer.current.model.dbType < 0">
<legend>What is the exact connectionstring we should use?</legend>
<legend>What is the exact connection string we should use?</legend>
<div class="control-group">
<label class="control-label" for="server">Connection string</label>
<div class="controls">
@@ -64,7 +64,7 @@
<label class="control-label" for="login">Login</label>
<div class="controls">
<input type="text" id="login" name="login"
placeholder="databaseuser"
placeholder="umbraco-database-user"
required ng-model="installer.current.model.login" />
<small class="inline-help">Enter the database user name</small>
</div>
@@ -1,8 +1,8 @@
<div>
<h1>Your permission settings are not ready for umbraco</h1>
<h1>Your permission settings are not ready for Umbraco</h1>
<p>
In order to run umbraco, you'll need to update your permission settings.
Detailed information about the correct file & folder permissions for Umbraco can be found
In order to run Umbraco, you'll need to update your permission settings.
Detailed information about the correct file and folder permissions for Umbraco can be found
<a href="https://our.umbraco.com/documentation/Installation/permissions"><strong>here</strong></a>.
</p>
<p>
@@ -2,11 +2,11 @@
<h1>Major version upgrade from {{installer.current.model.currentVersion}} to {{installer.current.model.newVersion}}</h1>
<h2>There were {{installer.current.model.errors.length}} issues detected</h2>
<p>
The following compatibility issues were found. If you continue all non-compatible property editors will be converted to a Readonly/Label.
The following compatibility issues were found. If you continue, all non-compatible property editors will be converted to a Readonly/Label.
You will be able to change the property editor to a compatible type manually by editing the data type after installation.
</p>
<p>
Otherwise if you choose not to proceed you will need to fix the errors listed below.
Otherwise, if you choose not to proceed, you will need to fix the errors listed below.
Refer to v{{installer.current.model.newVersion}} upgrade instructions for full details.
</p>
@@ -19,4 +19,4 @@
<p>
<button class="btn btn-success" ng-click="forward()">Continue</button>
</p>
</div>
</div>
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
.umb-editors {
.absolute();
overflow: hidden;
overflow: hidden;
.umb-editor {
box-shadow: 0px 0 30px 0 rgba(0,0,0,.3);
@@ -16,7 +16,7 @@
transform: none;
will-change: transform;
transition: transform 400ms ease-in-out;
&.umb-editor--moveRight {
transform: translateX(110%);
}
@@ -28,7 +28,7 @@
will-change: auto;
transition: display 0s 320ms;
}
&--level0 {
transform: none;
}
@@ -43,11 +43,11 @@
.umb-editor--level@{i} {
transform: translateX(@x);
}
.umb-editor--n@{i} {
right:@x;
}
.level-loop(@i - 1);
}
@@ -62,18 +62,18 @@
.umb-editor {
@size: extract(extract(@editorSizes, @iterator), 1);
@value: extract(extract(@editorSizes, @iterator), 2);
&--@{size} {
width: @value;
will-change: transform;
left: auto;
.umb-editor--container {
max-width: @value;
}
}
}
.create-editor-sizes(@iterator + 1);
}
@@ -94,3 +94,14 @@
opacity: 1;
transition: opacity 320ms 20ms, visibility 0s;
}
.umb-editor--trashed-message {
background:@errorBackground;
color:@errorText;
padding:10px;
margin-bottom:20px;
i {
margin-right:5px;
}
}
@@ -66,7 +66,7 @@
flex-shrink: 1;
flex-basis: auto;
position: relative;
padding: 20px;
padding: 30px;
background: @white;
max-height: calc(100vh - 170px);
overflow-y: auto;
@@ -117,7 +117,7 @@
.umb-overlay.umb-overlay-center .umb-overlay-drawer {
border: none;
background: transparent;
padding: 0 20px 20px;
padding: 0 30px 20px;
}
/* ---------- OVERLAY TARGET ---------- */
@@ -8,7 +8,7 @@
.umb-breadcrumbs__ancestor {
display: flex;
min-height: 25px;
align-items: center;
}
.umb-breadcrumbs__action {
@@ -6,7 +6,7 @@
flex-wrap: wrap;
align-items: center;
position: relative;
padding: 0 !important;
padding: 0 0 0 26px !important;
margin: 0;
min-height: 22px;
line-height: 22px;
@@ -21,7 +21,6 @@
}
&__text {
margin: 0 0 0 26px;
position: relative;
top: 1px;
user-select: none;
@@ -42,6 +42,10 @@
.umb-media-grid__item.-selectable {
cursor: pointer;
.tabbing-active &:focus {
outline: 2px solid @inputBorderTabFocus;
}
}
.umb-media-grid__item.-file {
@@ -118,6 +122,7 @@
.umb-media-grid__item-overlay {
display: flex;
width: 100%;
opacity: 0;
position: absolute;
right: 0;
@@ -137,6 +142,10 @@
&:hover {
text-decoration: underline;
}
.tabbing-active &:focus {
opacity: 1;
}
}
.umb-media-grid__info {
@@ -13,9 +13,15 @@
margin-right: 5px;
}
.umb-mini-list-view__breadcrumb {
.flex;
margin-bottom: 10px;
min-height: 25px;
}
.umb-mini-list-view__back {
font-size: 12px;
margin-right: 5px;
font-size: 13px;
margin-right: 5px;
color: @gray-4;
display: flex;
align-items: center;
@@ -67,6 +67,7 @@
height: 100%;
width: 100%;
border-radius: 3px;
border: 0 none;
text-decoration: none !important;
transition: border-color 100ms ease;
background-color: @white;
@@ -193,9 +194,8 @@
color: @black;
box-sizing: border-box;
justify-content: center;
border-top: 1px solid @gray-8;
border-bottom: 1px solid @gray-8;
border-right: 1px solid @gray-8;
border: 1px solid @gray-8;
border-left: 0;
padding: 10px 0;
background: @white;
}
@@ -411,6 +411,7 @@
}
.umb-gallery__thumbnail {
background: transparent;
flex: 0 1 100px;
border: 1px solid @ui-action-discreet-border;
border-radius: 3px;
@@ -232,6 +232,7 @@
}
.umb-mediapicker-multi > div {
width:100%;
.umb-property-editor--limit-width();
}
@@ -880,6 +881,21 @@
.umb-datepicker p {margin-top:10px;}
.umb-datepicker p a{color: @gray-3;}
//
// Link picker
// --------------------------------------------------
.umb-linkpicker {
.umb-linkpicker__url {
width: 50%;
padding-right: 5px;
}
.umb-linkpicker__anchor {
width: 50%;
padding-left: 5px;
}
}
//
// Code mirror - even though this isn't a proprety editor right now, it could be so I'm putting the styles here
// --------------------------------------------------
@@ -510,6 +510,14 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
if (!event) {
return;
}
closeTree();
};
$scope.onOutsideClick = function() {
closeTree();
};
function closeTree() {
if (!appState.getGlobalState("touchDevice")) {
treeActive = false;
$timeout(function () {
@@ -518,7 +526,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
}
}, 300);
}
};
}
$scope.toggleLanguageSelector = function () {
$scope.page.languageSelectorIsOpen = !$scope.page.languageSelectorIsOpen;
@@ -7,6 +7,31 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
.controller("previewController", function ($scope, $window, $location) {
$scope.currentCulture = {iso:'', title:'...', icon:'icon-loading'}
var cultures = [];
$scope.tabbingActive = false;
// There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution.
// For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2
function handleFirstTab(evt) {
if (evt.keyCode === 9) {
$scope.tabbingActive = true;
$scope.$digest();
window.removeEventListener('keydown', handleFirstTab);
window.addEventListener('mousedown', disableTabbingActive);
}
}
function disableTabbingActive(evt) {
$scope.tabbingActive = false;
$scope.$digest();
window.removeEventListener('mousedown', disableTabbingActive);
window.addEventListener("keydown", handleFirstTab);
}
window.addEventListener("keydown", handleFirstTab);
//gets a real query string value
function getParameterByName(name, url) {
if (!url) url = $window.location.href;
@@ -75,15 +100,55 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
$scope.valueAreLoaded = false;
$scope.devices = [
{ name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" },
{ name: "laptop - 1366px", css: "laptop border", icon: "icon-laptop", title: "Laptop" },
{ name: "iPad portrait - 768px", css: "iPad-portrait border", icon: "icon-ipad", title: "Tablet portrait" },
{ name: "iPad landscape - 1024px", css: "iPad-landscape border", icon: "icon-ipad flip", title: "Tablet landscape" },
{ name: "smartphone portrait - 480px", css: "smartphone-portrait border", icon: "icon-iphone", title: "Smartphone portrait" },
{ name: "smartphone landscape - 320px", css: "smartphone-landscape border", icon: "icon-iphone flip", title: "Smartphone landscape" }
{ name: "fullsize", css: "fullsize", icon: "icon-application-window-alt", title: "Browser" },
{ name: "desktop", css: "desktop shadow", icon: "icon-display", title: "Desktop" },
{ name: "laptop - 1366px", css: "laptop shadow", icon: "icon-laptop", title: "Laptop" },
{ name: "iPad portrait - 768px", css: "iPad-portrait shadow", icon: "icon-ipad", title: "Tablet portrait" },
{ name: "iPad landscape - 1024px", css: "iPad-landscape shadow", icon: "icon-ipad flip", title: "Tablet landscape" },
{ name: "smartphone portrait - 480px", css: "smartphone-portrait shadow", icon: "icon-iphone", title: "Smartphone portrait" },
{ name: "smartphone landscape - 320px", css: "smartphone-landscape shadow", icon: "icon-iphone flip", title: "Smartphone landscape" }
];
$scope.previewDevice = $scope.devices[0];
$scope.sizeOpen = false;
$scope.cultureOpen = false;
$scope.toggleSizeOpen = function() {
$scope.sizeOpen = toggleMenu($scope.sizeOpen);
}
$scope.toggleCultureOpen = function() {
$scope.cultureOpen = toggleMenu($scope.cultureOpen);
}
function toggleMenu(isCurrentlyOpen) {
if (isCurrentlyOpen === false) {
closeOthers();
return true;
} else {
return false;
}
}
function closeOthers() {
$scope.sizeOpen = false;
$scope.cultureOpen = false;
}
$scope.windowClickHandler = function() {
closeOthers();
}
function windowBlurHandler() {
closeOthers();
$scope.$digest();
}
var win = angular.element($window);
win.on("blur", windowBlurHandler);
$scope.$on("$destroy", function () {
win.off("blur", handleBlwindowBlurHandlerur );
});
function setPageUrl(){
$scope.pageId = $location.search().id || getParameterByName("id");
@@ -123,6 +188,8 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
$scope.onFrameLoaded = function (iframe) {
$scope.frameLoaded = true;
configureSignalR(iframe);
$scope.currentCultureIso = $location.search().culture || null;
};
/*****************************************************************************/
@@ -136,17 +203,32 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
/*****************************************************************************/
/* Change culture */
/*****************************************************************************/
$scope.changeCulture = function (culture) {
if($location.search().culture !== culture){
$scope.changeCulture = function (iso) {
if($location.search().culture !== iso) {
$scope.frameLoaded = false;
$location.search("culture", culture);
$scope.currentCultureIso = iso;
$location.search("culture", iso);
setPageUrl();
}
};
$scope.isCurrentCulture = function(culture) {
return $location.search().culture === culture;
$scope.registerCulture = function(iso, title, isDefault) {
var cultureObject = {iso: iso, title: title, isDefault: isDefault};
cultures.push(cultureObject);
}
$scope.$watch("currentCultureIso", function(oldIso, newIso) {
// if no culture is selected, we will pick the default one:
if ($scope.currentCultureIso === null) {
$scope.currentCulture = cultures.find(function(culture) {
return culture.isDefault === true;
})
return;
}
$scope.currentCulture = cultures.find(function(culture) {
return culture.iso === $scope.currentCultureIso;
})
});
})
@@ -1,98 +1,99 @@
(function() {
"use strict";
(function () {
"use strict";
function CopyController($scope, localizationService, eventsService, entityHelper) {
function CopyController($scope, localizationService, eventsService, entityHelper) {
var vm = this;
var vm = this;
vm.labels = {};
vm.hideSearch = hideSearch;
vm.selectResult = selectResult;
vm.onSearchResults = onSearchResults;
vm.submit = submit;
vm.close = close;
vm.labels = {};
vm.hideSearch = hideSearch;
vm.selectResult = selectResult;
vm.onSearchResults = onSearchResults;
vm.onToggle = toggleHandler;
vm.submit = submit;
vm.close = close;
var dialogOptions = $scope.model;
var node = dialogOptions.currentNode;
var dialogOptions = $scope.model;
var node = dialogOptions.currentNode;
$scope.model.relateToOriginal = true;
$scope.dialogTreeApi = {};
$scope.model.relateToOriginal = true;
$scope.dialogTreeApi = {};
vm.searchInfo = {
searchFromId: null,
searchFromName: null,
showSearch: false,
results: [],
selectedSearchResults: []
};
vm.searchInfo = {
searchFromId: null,
searchFromName: null,
showSearch: false,
results: [],
selectedSearchResults: []
};
// get entity type based on the section
$scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
// get entity type based on the section
$scope.entityType = entityHelper.getEntityTypeFromSection(dialogOptions.section);
function onInit() {
function onInit() {
var labelKeys = [
"general_copy",
"defaultdialogs_relateToOriginalLabel"
];
var labelKeys = [
"general_copy",
"defaultdialogs_relateToOriginalLabel"
];
localizationService.localizeMany(labelKeys).then(function (data) {
localizationService.localizeMany(labelKeys).then(function (data) {
vm.labels.title = data[0];
vm.labels.relateToOriginal = data[1];
vm.labels.title = data[0];
vm.labels.relateToOriginal = data[1];
setTitle(vm.labels.title);
});
}
setTitle(vm.labels.title);
});
}
function setTitle(value) {
if (!$scope.model.title) {
$scope.model.title = value;
}
}
function setTitle(value) {
if (!$scope.model.title) {
$scope.model.title = value;
}
}
function nodeSelectHandler(args) {
if (args && args.event) {
args.event.preventDefault();
args.event.stopPropagation();
}
function nodeSelectHandler(args) {
if (args && args.event) {
args.event.preventDefault();
args.event.stopPropagation();
}
//eventsService.emit("editors.content.copyController.select", args);
//eventsService.emit("editors.content.copyController.select", args);
if ($scope.model.target) {
//un-select if there's a current one selected
$scope.model.target.selected = false;
}
if ($scope.model.target) {
//un-select if there's a current one selected
$scope.model.target.selected = false;
}
$scope.model.target = args.node;
$scope.model.target.selected = true;
}
$scope.model.target = args.node;
$scope.model.target.selected = true;
}
function nodeExpandedHandler(args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
}
}
function nodeExpandedHandler(args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
}
}
function hideSearch() {
vm.searchInfo.showSearch = false;
vm.searchInfo.searchFromId = null;
vm.searchInfo.searchFromName = null;
vm.searchInfo.results = [];
}
function hideSearch() {
vm.searchInfo.showSearch = false;
vm.searchInfo.searchFromId = null;
vm.searchInfo.searchFromName = null;
vm.searchInfo.results = [];
}
// method to select a search result
function selectResult(evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler({ event: evt, node: result });
}
// method to select a search result
function selectResult(evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler({ event: evt, node: result });
}
//callback when there are search results
function onSearchResults(results) {
vm.searchInfo.results = results;
vm.searchInfo.showSearch = true;
}
//callback when there are search results
function onSearchResults(results) {
vm.searchInfo.results = results;
vm.searchInfo.showSearch = true;
}
$scope.onTreeInit = function () {
$scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
@@ -100,19 +101,19 @@
}
// Mini list view
$scope.selectListViewNode = function (node) {
node.selected = node.selected === true ? false : true;
nodeSelectHandler({ node: node });
};
// Mini list view
$scope.selectListViewNode = function (node) {
node.selected = node.selected === true ? false : true;
nodeSelectHandler({ node: node });
};
$scope.closeMiniListView = function () {
$scope.miniListView = undefined;
};
$scope.closeMiniListView = function () {
$scope.miniListView = undefined;
};
function openMiniListView(node) {
$scope.miniListView = node;
}
function openMiniListView(node) {
$scope.miniListView = node;
}
function submit() {
if ($scope.model && $scope.model.submit) {
@@ -126,9 +127,16 @@
}
}
onInit();
}
function toggleHandler(type) {
// If the relateToOriginal toggle is clicked
if (type === "relate") {
$scope.model.relateToOriginal = !$scope.model.relateToOriginal;
}
}
angular.module("umbraco").controller("Umbraco.Editors.CopyController", CopyController);
onInit();
}
angular.module("umbraco").controller("Umbraco.Editors.CopyController", CopyController);
})();
@@ -2,7 +2,7 @@
<umb-editor-view>
<form novalidate name="dataTypeSettingsForm" val-form-manager>
<form novalidate name="copyNodesForm" class="form-horizontal" val-form-manager>
<umb-editor-header
name="model.title"
@@ -59,11 +59,11 @@
</umb-mini-list-view>
</div>
<div class="umb-control-group -no-border">
<div class="flex">
<umb-checkbox input-id="relateToOriginal" model="model.relateToOriginal" text="{{vm.labels.relateToOriginal}}" />
</div>
</div>
<umb-pane>
<umb-control-group localize="label" label="@defaultdialogs_relateToOriginalLabel">
<umb-toggle label-position="left" checked="model.relateToOriginal" on-click="vm.onToggle('relate')"></umb-toggle>
</umb-control-group>
</umb-pane>
</umb-box-content>
</umb-box>
</umb-editor-container>
@@ -1,4 +1,4 @@
<div ng-controller="Umbraco.Editors.LinkPickerController as vm">
<div ng-controller="Umbraco.Editors.LinkPickerController as vm" class="umb-linkpicker">
<umb-editor-view>
@@ -16,7 +16,7 @@
<div class="flex">
<umb-control-group label="@defaultdialogs_urlLinkPicker">
<umb-control-group label="@defaultdialogs_urlLinkPicker" class="umb-linkpicker__url">
<input type="text"
style="margin-right: 10px;"
localize="placeholder"
@@ -27,7 +27,7 @@
ng-disabled="model.target.id || model.target.udi" />
</umb-control-group>
<umb-control-group label="@defaultdialogs_anchorLinkPicker">
<umb-control-group label="@defaultdialogs_anchorLinkPicker" class="umb-linkpicker__anchor">
<input type="text"
list="anchors"
localize="placeholder"
@@ -1,7 +1,7 @@
//used for the media picker dialog
angular.module("umbraco")
.controller("Umbraco.Editors.MediaPickerController",
function ($scope, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) {
function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) {
var vm = this;
@@ -292,13 +292,20 @@ angular.module("umbraco")
function onUploadComplete(files) {
gotoFolder($scope.currentFolder).then(function () {
if (files.length === 1 && $scope.model.selection.length === 0) {
var image = $scope.images[$scope.images.length - 1];
$scope.target = image;
$scope.target.url = mediaHelper.resolveFile(image);
selectMedia(image);
}
})
$timeout(function () {
if ($scope.multiPicker) {
var images = _.rest($scope.images, $scope.images.length - files.length);
_.each(images, function(image) {
selectMedia(image);
});
} else {
var image = $scope.images[$scope.images.length - 1];
$scope.target = image;
$scope.target.url = mediaHelper.resolveFile(image);
selectMedia(image);
}
});
});
}
function onFilesQueue() {
@@ -1,4 +1,4 @@
<div id="leftcolumn" ng-controller="Umbraco.NavigationController" ng-mouseleave="leaveTree($event)" ng-mouseenter="enterTree($event)">
<div id="leftcolumn" ng-controller="Umbraco.NavigationController" ng-mouseleave="leaveTree($event)" ng-mouseenter="enterTree($event)" on-outside-click="onOutsideClick()">
<!-- navigation container -->
<div id="navigation" ng-show="showNavigation" class="fill umb-modalcolumn" ng-animate="'slide'" nav-resize
@@ -8,10 +8,13 @@
type="text"
ng-model="vm.searchQuery"
ng-model-options="{ debounce: 200 }"
ng-change="vm.search(vm.searchQuery)"
placeholder="Search..."
ng-change="vm.search(vm.searchQuery)"
localize="placeholder"
placeholder="@placeholders_search"
focus-when="{{vm.searchHasFocus}}" />
<button ng-show="vm.searchQuery.length > 0" tabindex="-1" class="umb-search-input-clear umb-animated" ng-click="vm.clearSearch()">Clear</button>
<button ng-show="vm.searchQuery.length > 0" tabindex="-1" class="umb-search-input-clear umb-animated" ng-click="vm.clearSearch()">
<localize key="general_clear">Clear</localize>
</button>
</div>
<div class="umb-search-results">
@@ -27,6 +27,12 @@
</ng-form>
<umb-editor-container ng-if="vm.editor.content.apps.length > 0">
<!-- Deleted Context Message Bar (Displayed when viewing node in recycle bin) -->
<div ng-show="vm.content.trashed" class="umb-editor--trashed-message">
<i class="icon icon-trash"></i> <localize key="content_nodeIsInTrash">This item is in the Recycle Bin</localize>
</div>
<div class="umb-editor-sub-views">
<div ng-repeat="app in vm.editor.content.apps track by app.alias">
<umb-editor-sub-view model="app" content="vm.content" />
@@ -16,5 +16,7 @@
</span>
</span>
<span class="umb-form-check__text">{{vm.text}}</span>
<i ng-if="vm.iconClass.length" class="{{vm.iconClass}}" aria-hidden="true"></i>
<span ng-if="vm.text.length" class="umb-form-check__text">{{vm.text}}</span>
</label>
@@ -0,0 +1,50 @@
<div class="umb-package-details">
<div class="umb-package-details__main-content">
<umb-box data-element="node-info-membership" ng-repeat="group in node.tabs| filter: {properties:{view:'readonlyvalue'}} track by group.label">
<div class="umb-group-panel__header">
<div>{{ group.label }}</div>
</div>
<div class="umb-group-panel__content">
<umb-property data-element="property-{{group.alias}}" ng-repeat="property in group.properties | filter: {view:'readonlyvalue'} track by property.alias" property="property">
<umb-property-editor model="property"></umb-property-editor>
</umb-property>
</div>
</umb-box>
</div>
<div class="umb-package-details__sidebar">
<umb-box data-element="node-info-general">
<umb-box-header title-key="general_general"></umb-box-header>
<umb-box-content class="block-form">
<umb-control-group ng-if="node.id !== 0" data-element="node-info-create-date" label="@content_createDate">
{{node.createDateFormatted}} by {{ node.owner.name }}
</umb-control-group>
<umb-control-group ng-if="node.id !== 0" data-element="node-info-update-date" label="@content_updateDate">
{{node.updateDateFormatted}}
</umb-control-group>
<umb-control-group data-element="node-info-document-type" label="@content_membertype">
<umb-node-preview style="max-width: 100%; margin-bottom: 0px;"
icon="node.icon"
name="node.contentTypeName"
alias="node.contentTypeAlias"
allow-open="allowChangeMemberType"
on-open="openMemberType(node)">
</umb-node-preview>
</umb-control-group>
<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>
</umb-box-content>
</umb-box>
</div>
</div>
@@ -1 +1 @@
<i class="icon-check umb-checkmark umb-checkmark--{{size}}" ng-class="{'umb-checkmark--checked': checked}"></i>
<i class="icon-check umb-checkmark umb-checkmark--{{size}}" ng-class="{'umb-checkmark--checked': checked, 'cursor-auto': readonly}"></i>
@@ -4,10 +4,10 @@
<!--<i ng-show="item.selected" class="icon-check umb-media-grid__checkmark"></i>-->
<a ng-if="allowOnClickEdit === 'true'" ng-click="clickEdit(item, $event)" ng-href="" class="icon-edit umb-media-grid__edit"></a>
<div data-element="media-grid-item-edit" class="umb-media-grid__item-overlay" ng-class="{'-locked': item.selected || !item.file || !item.thumbnail}" ng-click="clickItemName(item, $event, $index)">
<button data-element="media-grid-item-edit" class="umb-media-grid__item-overlay btn-reset" ng-class="{'-locked': item.selected || !item.file || !item.thumbnail}" ng-click="clickItemName(item, $event, $index)" type="button">
<i ng-if="onDetailsHover" class="icon-info umb-media-grid__info" ng-mouseover="hoverItemDetails(item, $event, true)" ng-mouseleave="hoverItemDetails(item, $event, false)"></i>
<div class="umb-media-grid__item-name">{{item.name}}</div>
</div>
</button>
<!-- Check backgrund -->
<div class="umb-media-grid__image-background" ng-if="item.thumbnail || item.extension === 'svg'"></div>
@@ -9,7 +9,7 @@
<h4 class="umb-mini-list-view__title-text">{{ miniListView.node.name }}</h4>
</div>
<div class="flex" style="margin-bottom: 10px;">
<div class="umb-mini-list-view__breadcrumb">
<a ng-if="showBackButton()" href="" class="umb-mini-list-view__back" ng-click="exitMiniListView()">
<i class="icon-arrow-left umb-mini-list-view__back-icon"></i>
@@ -24,7 +24,7 @@
<!-- we need to show the old pass field when the provider cannot retrieve the password -->
<umb-control-group alias="oldPassword" label="@user_oldPassword" ng-if="vm.showOldPass()" required="true">
<input type="password" name="oldPassword" ng-model="vm.passwordValues.oldPassword"
<input type="password" name="oldPassword" id="oldPassword" ng-model="vm.passwordValues.oldPassword"
class="input-block-level umb-textstring textstring"
required
val-server-field="oldPassword"
@@ -36,7 +36,7 @@
</umb-control-group>
<umb-control-group alias="password" label="@user_newPassword" ng-if="!vm.showReset" required="true">
<input type="password" name="password" ng-model="vm.passwordValues.newPassword"
<input type="password" name="password" id="password" ng-model="vm.passwordValues.newPassword"
class="input-block-level umb-textstring textstring"
required
val-server-field="password"
@@ -50,7 +50,7 @@
</umb-control-group>
<umb-control-group alias="confirmPassword" label="@user_confirmNewPassword" ng-if="!vm.showReset" required="true">
<input type="password" name="confirmPassword" ng-model="vm.passwordValues.confirm"
<input type="password" name="confirmPassword" id="confirmPassword" ng-model="vm.passwordValues.confirm"
class="input-block-level umb-textstring textstring"
val-compare="password"
no-dirty-check />
@@ -45,7 +45,7 @@
<tbody>
<tr ng-repeat="domain in vm.domains">
<td>
<input style="width: 100%; margin-bottom: 0;" type="text" ng-model="domain.name" name="domain_name_{{$index}}" required />
<input style="width: 100%; margin-bottom: 0;" type="text" ng-model="domain.name" name="domain_name_{{$index}}" required umb-auto-focus />
<span ng-if="vm.domainForm.$submitted" ng-messages="vm.domainForm['domain_name_' + $index].$error">
<span class="help-inline" ng-message="required"><localize key="validation_invalidEmpty"></localize></span>
</span>
@@ -5,7 +5,7 @@
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong>?
</p>
<umb-confirm on-confirm="performDelete" on-cancel="cancel">
<umb-confirm on-confirm="performDelete" on-cancel="cancel" confirm-button-style="danger">
</umb-confirm>
</div>
@@ -1,10 +0,0 @@
<h3>Start here</h3>
<h4>Get started with Members right now</h4>
<p>Use the tool below to search for an existing member.</p>
<h4>More about members</h4>
<ul>
<li>Learn about how to protect pages of your site from <a class="btn-link -underline" href="https://our.umbraco.com/wiki/reference/umbraco-client/context-menus/public-access" target="_blank">this Wiki entry</a></li>
</ul>
@@ -27,7 +27,7 @@
</div>
<div class="mb4">
<div class="flex items-center">
<umb-toggle checked="vm.alwaysOn" id="profilerAlwaysOn" on-click="vm.toggle()"></umb-toggle>
<umb-toggle checked="vm.alwaysOn" input-id="profilerAlwaysOn" on-click="vm.toggle()"></umb-toggle>
<label for="profilerAlwaysOn" class="mb0 ml2">
<localize key="profiling_activateByDefault">Activate the profiler by default</localize>
</label>
@@ -10,7 +10,11 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
var evts = [];
var vm = this;
vm.header = {};
vm.header.editorfor = "visuallyHiddenTexts_newDataType";
vm.header.setPageTitle = true;
//setup scope vars
vm.page = {};
vm.page.loading = false;
@@ -8,7 +8,9 @@
hide-icon="true"
hide-description="true"
hide-alias="true"
navigation="vm.page.navigation">
navigation="vm.page.navigation"
editorfor="vm.header.editorfor"
setpagetitle="vm.header.setPageTitle">
</umb-editor-header>
<umb-editor-container class="form-horizontal">
@@ -11,7 +11,7 @@
<div ng-switch-when="container">
<umb-confirm
on-confirm="performContainerDelete"
on-cancel="cancel">
on-cancel="cancel" confirm-button-style="danger">
</umb-confirm>
</div>
@@ -31,7 +31,7 @@
<umb-checkbox model="confirmed" text="{{labels.deleteConfirm}}">
</umb-checkbox>
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel">
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel" confirm-button-style="danger">
</umb-confirm>
</div>
</ng-switch>
@@ -64,7 +64,8 @@
if (infiniteMode) {
documentTypeId = $scope.model.id;
create = $scope.model.create;
noTemplate = $scope.model.notemplate;
if (create && !documentTypeId) documentTypeId = -1;
noTemplate = $scope.model.notemplate || $scope.model.noTemplate;
isElement = $scope.model.isElement;
allowVaryByCulture = $scope.model.allowVaryByCulture;
vm.submitButtonKey = "buttons_saveAndClose";
@@ -9,7 +9,7 @@
<form name="importDoctype">
<!-- Select files -->
<button class="btn"
<button class="btn btn-action"
name="file"
ngf-select
ng-model="filesHolder"
@@ -159,11 +159,17 @@
</tbody>
</table>
<div class="btn-group">
<a class="btn btn-info dropdown-toggle" data-toggle="dropdown" ng-href="" ng-click="log.searchDropdownOpen = !log.searchDropdownOpen">
<i class="icon-search"></i> <localize key="general_search">Search</localize>
<span class="caret"></span>
</a>
<div class="btn-group" deep-blur="log.searchDropdownOpen = !log.searchDropdownOpen">
<button
type="button"
class="btn btn-info dropdown-toggle"
aria-haspopup="true"
aria-expanded="{{log.searchDropdownOpen === undefined ? false : log.searchDropdownOpen}}"
ng-click="log.searchDropdownOpen = !log.searchDropdownOpen">
<i class="icon-search" aria-hidden="true"></i>
<localize key="general_search">Search</localize>
<span class="caret" aria-hidden="true"></span>
</button>
<umb-dropdown ng-if="log.searchDropdownOpen" on-close="log.searchDropdownOpen = false">
<umb-dropdown-item>
@@ -5,6 +5,6 @@
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{vm.name}}</strong>?
</p>
<umb-confirm on-confirm="vm.performDelete" on-cancel="vm.cancel"></umb-confirm>
<umb-confirm on-confirm="vm.performDelete" on-cancel="vm.cancel" confirm-button-style="danger"></umb-confirm>
</div>
</div>
@@ -6,7 +6,8 @@
alias="vm.macro.alias"
hide-description="true"
hide-icon="true"
navigation="vm.page.navigation">
navigation="vm.page.navigation"editorfor="vm.header.editorfor"
setpagetitle="vm.header.setPageTitle">
</umb-editor-header>
<umb-editor-container class="form-horizontal">
@@ -11,7 +11,10 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta
var vm = this;
vm.promises = {};
vm.header = {};
vm.header.editorfor = "general_macro";
vm.header.setPageTitle = true;
vm.page = {};
vm.page.loading = false;
vm.page.saveButtonState = "init";
@@ -10,13 +10,13 @@
<div class="umb-property-editor form-horizontal">
<div ui-sortable="sortableOptions" ng-model="model.macro.parameters">
<div class="control-group umb-prevalues-multivalues__listitem" ng-repeat="parameter in model.macro.parameters track by $id(parameter)">
<i class="icon icon-navigation handle"></i>
<i class="icon icon-navigation handle" aria-hidden="true"></i>
<div class="umb-prevalues-multivalues__left">
<span>{{parameter.label}}</span>&nbsp;<span class="muted">({{parameter.key}})</span>:&nbsp;<small>{{parameter.editor}}</small>
</div>
<div class="umb-prevalues-multivalues__right">
<a class="umb-node-preview__action" ng-click="vm.edit(parameter, $event)"><localize key="general_edit">Edit</localize></a>
<a class="umb-node-preview__action umb-node-preview__action--red" ng-click="vm.remove(parameter, $event)"><localize key="general_remove">Remove</localize></a>
<button type="button" class="umb-node-preview__action" ng-click="vm.edit(parameter, $event)"><localize key="general_edit">Edit</localize></button>
<button type="button" class="umb-node-preview__action umb-node-preview__action--red" ng-click="vm.remove(parameter, $event)"><localize key="general_remove">Remove</localize></button>
</div>
</div>
</div>
@@ -27,7 +27,7 @@
<umb-checkbox model="confirmed" text="{{labels.deleteConfirm}}">
</umb-checkbox>
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel">
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel" confirm-button-style="danger">
</umb-confirm>
</div>
</ng-switch>
@@ -0,0 +1,11 @@
(function () {
"use strict";
function MemberAppContentController($scope) {
var vm = this;
}
angular.module("umbraco").controller("Umbraco.Editors.Member.Apps.ContentController", MemberAppContentController);
})();
@@ -0,0 +1,17 @@
<div class="form-horizontal" ng-controller="Umbraco.Editors.Member.Apps.ContentController as vm">
<div class="umb-group-panel" ng-repeat="group in content.tabs track by group.label">
<div class="umb-group-panel__header">
<div>{{ group.label }}</div>
</div>
<div class="umb-group-panel__content">
<umb-property data-element="property-{{group.alias}}" ng-repeat="property in group.properties | filter: {view:'!readonlyvalue'} | filter: {alias:'!_umb_id'} | filter: {alias:'!_umb_doctype'} track by property.alias" property="property">
<umb-property-editor model="property"></umb-property-editor>
</umb-property>
</div>
</div>
</div>
@@ -0,0 +1,4 @@
<umb-member-node-info
ng-if="content"
node="content">
</umb-member-node-info>
@@ -14,29 +14,22 @@
hide-icon="true"
hide-description="true"
hide-alias="true"
navigation="content.apps"
on-select-navigation-item="appChanged(item)"
show-back-button="showBack()"
on-back="onBack()"
editorfor="header.editorfor"
setpagetitle="header.setPageTitle">
</umb-editor-header>
<umb-editor-container class="form-horizontal">
<div class="umb-group-panel" ng-repeat="group in content.tabs track by group.label">
<div class="umb-group-panel__header">
<div>{{ group.label }}</div>
<umb-editor-container>
<div class="umb-editor-sub-views" ng-if="!page.loading">
<div id="sub-view-{{$index}}" ng-repeat="app in content.apps track by app.alias">
<umb-editor-sub-view model="app" content="content" />
</div>
<div class="umb-group-panel__content">
<umb-property data-element="property-{{group.alias}}" ng-repeat="property in group.properties track by property.alias" property="property">
<umb-property-editor model="property"></umb-property-editor>
</umb-property>
</div>
</div>
</umb-editor-container>
</umb-editor-container>
<umb-editor-footer>
@@ -45,9 +45,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
$scope.content = data;
setHeaderNameState($scope.content);
editorState.set($scope.content);
init();
$scope.page.loading = false;
@@ -59,9 +57,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
.then(function (data) {
$scope.content = data;
setHeaderNameState($scope.content);
editorState.set($scope.content);
init();
$scope.page.loading = false;
@@ -90,9 +86,7 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
$scope.content = data;
setHeaderNameState($scope.content);
editorState.set($scope.content);
init();
if (!infiniteMode) {
var path = buildTreePath(data);
@@ -121,11 +115,48 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
}
function setHeaderNameState(content) {
function init() {
var content = $scope.content;
// we need to check wether an app is present in the current data, if not we will present the default app.
var isAppPresent = false;
// on first init, we dont have any apps. but if we are re-initializing, we do, but ...
if ($scope.app) {
// lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.)
_.forEach(content.apps, function (app) {
if (app === $scope.app) {
isAppPresent = true;
}
});
// if we did reload our DocType, but still have the same app we will try to find it by the alias.
if (isAppPresent === false) {
_.forEach(content.apps, function (app) {
if (app.alias === $scope.app.alias) {
isAppPresent = true;
app.active = true;
$scope.appChanged(app);
}
});
}
}
// if we still dont have a app, lets show the first one:
if (isAppPresent === false) {
content.apps[0].active = true;
$scope.appChanged(content.apps[0]);
}
if (content.membershipScenario === 0) {
$scope.page.nameLocked = true;
}
editorState.set($scope.content);
if(content.membershipScenario === 0) {
$scope.page.nameLocked = true;
}
}
/** Just shows a simple notification that there are client side validation issues to be fixed */
@@ -194,6 +225,15 @@ function MemberEditController($scope, $routeParams, $location, appState, memberR
};
$scope.appChanged = function (app) {
$scope.app = app;
// setup infinite mode
if (infiniteMode) {
$scope.page.submitButtonLabelKey = "buttons_saveAndClose";
}
}
$scope.showBack = function () {
return !!listName;
}
@@ -5,7 +5,7 @@
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong>?
</p>
<umb-confirm on-confirm="performDelete" on-cancel="cancel">
<umb-confirm on-confirm="performDelete" on-cancel="cancel" confirm-button-style="danger">
</umb-confirm>
</div>
@@ -16,7 +16,7 @@
<umb-checkbox model="confirmed" text="{{labels.deleteConfirm}}">
</umb-checkbox>
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel">
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel" confirm-button-style="danger">
</umb-confirm>
@@ -7,19 +7,31 @@
<div class="umb-packages-section">
<div class="umb-packages-search">
<input class="-full-width-input" type="text" name="query" localize="placeholder" placeholder="@packager_packageSearch" umb-auto-focus ng-model="vm.searchQuery" ng-change="vm.search()" no-dirty-check>
<label for="package-query" class="sr-only">
<localize key="packager_packageSearch">Search for packages</localize>
</label>
<input
class="-full-width-input"
type="text"
name="query"
id="package-query"
localize="placeholder"
placeholder="@packager_packageSearch"
umb-auto-focus ng-model="vm.searchQuery"
ng-change="vm.search()"
no-dirty-check>
</div>
</div>
<div class="umb-packages-section" ng-if="vm.searchQuery == ''">
<div class="umb-packages-categories">
<a href=""
<button type="button"
class="umb-packages-category"
ng-click="vm.selectCategory(category, vm.categories)"
ng-repeat="category in vm.categories"
ng-class="{'-active': category.active, '-first': $first, '-last': $last}">
<div>{{category.name}}</div>
</a>
{{category.name}}
</button>
</div>
</div>
@@ -30,7 +42,7 @@
<div class="umb-packages">
<div class="umb-package" ng-repeat="package in vm.popular">
<a class="umb-package-link" href="" ng-click="vm.showPackageDetails(package)">
<button type="button" class="umb-package-link" ng-click="vm.showPackageDetails(package)">
<div class="umb-package-icon">
<img ng-src="{{package.icon}}" alt="" />
@@ -42,15 +54,17 @@
<div class="umb-package-numbers">
<small class="umb-package-downloads">
<i class="icon-download-alt"></i> <strong>{{package.downloads}}</strong>
<i class="icon-download-alt" aria-hidden="true"></i>
<strong>{{package.downloads}}</strong>
</small>
<small class="umb-package-likes">
<i class="icon-hearts"></i> <strong>{{package.likes}}</strong>
<i class="icon-hearts" aria-hidden="true"></i>
<strong>{{package.likes}}</strong>
</small>
</div>
</div>
</a>
</button>
</div> <!-- end package -->
</div> <!-- end packages -->
@@ -64,7 +78,7 @@
<div class="umb-packages">
<div class="umb-package" ng-repeat="package in vm.packages">
<a class="umb-package-link" href="" ng-click="vm.showPackageDetails(package)">
<button type="button" class="umb-package-link" ng-click="vm.showPackageDetails(package)">
<div class="umb-package-icon">
<img ng-src="{{package.icon}}" alt="" />
@@ -76,15 +90,17 @@
<div class="umb-package-numbers">
<small class="umb-package-downloads">
<i class="icon-download-alt"></i> <strong>{{ package.downloads }}</strong>
<i class="icon-download-alt" aria-hidden="true"></i>
<strong>{{ package.downloads }}</strong>
</small>
<small class="umb-package-likes">
<i class="icon-hearts"></i> <strong>{{ package.likes }}</strong>
<i class="icon-hearts" aria-hidden="true"></i>
<strong>{{ package.likes }}</strong>
</small>
</div>
</div>
</a>
</button>
</div> <!-- end package -->
</div> <!-- end packages -->
@@ -140,9 +156,9 @@
<div class="umb-package-details__description" ng-bind-html="vm.package.description"></div>
<div class="umb-gallery">
<div class="umb-gallery__thumbnails">
<a class="umb-gallery__thumbnail" ng-repeat="image in vm.package.images" ng-click="vm.openLightbox($index, vm.package.images)">
<img ng-src="{{ image.thumbnail }}" />
</a>
<button type="button" class="umb-gallery__thumbnail" ng-repeat="image in vm.package.images" ng-click="vm.openLightbox($index, vm.package.images)">
<img ng-src="{{ image.thumbnail }}" />
</button>
</div>
</div>
</umb-box-content>
@@ -274,7 +290,7 @@
<div>
<div class="umb-package-details__information-item" ng-repeat="externalSource in vm.package.externalSources">
<a class="umb-package-details__link" target="_blank" href="{{ externalSource.url }}">
<i class="icon-out"></i>
<i class="icon-out" aria-hidden="true"></i>
{{ externalSource.name }}
</a>
</div>
@@ -304,7 +320,7 @@
<form novalidate name="localPackageForm" class="w-100">
<div class="umb-package-icon">
<i ng-if="!vm.localPackage.icon" class="icon-box"></i>
<i ng-if="!vm.localPackage.icon" class="icon-box" aria-hidden="true"></i>
<img ng-if="vm.localPackage.icon" ng-src="{{vm.localPackage.icon}}" alt="" />
</div>
@@ -5,7 +5,7 @@
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong>?
</p>
<umb-confirm on-confirm="performDelete" on-cancel="cancel">
<umb-confirm on-confirm="performDelete" on-cancel="cancel" confirm-button-style="danger">
</umb-confirm>
</div>
@@ -12,7 +12,7 @@
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong>?
</p>
<umb-confirm on-confirm="performDelete" on-cancel="cancel">
<umb-confirm on-confirm="performDelete" on-cancel="cancel" confirm-button-style="danger">
</umb-confirm>
</div>
@@ -21,7 +21,7 @@
</div>
</div>
<div class="umb-prevalues-multivalues__right">
<a class="umb-node-preview__action umb-node-preview__action--red" ng-click="remove(item, $event)"><localize key="general_remove">Remove</localize></a>
<button type="button" class="umb-node-preview__action umb-node-preview__action--red" ng-click="remove(item, $event)"><localize key="general_remove">Remove</localize></button>
</div>
</div>
</div>
@@ -53,7 +53,7 @@
<div class="umb-cropsizes__controls">
<button class="btn btn-info add" ng-hide="editMode" ng-click="add($event)"><localize key="general_add">Add</localize></button>
<button class="btn btn-info add" ng-show="editMode" ng-click="add($event)"><localize key="general_update">Update</localize></button>
<a href class="btn btn-link" ng-show="editMode" ng-click="cancel($event)"><localize key="general_cancel">Cancel</localize></a>
<button type="button" class="btn btn-link" ng-show="editMode" ng-click="cancel($event)"><localize key="general_cancel">Cancel</localize></button>
</div>
</div>
@@ -65,8 +65,8 @@
<p><span>{{item.alias}}</span> <small>({{item.width}}px &times; {{item.height}}px)</small></p>
</div>
<div class="umb-prevalues-multivalues__right">
<a href="#" prevent-default class="umb-node-preview__action" ng-click="edit(item, $event)"><localize key="general_edit">Edit</localize></a>
<a href="#" prevent-default class="umb-node-preview__action umb-node-preview__action--red" ng-click="remove(item, $event)"><localize key="general_remove">Remove</localize></a>
<button type="button" class="umb-node-preview__action" ng-click="edit(item, $event)"><localize key="general_edit">Edit</localize></button>
<button type="button" class="umb-node-preview__action umb-node-preview__action--red" ng-click="remove(item, $event)"><localize key="general_remove">Remove</localize></button>
</div>
</div>
</div>
@@ -12,7 +12,7 @@
<span class="help-inline" ng-bind="errorMsg"</span>
</div>
<div class="control-group">
<table ng-show="model.value.length > 0">
<table class="table" ng-show="model.value.length > 0">
<thead>
<tr>
<td></td>
@@ -31,7 +31,7 @@
<div class="list-view-layout__name flex-column content-start">
<span class="list-view-layout__name-text" ng-if="!val.isSystem" ng-bind="val.alias"></span>
<span class="list-view-layout__name-text" ng-if="val.isSystem == 1" ng-bind="val.alias"></span>
<span class="list-view-layout__system" ng-show="val.isSystem == 1">(system field)</span>
<span class="list-view-layout__system" ng-show="val.isSystem == 1">(system field)</span>
</div>
</td>
<td>
@@ -53,10 +53,6 @@
});
}
$scope.canAdd = function () {
return !$scope.model.docTypes || !$scope.model.value || $scope.model.value.length < $scope.model.docTypes.length;
}
$scope.remove = function (index) {
$scope.model.value.splice(index, 1);
}
@@ -112,6 +108,7 @@
});
});
}
$scope.canAdd = function () {
return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) {
return !_.find($scope.model.value, function (c) {
@@ -120,7 +117,6 @@
});
}
$scope.openElemTypeModal = function ($event, config) {
//we have to add the alias to the objects (they are stored as ncAlias)
@@ -3,6 +3,6 @@
<div class="umb-rte-editor-con">
<input type="text" id="{{model.alias}}" ng-focus="focus()" style="position:absolute;top:0;width:0;height:0;" />
<div id="{{textAreaHtmlId}}" class="umb-rte-editor" ng-style="{ width: containerWidth, height: containerHeight, overflow: containerOverflow}"></div>
<div disable-hotkeys id="{{textAreaHtmlId}}" class="umb-rte-editor" ng-style="{ width: containerWidth, height: containerHeight, overflow: containerOverflow}"></div>
</div>
</div>
@@ -2,16 +2,12 @@
<umb-control-group label="Toolbar" description="Pick the toolbar options that should be available when editing">
<div ng-repeat="cmd in tinyMceConfig.commands">
<label>
<umb-checkbox model="cmd.selected"
on-change="selectCommand(cmd)"
class="mce-cmd" />
<!--<img ng-src="{{cmd.icon}}" />-->
<i class="mce-ico" ng-class="(cmd.isCustom ? ' mce-i-custom ' : ' mce-i-') + cmd.fontIcon"></i>
{{cmd.name}}
</label>
<umb-checkbox
model="cmd.selected"
on-change="selectCommand(cmd)"
icon-class="mce-ico {{(cmd.isCustom ? ' mce-i-custom ' : ' mce-i-') + cmd.fontIcon}}"
text="{{cmd.name}}"
class="mce-cmd" />
</div>
</umb-control-group>
@@ -15,7 +15,22 @@ function textAreaController($scope, validationMessageService) {
if ($scope.model.config && $scope.model.config.maxChars) {
$scope.model.maxlength = true;
}
$scope.$on("formSubmitting", function() {
if ($scope.isLengthValid()) {
$scope.textareaFieldForm.textarea.$setValidity("maxChars", true);
} else {
$scope.textareaFieldForm.textarea.$setValidity("maxChars", false);
}
});
$scope.isLengthValid = function() {
if (!$scope.model.maxlength) {
return true;
}
return $scope.model.config.maxChars >= $scope.model.count;
}
$scope.model.change = function () {
if ($scope.model.value) {
$scope.model.count = $scope.model.value.length;
@@ -10,7 +10,7 @@
<div class="help" ng-if="model.maxlength === true && model.count >= (model.config.maxChars*.8) && model.count <= model.config.maxChars">
<localize key="textbox_characters_left" tokens="[model.config.maxChars - model.count]" watch-tokens="true">%0% characters left.</localize>
</div>
<div class="help text-error" ng-if="model.maxlength === true && model.count > model.config.maxChars">
<div class="help text-error" ng-if="!isLengthValid()">
<localize key="textbox_characters_exceed" tokens="[model.config.maxChars, model.count - model.config.maxChars]" watch-tokens="true">Maximum %0% characters, <strong>%1%</strong> too many.</localize>
</div>
@@ -11,7 +11,19 @@ function textboxController($scope, validationMessageService) {
// if no max is specified in the config
$scope.model.config.maxChars = 500;
}
$scope.$on("formSubmitting", function() {
if ($scope.isLengthValid()) {
$scope.textboxFieldForm.textbox.$setValidity("maxChars", true);
} else {
$scope.textboxFieldForm.textbox.$setValidity("maxChars", false);
}
});
$scope.isLengthValid = function() {
return $scope.model.config.maxChars >= $scope.model.count;
}
$scope.model.change = function () {
if ($scope.model.value) {
$scope.model.count = $scope.model.value.length;
@@ -19,7 +19,7 @@
<p class="sr-only" tabindex="0">{{model.label}} <localize key="textbox_characters_left" tokens="[model.config.maxChars - model.count]" watch-tokens="true">%0% characters left.</localize></p>
<p aria-hidden="True"><localize key="textbox_characters_left" tokens="[model.config.maxChars - model.count]" watch-tokens="true">%0% characters left.</localize></p>
</div>
<div class="help text-error" ng-if="model.count > model.config.maxChars">
<div class="help text-error" ng-if="!isLengthValid()">
<p class="sr-only" tabindex="0">{{model.label}} <localize key="textbox_characters_exceed" tokens="[model.config.maxChars, model.count - model.config.maxChars]" watch-tokens="true">Maximum %0% characters, <strong>%1%</strong> too many.</localize></p>
<p aria-hidden="true"><localize key="textbox_characters_exceed" tokens="[model.config.maxChars, model.count - model.config.maxChars]" watch-tokens="true">Maximum %0% characters, <strong>%1%</strong> too many.</localize></p>
</div>
@@ -5,7 +5,7 @@
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong>?
</p>
<umb-confirm on-confirm="vm.performDelete" on-cancel="vm.cancel">
<umb-confirm on-confirm="vm.performDelete" on-cancel="vm.cancel" confirm-button-style="danger">
</umb-confirm>
</div>
@@ -10,6 +10,10 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource,
var vm = this;
vm.header = {};
vm.header.editorfor = "relationType_tabRelationType";
vm.header.setPageTitle = true;
vm.page = {};
vm.page.loading = false;
vm.page.saveButtonState = "init";

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