Merge remote-tracking branch 'origin/netcore/dev' into netcore/feature/AB4549-Move-models-from-web

# Conflicts:
#	src/Umbraco.Web/Editors/ContentTypeController.cs
#	src/Umbraco.Web/Editors/PackageInstallController.cs
This commit is contained in:
Bjarke Berg
2020-01-21 11:03:44 +01:00
234 changed files with 5137 additions and 4145 deletions
+10 -6
View File
@@ -13,7 +13,7 @@
*.png binary
*.gif binary
*.cs text=auto diff=csharp
*.cs text=auto diff=csharp
*.vb text=auto
*.c text=auto
*.cpp text=auto
@@ -41,9 +41,13 @@
*.fs text=auto
*.fsx text=auto
*.hs text=auto
*.json text=auto
*.xml text=auto
*.csproj text=auto merge=union
*.vbproj text=auto merge=union
*.fsproj text=auto merge=union
*.dbproj text=auto merge=union
*.sln text=auto eol=crlf merge=union
*.csproj text=auto merge=union
*.vbproj text=auto merge=union
*.fsproj text=auto merge=union
*.dbproj text=auto merge=union
*.sln text=auto eol=crlf merge=union
*.gitattributes text=auto
+1
View File
@@ -55,6 +55,7 @@ App_Data/TEMP/*
src/Umbraco.Web.UI/[Cc]ss/*
src/Umbraco.Web.UI/App_Code/*
src/Umbraco.Web.UI/App_Data/*
src/Umbraco.Web.UI/data/*
src/Umbraco.Tests/App_Data/*
src/Umbraco.Web.UI/[Mm]edia/*
src/Umbraco.Web.UI/[Mm]aster[Pp]ages/*
+1 -1
View File
@@ -28,7 +28,7 @@
<dependency id="ClientDependency" version="[1.9.8,1.999999)" />
<dependency id="ClientDependency-Mvc5" version="[1.9.3,1.999999)" />
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
<dependency id="Examine" version="[1.0.1,1.999999)" />
<dependency id="Examine" version="[1.0.2,1.999999)" />
<dependency id="HtmlAgilityPack" version="[1.8.14,1.999999)" />
<dependency id="ImageProcessor" version="[2.7.0.100,2.999999)" />
<dependency id="LightInject.Mvc" version="[2.0.0,2.999999)" />
@@ -51,7 +51,7 @@ namespace Umbraco.Core.Composing
}
catch (Exception ex)
{
_logger.Error(componentType, ex, "Error while terminating component {ComponentType}.", componentType.FullName);
_logger.Error<ComponentCollection>(ex, "Error while terminating component {ComponentType}.", componentType.FullName);
}
}
}
@@ -22,28 +22,6 @@ namespace Umbraco.Core.Composing
private const int LogThresholdMilliseconds = 100;
/// <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="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>
@@ -9,10 +9,6 @@ namespace Umbraco.Core
/// </summary>
public static class AppSettings
{
// TODO: Kill me - still used in Umbraco.Core.IO.SystemFiles:27
[Obsolete("We need to kill this appsetting as we do not use XML content cache umbraco.config anymore due to NuCache")]
public const string ContentXML = "Umbraco.Core.ContentXML"; //umbracoContentXML
/// <summary>
/// TODO: FILL ME IN
/// </summary>
@@ -19,6 +19,11 @@ namespace Umbraco.Core
/// </summary>
public static bool VariesByCulture(this ISimpleContentType contentType) => contentType.Variations.VariesByCulture();
/// <summary>
/// Determines whether the content type varies by segment.
/// </summary>
public static bool VariesBySegment(this ISimpleContentType contentType) => contentType.Variations.VariesBySegment();
/// <summary>
/// Determines whether the content type is invariant.
/// </summary>
@@ -1,55 +0,0 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Exceptions
{
/// <summary>
/// The exception that is thrown when a null reference, or an empty argument, is passed to a method that does not accept it as a valid argument.
/// </summary>
/// <seealso cref="System.ArgumentNullException" />
[Obsolete("Throw an ArgumentNullException when the parameter is null or an ArgumentException when its empty instead.")]
[Serializable]
public class ArgumentNullOrEmptyException : ArgumentNullException
{
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentNullOrEmptyException" /> class.
/// </summary>
public ArgumentNullOrEmptyException()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentNullOrEmptyException" /> class with the name of the parameter that caused this exception.
/// </summary>
/// <param name="paramName">The named of the parameter that caused the exception.</param>
public ArgumentNullOrEmptyException(string paramName)
: base(paramName)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentNullOrEmptyException" /> class with a specified error message and the name of the parameter that caused this exception.
/// </summary>
/// <param name="paramName">The named of the parameter that caused the exception.</param>
/// <param name="message">A message that describes the error.</param>
public ArgumentNullOrEmptyException(string paramName, string message)
: base(paramName, message)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentNullOrEmptyException" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for this exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public ArgumentNullOrEmptyException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="ArgumentNullOrEmptyException" /> class.
/// </summary>
/// <param name="info">The object that holds the serialized object data.</param>
/// <param name="context">An object that describes the source or destination of the serialized data.</param>
protected ArgumentNullOrEmptyException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}
@@ -1,52 +0,0 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Exceptions
{
/// <summary>
/// The exception that is thrown when a requested method or operation is not, and will not be, implemented.
/// </summary>
/// <remarks>
/// The <see cref="NotImplementedException" /> is to be used when some code is not implemented,
/// but should eventually be implemented (i.e. work in progress) and is reported by tools such as ReSharper.
/// This exception is to be used when some code is not implemented, and is not meant to be, for whatever
/// reason.
/// </remarks>
/// <seealso cref="System.NotImplementedException" />
[Serializable]
[Obsolete("If a method or operation is not, and will not be, implemented, it is invalid or not supported, so we should throw either an InvalidOperationException or NotSupportedException instead.")]
public class WontImplementException : NotImplementedException
{
/// <summary>
/// Initializes a new instance of the <see cref="WontImplementException" /> class.
/// </summary>
public WontImplementException()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="WontImplementException" /> class with a specified reason message.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
public WontImplementException(string message)
: base(message)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="WontImplementException" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="inner">The exception that is the cause of the current exception. If the <paramref name="inner" /> parameter is not <see langword="null" />, the current exception is raised in a <see langword="catch" /> block that handles the inner exception.</param>
public WontImplementException(string message, Exception inner)
: base(message, inner)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="WontImplementException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected WontImplementException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}
@@ -1,46 +0,0 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.IO
{
/// <summary>
/// The exception that is thrown when the caller does not have the required permission to access a file.
/// </summary>
/// <seealso cref="System.Exception" />
[Obsolete("Throw an UnauthorizedAccessException instead.")]
[Serializable]
public class FileSecurityException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="FileSecurityException" /> class.
/// </summary>
public FileSecurityException()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="FileSecurityException" /> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public FileSecurityException(string message)
: base(message)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="FileSecurityException" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public FileSecurityException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="FileSecurityException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected FileSecurityException(SerializationInfo info, StreamingContext context)
: base(info, context)
{ }
}
}
@@ -53,11 +53,21 @@ namespace Umbraco.Web.Models.ContentEditing
[ReadOnly(true)]
public string Culture { get; set; }
/// <summary>
/// The segment of the property
/// </summary>
/// <remarks>
/// The segment value of a property can always be null but can only have a non-null value
/// when the property can be varied by segment.
/// </remarks>
[DataMember(Name = "segment")]
[ReadOnly(true)]
public string Segment { get; set; }
/// <summary>
/// Used internally during model mapping
/// </summary>
[IgnoreDataMember]
internal IDataEditor PropertyEditor { get; set; }
}
}
@@ -29,6 +29,12 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "culture")]
public string Culture { get; set; }
/// <summary>
/// The segment of this variant, if this is invariant than this is null or empty
/// </summary>
[DataMember(Name = "segment")]
public string Segment { get; set; }
/// <summary>
/// Indicates if the variant should be updated
/// </summary>
@@ -17,25 +17,6 @@ namespace Umbraco.Core
/// <returns></returns>
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
[Obsolete("This method is no longer used or necessary and will be removed from future")]
[EditorBrowsable(EditorBrowsableState.Never)]
public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action)
{
if (factory is ILivePublishedModelFactory liveFactory)
{
lock (liveFactory.SyncRoot)
{
//Call refresh on the live factory to re-compile the models
liveFactory.Refresh();
action();
}
}
else
{
action();
}
}
/// <summary>
/// Sets a flag to reset the ModelsBuilder models if the <see cref="IPublishedModelFactory"/> is <see cref="ILivePublishedModelFactory"/>
/// </summary>
@@ -312,13 +312,6 @@ namespace Umbraco.Core.Services
/// </summary>
OperationResult MoveToRecycleBin(IContent content, int userId = Constants.Security.SuperUserId);
/// <summary>
/// Empties the recycle bin.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")]
OperationResult EmptyRecycleBin();
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IContent"/> that resides in the bin
/// </summary>
@@ -156,13 +156,6 @@ namespace Umbraco.Core.Services
/// <param name="userId">Id of the User deleting the Media</param>
Attempt<OperationResult> MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId);
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")]
OperationResult EmptyRecycleBin();
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
@@ -185,23 +185,6 @@ namespace Umbraco.Core.Configuration
}
}
/// <summary>
/// Gets the name of the content XML file.
/// </summary>
/// <value>The content XML.</value>
/// <remarks>
/// Defaults to ~/App_Data/umbraco.config
/// </remarks>
public string ContentXmlFile
{
get
{
return ConfigurationManager.AppSettings.ContainsKey(Constants.AppSettings.ContentXML)
? ConfigurationManager.AppSettings[Constants.AppSettings.ContentXML]
: "~/App_Data/umbraco.config";
}
}
/// <summary>
/// Gets the path to umbraco's root directory (/umbraco by default).
/// </summary>
+2 -2
View File
@@ -49,9 +49,9 @@
</ItemGroup>
<ItemGroup>
<!-- note: NuGet deals with transitive references now -->
<PackageReference Include="Examine" Version="1.0.1" />
<PackageReference Include="Examine" Version="1.0.2" />
<PackageReference Include="Microsoft.SourceLink.GitHub">
<Version>1.0.0-beta2-19324-01</Version>
<Version>1.0.0-beta2-19554-01</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@@ -5,7 +5,7 @@ using Umbraco.Core.Persistence.DatabaseModelDefinitions;
namespace Umbraco.Core.Migrations.Expressions.Delete.Index
{
public class DeleteIndexBuilder : ExpressionBuilderBase<DeleteIndexExpression>,
IDeleteIndexForTableBuilder, IDeleteIndexOnColumnBuilder
IDeleteIndexForTableBuilder, IExecutableBuilder
{
public DeleteIndexBuilder(DeleteIndexExpression expression)
: base(expression)
@@ -14,28 +14,10 @@ namespace Umbraco.Core.Migrations.Expressions.Delete.Index
/// <inheritdoc />
public void Do() => Expression.Execute();
public IndexColumnDefinition CurrentColumn { get; set; }
public IDeleteIndexOnColumnBuilder OnTable(string tableName)
public IExecutableBuilder OnTable(string tableName)
{
Expression.Index.TableName = tableName;
return this;
}
/// <inheritdoc />
public IExecutableBuilder OnColumn(string columnName)
{
var column = new IndexColumnDefinition { Name = columnName };
Expression.Index.Columns.Add(column);
return new ExecutableBuilder(Expression);
}
/// <inheritdoc />
public IExecutableBuilder OnColumns(params string[] columnNames)
{
foreach (string columnName in columnNames)
Expression.Index.Columns.Add(new IndexColumnDefinition { Name = columnName });
return new ExecutableBuilder(Expression);
}
}
}
@@ -1,4 +1,6 @@
namespace Umbraco.Core.Migrations.Expressions.Delete.Index
using Umbraco.Core.Migrations.Expressions.Common;
namespace Umbraco.Core.Migrations.Expressions.Delete.Index
{
/// <summary>
/// Builds a Delete expression.
@@ -8,6 +10,6 @@
/// <summary>
/// Specifies the table of the index to delete.
/// </summary>
IDeleteIndexOnColumnBuilder OnTable(string tableName);
IExecutableBuilder OnTable(string tableName);
}
}
@@ -1,23 +0,0 @@
using System;
using Umbraco.Core.Migrations.Expressions.Common;
namespace Umbraco.Core.Migrations.Expressions.Delete.Index
{
/// <summary>
/// Builds a Delete expression.
/// </summary>
public interface IDeleteIndexOnColumnBuilder : IFluentBuilder, IExecutableBuilder
{
/// <summary>
/// Specifies the column of the index.
/// </summary>
[Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")]
IExecutableBuilder OnColumn(string columnName);
/// <summary>
/// Specifies the column of the index.
/// </summary>
[Obsolete("I don't think this would ever be used when dropping an index, see DeleteIndexExpression.ToString")]
IExecutableBuilder OnColumns(params string[] columnNames);
}
}
@@ -16,16 +16,25 @@ namespace Umbraco.Web.Models
[DataMember(Name = "name")]
public string Name { get; set; }
[DataMember(Name = "alias")]
public string Alias { get; set; }
[DataMember(Name = "group")]
public string Group { get; set; }
[DataMember(Name = "groupOrder")]
public int GroupOrder { get; set; }
[DataMember(Name = "hidden")]
public bool Hidden { get; set; }
[DataMember(Name = "allowDisable")]
public bool AllowDisable { get; set; }
[DataMember(Name = "requiredSections")]
public List<string> RequiredSections { get; set; }
[DataMember(Name = "steps")]
public BackOfficeTourStep[] Steps { get; set; }
@@ -21,52 +21,117 @@ namespace Umbraco.Web.Models.Mapping
public IEnumerable<ContentVariantDisplay> Map(IContent source, MapperContext context)
{
var result = new List<ContentVariantDisplay>();
if (!source.ContentType.VariesByCulture())
var variesByCulture = source.ContentType.VariesByCulture();
var variesBySegment = source.ContentType.VariesBySegment();
IList<ContentVariantDisplay> variants = new List<ContentVariantDisplay>();
if (!variesByCulture && !variesBySegment)
{
//this is invariant so just map the IContent instance to ContentVariationDisplay
result.Add(context.Map<ContentVariantDisplay>(source));
// this is invariant so just map the IContent instance to ContentVariationDisplay
var variantDisplay = context.Map<ContentVariantDisplay>(source);
variants.Add(variantDisplay);
}
else if (variesByCulture && !variesBySegment)
{
var languages = GetLanguages(context);
variants = languages
.Select(language => CreateVariantDisplay(context, source, language, null))
.ToList();
}
else if (variesBySegment && !variesByCulture)
{
// Segment only
var segments = GetSegments(source);
variants = segments
.Select(segment => CreateVariantDisplay(context, source, null, segment))
.ToList();
}
else
{
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
if (allLanguages.Count == 0) return Enumerable.Empty<ContentVariantDisplay>(); //this should never happen
// Culture and segment
var languages = GetLanguages(context).ToList();
var segments = GetSegments(source).ToList();
var langs = context.MapEnumerable<ILanguage, Language>(allLanguages).ToList();
//create a variant for each language, then we'll populate the values
var variants = langs.Select(x =>
if (languages.Count == 0 || segments.Count == 0)
{
//We need to set the culture in the mapping context since this is needed to ensure that the correct property values
//are resolved during the mapping
context.SetCulture(x.IsoCode);
return context.Map<ContentVariantDisplay>(source);
}).ToList();
for (int i = 0; i < langs.Count; i++)
{
var x = langs[i];
var variant = variants[i];
variant.Language = x;
variant.Name = source.GetCultureName(x.IsoCode);
// This should not happen
throw new InvalidOperationException("No languages or segments available");
}
//Put the default language first in the list & then sort rest by a-z
var defaultLang = variants.SingleOrDefault(x => x.Language.IsDefault);
variants = languages
.SelectMany(language => segments
.Select(segment => CreateVariantDisplay(context, source, language, segment)))
.ToList();
}
//Remove the default language from the list for now
variants.Remove(defaultLang);
//Sort the remaining languages a-z
variants = variants.OrderBy(x => x.Language.Name).ToList();
//Insert the default language as the first item
variants.Insert(0, defaultLang);
return SortVariants(variants);
}
private IList<ContentVariantDisplay> SortVariants(IList<ContentVariantDisplay> variants)
{
if (variants == null || variants.Count <= 1)
{
return variants;
}
return result;
// Default variant first, then order by language, segment.
return variants
.OrderBy(v => IsDefaultLanguage(v) ? 0 : 1)
.ThenBy(v => IsDefaultSegment(v) ? 0 : 1)
.ThenBy(v => v?.Language?.Name)
.ThenBy(v => v.Segment)
.ToList();
}
private static bool IsDefaultSegment(ContentVariantDisplay variant)
{
return variant.Segment == null;
}
private static bool IsDefaultLanguage(ContentVariantDisplay variant)
{
return variant.Language == null || variant.Language.IsDefault;
}
private IEnumerable<Language> GetLanguages(MapperContext context)
{
var allLanguages = _localizationService.GetAllLanguages().OrderBy(x => x.Id).ToList();
if (allLanguages.Count == 0)
{
// This should never happen
return Enumerable.Empty<Language>();
}
else
{
return context.MapEnumerable<ILanguage, Language>(allLanguages).ToList();
}
}
/// <summary>
/// Returns all segments assigned to the content
/// </summary>
/// <param name="content"></param>
/// <returns>
/// Returns all segments assigned to the content including 'null' values
/// </returns>
private IEnumerable<string> GetSegments(IContent content)
{
return content.Properties.SelectMany(p => p.Values.Select(v => v.Segment)).Distinct();
}
private ContentVariantDisplay CreateVariantDisplay(MapperContext context, IContent content, Language language, string segment)
{
context.SetCulture(language?.IsoCode);
context.SetSegment(segment);
var variantDisplay = context.Map<ContentVariantDisplay>(content);
variantDisplay.Segment = segment;
variantDisplay.Language = language;
variantDisplay.Name = content.GetCultureName(language?.IsoCode);
return variantDisplay;
}
}
}
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Logging;
@@ -23,6 +24,7 @@ namespace Umbraco.Web.Models.Mapping
public void DefineMaps(UmbracoMapper mapper)
{
mapper.Define<IMacro, EntityBasic>((source, context) => new EntityBasic(), Map);
mapper.Define<IMacro, MacroDisplay>((source, context) => new MacroDisplay(), Map);
mapper.Define<IMacro, IEnumerable<MacroParameter>>((source, context) => context.MapEnumerable<IMacroProperty, MacroParameter>(source.Properties.Values));
mapper.Define<IMacroProperty, MacroParameter>((source, context) => new MacroParameter(), Map);
}
@@ -40,6 +42,23 @@ namespace Umbraco.Web.Models.Mapping
target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key);
}
private void Map(IMacro source, MacroDisplay target, MapperContext context)
{
target.Alias = source.Alias;
target.Icon = Constants.Icons.Macro;
target.Id = source.Id;
target.Key = source.Key;
target.Name = source.Name;
target.ParentId = -1;
target.Path = "-1," + source.Id;
target.Udi = Udi.Create(Constants.UdiEntityType.Macro, source.Key);
target.CacheByPage = source.CacheByPage;
target.CacheByUser = source.CacheByMember;
target.CachePeriod = source.CacheDuration;
target.UseInEditor = source.UseInEditor;
target.RenderInEditor = !source.DontRender;
target.View = source.MacroSource;
}
// Umbraco.Code.MapAll -Value
private void Map(IMacroProperty source, MacroParameter target, MapperContext context)
{
@@ -8,6 +8,7 @@ namespace Umbraco.Web.Models.Mapping
public static class MapperContextExtensions
{
private const string CultureKey = "Map.Culture";
private const string SegmentKey = "Map.Segment";
private const string IncludedPropertiesKey = "Map.IncludedProperties";
/// <summary>
@@ -18,6 +19,14 @@ namespace Umbraco.Web.Models.Mapping
return context.HasItems && context.Items.TryGetValue(CultureKey, out var obj) && obj is string s ? s : null;
}
/// <summary>
/// Gets the context segment.
/// </summary>
public static string GetSegment(this MapperContext context)
{
return context.HasItems && context.Items.TryGetValue(SegmentKey, out var obj) && obj is string s ? s : null;
}
/// <summary>
/// Sets a context culture.
/// </summary>
@@ -26,6 +35,14 @@ namespace Umbraco.Web.Models.Mapping
context.Items[CultureKey] = culture;
}
/// <summary>
/// Sets a context segment.
/// </summary>
public static void SetSegment(this MapperContext context, string segment)
{
context.Items[SegmentKey] = segment;
}
/// <summary>
/// Get included properties.
/// </summary>
@@ -1,106 +0,0 @@
using System;
using System.Runtime.Serialization;
namespace Umbraco.Core.Persistence
{
/// <summary>
/// An exception used to indicate that an Umbraco entity could not be found.
/// </summary>
/// <seealso cref="System.Exception" />
[Obsolete("Instead of throwing an exception, return null or an HTTP 404 status code instead.")]
[Serializable]
public class EntityNotFoundException : Exception
{
/// <summary>
/// Gets the identifier.
/// </summary>
/// <value>
/// The identifier.
/// </value>
/// <remarks>
/// This object should be serializable to prevent a <see cref="SerializationException" /> to be thrown.
/// </remarks>
public object Id { get; private set; }
/// <summary>
/// Initializes a new instance of the <see cref="EntityNotFoundException" /> class.
/// </summary>
public EntityNotFoundException()
{ }
/// <summary>
/// Initializes a new instance of the <see cref="EntityNotFoundException" /> class.
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="message">The message.</param>
public EntityNotFoundException(object id, string message)
: base(message)
{
Id = id;
}
/// <summary>
/// Initializes a new instance of the <see cref="EntityNotFoundException" /> class.
/// </summary>
/// <param name="message">The message that describes the error.</param>
public EntityNotFoundException(string message)
: base(message)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="EntityNotFoundException" /> class.
/// </summary>
/// <param name="message">The error message that explains the reason for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference (<see langword="Nothing" /> in Visual Basic) if no inner exception is specified.</param>
public EntityNotFoundException(string message, Exception innerException)
: base(message, innerException)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="EntityNotFoundException" /> class.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
protected EntityNotFoundException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
Id = info.GetValue(nameof(Id), typeof(object));
}
/// <summary>
/// When overridden in a derived class, sets the <see cref="T:System.Runtime.Serialization.SerializationInfo" /> with information about the exception.
/// </summary>
/// <param name="info">The <see cref="T:System.Runtime.Serialization.SerializationInfo" /> that holds the serialized object data about the exception being thrown.</param>
/// <param name="context">The <see cref="T:System.Runtime.Serialization.StreamingContext" /> that contains contextual information about the source or destination.</param>
/// <exception cref="ArgumentNullException">info</exception>
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
if (info == null)
{
throw new ArgumentNullException(nameof(info));
}
info.AddValue(nameof(Id), Id);
base.GetObjectData(info, context);
}
/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="System.String" /> that represents this instance.
/// </returns>
public override string ToString()
{
var result = base.ToString();
if (Id != null)
{
return "Umbraco entity (id: " + Id + ") not found. " + result;
}
return result;
}
}
}
@@ -2030,13 +2030,6 @@ namespace Umbraco.Core.Services.Implement
_documentRepository.Save(content);
}
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IContent"/> that resides in the bin
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")]
public OperationResult EmptyRecycleBin() => EmptyRecycleBin(Constants.Security.SuperUserId);
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IContent"/> that resides in the bin
/// </summary>
@@ -1029,13 +1029,6 @@ namespace Umbraco.Core.Services.Implement
_mediaRepository.Save(media);
}
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Use EmptyRecycleBin with explicit indication of user ID instead")]
public OperationResult EmptyRecycleBin() => EmptyRecycleBin(Constants.Security.SuperUserId);
/// <summary>
/// Empties the Recycle Bin by deleting all <see cref="IMedia"/> that resides in the bin
/// </summary>
@@ -23,6 +23,9 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
{
var isLegacyModelsBuilderInstalled = IsLegacyModelsBuilderInstalled();
composition.Configs.Add<IModelsBuilderConfig>(() => new ModelsBuilderConfig(composition.IOHelper));
if (isLegacyModelsBuilderInstalled)
{
ComposeForLegacyModelsBuilder(composition);
@@ -32,7 +35,6 @@ namespace Umbraco.ModelsBuilder.Embedded.Compose
composition.Components().Append<ModelsBuilderComponent>();
composition.Register<UmbracoServices>(Lifetime.Singleton);
composition.Configs.Add<IModelsBuilderConfig>(() => new ModelsBuilderConfig(composition.IOHelper));
composition.RegisterUnique<ModelsGenerator>();
composition.RegisterUnique<LiveModelsProvider>();
composition.RegisterUnique<OutOfDateModelsStatus>();
@@ -1,9 +1,9 @@
using System;
using System.Configuration;
using System.IO;
using System.Threading;
using System.Web.Configuration;
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.IO;
namespace Umbraco.ModelsBuilder.Embedded.Configuration
@@ -14,6 +14,13 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
public class ModelsBuilderConfig : IModelsBuilderConfig
{
private readonly IIOHelper _ioHelper;
private const string Prefix = "Umbraco.ModelsBuilder.";
private object _modelsModelLock;
private bool _modelsModelConfigured;
private ModelsMode _modelsMode;
private object _flagOutOfDateModelsLock;
private bool _flagOutOfDateModelsConfigured;
private bool _flagOutOfDateModels;
public const string DefaultModelsNamespace = "Umbraco.Web.PublishedModels";
public string DefaultModelsDirectory => _ioHelper.MapPath("~/App_Data/Models");
@@ -24,11 +31,10 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
public ModelsBuilderConfig(IIOHelper ioHelper)
{
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
const string prefix = "Umbraco.ModelsBuilder.";
// giant kill switch, default: false
// must be explicitely set to true for anything else to happen
Enable = ConfigurationManager.AppSettings[prefix + "Enable"] == "true";
Enable = ConfigurationManager.AppSettings[Prefix + "Enable"] == "true";
// ensure defaults are initialized for tests
ModelsNamespace = DefaultModelsNamespace;
@@ -38,44 +44,19 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
// stop here, everything is false
if (!Enable) return;
// mode
var modelsMode = ConfigurationManager.AppSettings[prefix + "ModelsMode"];
if (!string.IsNullOrWhiteSpace(modelsMode))
{
switch (modelsMode)
{
case nameof(ModelsMode.Nothing):
ModelsMode = ModelsMode.Nothing;
break;
case nameof(ModelsMode.PureLive):
ModelsMode = ModelsMode.PureLive;
break;
case nameof(ModelsMode.AppData):
ModelsMode = ModelsMode.AppData;
break;
case nameof(ModelsMode.LiveAppData):
ModelsMode = ModelsMode.LiveAppData;
break;
default:
throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode."
+ " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode))));
}
}
// default: false
AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true");
AcceptUnsafeModelsDirectory = ConfigurationManager.AppSettings[Prefix + "AcceptUnsafeModelsDirectory"].InvariantEquals("true");
// default: true
EnableFactory = !ConfigurationManager.AppSettings[prefix + "EnableFactory"].InvariantEquals("false");
FlagOutOfDateModels = !ConfigurationManager.AppSettings[prefix + "FlagOutOfDateModels"].InvariantEquals("false");
EnableFactory = !ConfigurationManager.AppSettings[Prefix + "EnableFactory"].InvariantEquals("false");
// default: initialized above with DefaultModelsNamespace const
var value = ConfigurationManager.AppSettings[prefix + "ModelsNamespace"];
var value = ConfigurationManager.AppSettings[Prefix + "ModelsNamespace"];
if (!string.IsNullOrWhiteSpace(value))
ModelsNamespace = value;
// default: initialized above with DefaultModelsDirectory const
value = ConfigurationManager.AppSettings[prefix + "ModelsDirectory"];
value = ConfigurationManager.AppSettings[Prefix + "ModelsDirectory"];
if (!string.IsNullOrWhiteSpace(value))
{
var root = _ioHelper.MapPath("~/");
@@ -87,18 +68,14 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
}
// default: 0
value = ConfigurationManager.AppSettings[prefix + "DebugLevel"];
value = ConfigurationManager.AppSettings[Prefix + "DebugLevel"];
if (!string.IsNullOrWhiteSpace(value))
{
int debugLevel;
if (!int.TryParse(value, out debugLevel))
if (!int.TryParse(value, out var debugLevel))
throw new ConfigurationErrorsException($"Invalid debug level \"{value}\".");
DebugLevel = debugLevel;
}
// not flagging if not generating, or live (incl. pure)
if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive())
FlagOutOfDateModels = false;
}
/// <summary>
@@ -116,11 +93,11 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
{
_ioHelper = ioHelper ?? throw new ArgumentNullException(nameof(ioHelper));
Enable = enable;
ModelsMode = modelsMode;
_modelsMode = modelsMode;
ModelsNamespace = string.IsNullOrWhiteSpace(modelsNamespace) ? DefaultModelsNamespace : modelsNamespace;
EnableFactory = enableFactory;
FlagOutOfDateModels = flagOutOfDateModels;
_flagOutOfDateModels = flagOutOfDateModels;
ModelsDirectory = string.IsNullOrWhiteSpace(modelsDirectory) ? DefaultModelsDirectory : modelsDirectory;
AcceptUnsafeModelsDirectory = acceptUnsafeModelsDirectory;
DebugLevel = debugLevel;
@@ -169,7 +146,26 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
/// <summary>
/// Gets the models mode.
/// </summary>
public ModelsMode ModelsMode { get; }
public ModelsMode ModelsMode =>
LazyInitializer.EnsureInitialized(ref _modelsMode, ref _modelsModelConfigured, ref _modelsModelLock, () =>
{
// mode
var modelsMode = ConfigurationManager.AppSettings[Prefix + "ModelsMode"];
if (string.IsNullOrWhiteSpace(modelsMode)) return ModelsMode.Nothing; //default
switch (modelsMode)
{
case nameof(ModelsMode.Nothing):
return ModelsMode.Nothing;
case nameof(ModelsMode.PureLive):
return ModelsMode.PureLive;
case nameof(ModelsMode.AppData):
return ModelsMode.AppData;
case nameof(ModelsMode.LiveAppData):
return ModelsMode.LiveAppData;
default:
throw new ConfigurationErrorsException($"ModelsMode \"{modelsMode}\" is not a valid mode." + " Note that modes are case-sensitive. Possible values are: " + string.Join(", ", Enum.GetNames(typeof(ModelsMode))));
}
});
/// <summary>
/// Gets a value indicating whether system.web/compilation/@debug is true.
@@ -201,7 +197,17 @@ namespace Umbraco.ModelsBuilder.Embedded.Configuration
/// <remarks>Models become out-of-date when data types or content types are updated. When this
/// setting is activated the ~/App_Data/Models/ood.txt file is then created. When models are
/// generated through the dashboard, the files is cleared. Default value is <c>false</c>.</remarks>
public bool FlagOutOfDateModels { get; }
public bool FlagOutOfDateModels
=> LazyInitializer.EnsureInitialized(ref _flagOutOfDateModels, ref _flagOutOfDateModelsConfigured, ref _flagOutOfDateModelsLock, () =>
{
var flagOutOfDateModels = !ConfigurationManager.AppSettings[Prefix + "FlagOutOfDateModels"].InvariantEquals("false");
if (ModelsMode == ModelsMode.Nothing || ModelsMode.IsLive())
{
flagOutOfDateModels = false;
}
return flagOutOfDateModels;
});
/// <summary>
/// Gets the models directory.
@@ -92,7 +92,7 @@
<Version>2.10.0</Version>
</PackageReference>
<PackageReference Include="Microsoft.SourceLink.GitHub">
<Version>1.0.0-beta2-19324-01</Version>
<Version>1.0.0-beta2-19554-01</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Umbraco.TestData")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Umbraco.TestData")]
[assembly: AssemblyCopyright("Copyright © 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("fb5676ed-7a69-492c-b802-e7b24144c0fc")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
@@ -0,0 +1,83 @@
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Mvc;
using Umbraco.Core;
using Umbraco.Core.Models;
using Umbraco.Web.Mvc;
namespace Umbraco.TestData
{
public class SegmentTestController : SurfaceController
{
public ActionResult EnableDocTypeSegments(string alias, string propertyTypeAlias)
{
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
return HttpNotFound();
var ct = Services.ContentTypeService.Get(alias);
if (ct == null)
return Content($"No document type found by alias {alias}");
var propType = ct.PropertyTypes.FirstOrDefault(x => x.Alias == propertyTypeAlias);
if (propType == null)
return Content($"The document type {alias} does not have a property type {propertyTypeAlias ?? "null"}");
if (ct.Variations.VariesBySegment())
return Content($"The document type {alias} already allows segments, nothing has been changed");
ct.Variations = ct.Variations.SetFlag(ContentVariation.Segment);
propType.Variations = propType.Variations.SetFlag(ContentVariation.Segment);
Services.ContentTypeService.Save(ct);
return Content($"The document type {alias} and property type {propertyTypeAlias} now allows segments");
}
public ActionResult DisableDocTypeSegments(string alias)
{
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
return HttpNotFound();
var ct = Services.ContentTypeService.Get(alias);
if (ct == null)
return Content($"No document type found by alias {alias}");
if (!ct.VariesBySegment())
return Content($"The document type {alias} does not allow segments, nothing has been changed");
ct.Variations = ct.Variations.UnsetFlag(ContentVariation.Segment);
Services.ContentTypeService.Save(ct);
return Content($"The document type {alias} no longer allows segments");
}
public ActionResult AddSegmentData(int contentId, string propertyAlias, string value, string segment, string culture = null)
{
var content = Services.ContentService.GetById(contentId);
if (content == null)
return Content($"No content found by id {contentId}");
if (propertyAlias.IsNullOrWhiteSpace() || !content.HasProperty(propertyAlias))
return Content($"The content by id {contentId} does not contain a property with alias {propertyAlias ?? "null"}");
if (content.ContentType.VariesByCulture() && culture.IsNullOrWhiteSpace())
return Content($"The content by id {contentId} varies by culture but no culture was specified");
if (value.IsNullOrWhiteSpace())
return Content("'value' cannot be null");
if (segment.IsNullOrWhiteSpace())
return Content("'segment' cannot be null");
content.SetValue(propertyAlias, value, culture, segment);
Services.ContentService.Save(content);
return Content($"Segment value has been set on content {contentId} for property {propertyAlias}");
}
}
}
@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{FB5676ED-7A69-492C-B802-E7B24144C0FC}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Umbraco.TestData</RootNamespace>
<AssemblyName>Umbraco.TestData</AssemblyName>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="SegmentTestController.cs" />
<Compile Include="UmbracoTestDataController.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="readme.md" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Umbraco.Abstractions\Umbraco.Abstractions.csproj">
<Project>{29aa69d9-b597-4395-8d42-43b1263c240a}</Project>
<Name>Umbraco.Abstractions</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Infrastructure\Umbraco.Infrastructure.csproj">
<Project>{3ae7bf57-966b-45a5-910a-954d7c554441}</Project>
<Name>Umbraco.Infrastructure</Name>
</ProjectReference>
<ProjectReference Include="..\Umbraco.Web\Umbraco.Web.csproj">
<Project>{651e1350-91b6-44b7-bd60-7207006d7003}</Project>
<Name>Umbraco.Web</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Bogus">
<Version>28.4.4</Version>
</PackageReference>
<PackageReference Include="Microsoft.AspNet.Mvc">
<Version>5.2.7</Version>
</PackageReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
@@ -0,0 +1,292 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Umbraco.Core;
using System.Web.Mvc;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Persistence;
using Umbraco.Core.PropertyEditors;
using Umbraco.Core.Services;
using Umbraco.Web;
using Umbraco.Web.Mvc;
using System.Configuration;
using Bogus;
using Umbraco.Core.Scoping;
using Umbraco.Core.Strings;
namespace Umbraco.TestData
{
/// <summary>
/// Creates test data
/// </summary>
public class UmbracoTestDataController : SurfaceController
{
private const string RichTextDataTypeName = "UmbracoTestDataContent.RTE";
private const string MediaPickerDataTypeName = "UmbracoTestDataContent.MediaPicker";
private const string TextDataTypeName = "UmbracoTestDataContent.Text";
private const string TestDataContentTypeAlias = "umbTestDataContent";
private readonly IScopeProvider _scopeProvider;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IShortStringHelper _shortStringHelper;
public UmbracoTestDataController(IScopeProvider scopeProvider, PropertyEditorCollection propertyEditors, IUmbracoContextAccessor umbracoContextAccessor, IUmbracoDatabaseFactory databaseFactory, ServiceContext services, AppCaches appCaches, ILogger logger, IProfilingLogger profilingLogger, UmbracoHelper umbracoHelper, IShortStringHelper shortStringHelper)
: base(umbracoContextAccessor, databaseFactory, services, appCaches, logger, profilingLogger, umbracoHelper)
{
_scopeProvider = scopeProvider;
_propertyEditors = propertyEditors;
_shortStringHelper = shortStringHelper;
}
/// <summary>
/// Creates a content and associated media tree (hierarchy)
/// </summary>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <param name="locale"></param>
/// <returns></returns>
/// <remarks>
/// Each content item created is associated to a media item via a media picker and therefore a relation is created between the two
/// </remarks>
public ActionResult CreateTree(int count, int depth, string locale = "en")
{
if (ConfigurationManager.AppSettings["Umbraco.TestData.Enabled"] != "true")
return HttpNotFound();
if (!Validate(count, depth, out var message, out var perLevel))
throw new InvalidOperationException(message);
var faker = new Faker(locale);
var company = faker.Company.CompanyName();
using (var scope = _scopeProvider.CreateScope())
{
var imageIds = CreateMediaTree(company, faker, count, depth).ToList();
var contentIds = CreateContentTree(company, faker, count, depth, imageIds, out var root).ToList();
Services.ContentService.SaveAndPublishBranch(root, true);
scope.Complete();
}
return Content("Done");
}
private bool Validate(int count, int depth, out string message, out int perLevel)
{
perLevel = 0;
message = null;
if (count <= 0)
{
message = "Count must be more than 0";
return false;
}
perLevel = count / depth;
if (perLevel < 1)
{
message = "Count not high enough for specified for number of levels required";
return false;
}
return true;
}
/// <summary>
/// Utility to create a tree hierarchy
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="parent"></param>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <param name="create">
/// A callback that returns a tuple of Content and another callback to produce a Container.
/// For media, a container will be another folder, for content the container will be the Content itself.
/// </param>
/// <returns></returns>
private IEnumerable<Udi> CreateHierarchy<T>(
T parent, int count, int depth,
Func<T, (T content, Func<T> container)> create)
where T: class, IContentBase
{
yield return parent.GetUdi();
// This will not calculate a balanced tree but it will ensure that there will be enough nodes deep enough to not fill up the tree.
var totalDescendants = count - 1;
var perLevel = Math.Ceiling(totalDescendants / (double)depth);
var perBranch = Math.Ceiling(perLevel / depth);
var tracked = new Stack<(T parent, int childCount)>();
var currChildCount = 0;
for (int i = 0; i < count; i++)
{
var created = create(parent);
var contentItem = created.content;
yield return contentItem.GetUdi();
currChildCount++;
if (currChildCount == perBranch)
{
// move back up...
var prev = tracked.Pop();
// restore child count
currChildCount = prev.childCount;
// restore the parent
parent = prev.parent;
}
else if (contentItem.Level < depth)
{
// track the current parent and it's current child count
tracked.Push((parent, currChildCount));
// not at max depth, create below
parent = created.container();
currChildCount = 0;
}
}
}
/// <summary>
/// Creates the media tree hiearachy
/// </summary>
/// <param name="company"></param>
/// <param name="faker"></param>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <returns></returns>
private IEnumerable<Udi> CreateMediaTree(string company, Faker faker, int count, int depth)
{
var parent = Services.MediaService.CreateMediaWithIdentity(company, -1, Constants.Conventions.MediaTypes.Folder);
return CreateHierarchy(parent, count, depth, currParent =>
{
var imageUrl = faker.Image.PicsumUrl();
// we are appending a &ext=.jpg to the end of this for a reason. The result of this url will be something like:
// https://picsum.photos/640/480/?image=106
// and due to the way that we detect images there must be an extension so we'll change it to
// https://picsum.photos/640/480/?image=106&ext=.jpg
// which will trick our app into parsing this and thinking it's an image ... which it is so that's good.
// if we don't do this we don't get thumbnails in the back office.
imageUrl += "&ext=.jpg";
var media = Services.MediaService.CreateMedia(faker.Commerce.ProductName(), currParent, Constants.Conventions.MediaTypes.Image);
media.SetValue(Constants.Conventions.Media.File, imageUrl);
Services.MediaService.Save(media);
return (media, () =>
{
// create a folder to contain child media
var container = Services.MediaService.CreateMediaWithIdentity(faker.Commerce.Department(), currParent, Constants.Conventions.MediaTypes.Folder);
return container;
});
});
}
/// <summary>
/// Creates the content tree hiearachy
/// </summary>
/// <param name="company"></param>
/// <param name="faker"></param>
/// <param name="count"></param>
/// <param name="depth"></param>
/// <param name="imageIds"></param>
/// <returns></returns>
private IEnumerable<Udi> CreateContentTree(string company, Faker faker, int count, int depth, List<Udi> imageIds, out IContent root)
{
var random = new Random(company.GetHashCode());
var docType = GetOrCreateContentType();
var parent = Services.ContentService.Create(company, -1, docType.Alias);
parent.SetValue("review", faker.Rant.Review());
parent.SetValue("desc", company);
parent.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
Services.ContentService.Save(parent);
root = parent;
return CreateHierarchy(parent, count, depth, currParent =>
{
var content = Services.ContentService.Create(faker.Commerce.ProductName(), currParent, docType.Alias);
content.SetValue("review", faker.Rant.Review());
content.SetValue("desc", string.Join(", ", Enumerable.Range(0, 5).Select(x => faker.Commerce.ProductAdjective()))); ;
content.SetValue("media", imageIds[random.Next(0, imageIds.Count - 1)]);
Services.ContentService.Save(content);
return (content, () => content);
});
}
private IContentType GetOrCreateContentType()
{
var docType = Services.ContentTypeService.Get(TestDataContentTypeAlias);
if (docType != null)
return docType;
docType = new ContentType(_shortStringHelper, -1)
{
Alias = TestDataContentTypeAlias,
Name = "Umbraco Test Data Content",
Icon = "icon-science color-green"
};
docType.AddPropertyGroup("Content");
docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateRichText(), "review")
{
Name = "Review"
});
docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateMediaPicker(), "media")
{
Name = "Media"
});
docType.AddPropertyType(new PropertyType(_shortStringHelper, GetOrCreateText(), "desc")
{
Name = "Description"
});
Services.ContentTypeService.Save(docType);
docType.AllowedContentTypes = new[] { new ContentTypeSort(docType.Id, 0) };
Services.ContentTypeService.Save(docType);
return docType;
}
private IDataType GetOrCreateRichText() => GetOrCreateDataType(RichTextDataTypeName, Constants.PropertyEditors.Aliases.TinyMce);
private IDataType GetOrCreateMediaPicker() => GetOrCreateDataType(MediaPickerDataTypeName, Constants.PropertyEditors.Aliases.MediaPicker);
private IDataType GetOrCreateText() => GetOrCreateDataType(TextDataTypeName, Constants.PropertyEditors.Aliases.TextBox);
private IDataType GetOrCreateDataType(string name, string editorAlias)
{
var dt = Services.DataTypeService.GetDataType(name);
if (dt != null) return dt;
var editor = _propertyEditors.FirstOrDefault(x => x.Alias == editorAlias);
if (editor == null)
throw new InvalidOperationException($"No {editorAlias} editor found");
dt = new DataType(editor)
{
Name = name,
Configuration = editor.GetConfigurationEditor().DefaultConfigurationObject,
DatabaseType = ValueStorageType.Ntext
};
Services.DataTypeService.Save(dt);
return dt;
}
}
}
+51
View File
@@ -0,0 +1,51 @@
## Umbraco Test Data
This project is a utility to be able to generate large amounts of content and media in an
Umbraco installation for testing.
Currently this project is referenced in the Umbraco.Web.UI project but only when it's being built
in Debug mode (i.e. when testing within Visual Studio).
## Usage
You must use SQL Server for this, using SQLCE will die if you try to bulk create huge amounts of data.
It has to be enabled by an appSetting:
```xml
<add key="Umbraco.TestData.Enabled" value="true"/>
```
Once this is enabled this endpoint can be executed:
`/umbraco/surface/umbracotestdata/CreateTree?count=100&depth=5`
The query string options are:
* `count` = the number of content and media nodes to create
* `depth` = how deep the trees created will be
* `locale` (optional, default = "en") = the language that the data will be generated in
This creates a content and associated media tree (hierarchy). Each content item created is associated
to a media item via a media picker and therefore a relation is created between the two. Each content and
media tree created have the same root node name so it's easy to know which content branch relates to
which media branch.
All values are generated using the very handy `Bogus` package.
## Schema
This will install some schema items:
* `umbTestDataContent` Document Type. __TIP__: If you want to delete all of the content data generated with this tool, just delete this content type
* `UmbracoTestDataContent.RTE` Data Type
* `UmbracoTestDataContent.MediaPicker` Data Type
* `UmbracoTestDataContent.Text` Data Type
For media, the normal folder and image is used
## Media
This does not upload physical files, it just uses a randomized online image as the `umbracoFile` value.
This works when viewing the media item in the media section and the image will show up and with recent changes this will also work
when editing content to view the thumbnail for the picked media.
@@ -251,7 +251,7 @@ namespace Umbraco.Web.PublishedModels
{
Alias = "prop3",
ClrName = "Prop3",
ModelClrType = typeof(global::Umbraco.Core.IO.FileSecurityException),
ModelClrType = typeof(global::Umbraco.Core.Exceptions.BootFailedException),
});
var types = new[] { type1 };
@@ -272,7 +272,7 @@ namespace Umbraco.Web.PublishedModels
Assert.IsTrue(gen.Contains(" global::Umbraco.Core.Models.PublishedContent.IPublishedContent Prop1"));
Assert.IsTrue(gen.Contains(" global::System.Text.StringBuilder Prop2"));
Assert.IsTrue(gen.Contains(" global::Umbraco.Core.IO.FileSecurityException Prop3"));
Assert.IsTrue(gen.Contains(" global::Umbraco.Core.Exceptions.BootFailedException Prop3"));
}
[TestCase("int", typeof(int))]
@@ -7,6 +7,8 @@ using Umbraco.Web;
using Umbraco.Core;
using Umbraco.Tests.Testing;
using Umbraco.Web.Composing;
using Moq;
using Examine;
namespace Umbraco.Tests.PublishedContent
{
@@ -201,7 +203,8 @@ namespace Umbraco.Tests.PublishedContent
[Test]
public void PublishedContentQueryTypedContentList()
{
var query = new PublishedContentQuery(Current.UmbracoContext.PublishedSnapshot, Current.UmbracoContext.VariationContextAccessor);
var examineManager = new Mock<IExamineManager>();
var query = new PublishedContentQuery(Current.UmbracoContext.PublishedSnapshot, Current.UmbracoContext.VariationContextAccessor, examineManager.Object);
var result = query.Content(new[] { 1, 2, 4 }).ToArray();
Assert.AreEqual(2, result.Length);
Assert.AreEqual(1, result[0].Id);
@@ -2372,8 +2372,6 @@ namespace Umbraco.Tests.Services
//MCH: I'm guessing this is an issue because of the format the date is actually stored as, right? Cause we don't do any formatting when saving or loading
Assert.That(sut.GetValue<DateTime>("dateTime").ToString("G"), Is.EqualTo(content.GetValue<DateTime>("dateTime").ToString("G")));
Assert.That(sut.GetValue<string>("colorPicker"), Is.EqualTo("black"));
//that one is gone in 7.4
//Assert.That(sut.GetValue<string>("folderBrowser"), Is.Null);
Assert.That(sut.GetValue<string>("ddlMultiple"), Is.EqualTo("1234,1235"));
Assert.That(sut.GetValue<string>("rbList"), Is.EqualTo("random"));
Assert.That(sut.GetValue<DateTime>("date").ToString("G"), Is.EqualTo(content.GetValue<DateTime>("date").ToString("G")));
@@ -122,8 +122,6 @@ namespace Umbraco.Tests.TestHelpers.Entities
content.SetValue("label", "Non-editable label");
content.SetValue("dateTime", DateTime.Now.AddDays(-20));
content.SetValue("colorPicker", "black");
//that one is gone in 7.4
//content.SetValue("folderBrowser", "");
content.SetValue("ddlMultiple", "1234,1235");
content.SetValue("rbList", "random");
content.SetValue("date", DateTime.Now.AddDays(-10));
@@ -115,7 +115,6 @@ namespace Umbraco.Tests.Testing.TestingTests
internal class FakeUmbracoApiController : UmbracoApiController
{
public FakeUmbracoApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { }
public FakeUmbracoApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { }
}
}
+1 -1
View File
@@ -78,7 +78,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Castle.Core" Version="4.3.1" />
<PackageReference Include="Examine" Version="1.0.1" />
<PackageReference Include="Examine" Version="1.0.2" />
<PackageReference Include="HtmlAgilityPack">
<Version>1.8.14</Version>
</PackageReference>
@@ -34,6 +34,7 @@ using Umbraco.Web.Trees;
using Umbraco.Web.WebApi;
using Umbraco.Web.Composing;
using Task = System.Threading.Tasks.Task;
using Umbraco.Core.Mapping;
namespace Umbraco.Tests.Web.Controllers
{
@@ -268,7 +269,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IProfilingLogger>(),
Factory.GetInstance<IRuntimeState>(),
helper,
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return controller;
}
@@ -303,7 +305,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IProfilingLogger>(),
Factory.GetInstance<IRuntimeState>(),
helper,
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return controller;
}
@@ -346,7 +349,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IProfilingLogger>(),
Factory.GetInstance<IRuntimeState>(),
helper,
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return controller;
}
@@ -394,7 +398,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IProfilingLogger>(),
Factory.GetInstance<IRuntimeState>(),
helper,
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return controller;
}
@@ -434,7 +439,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IProfilingLogger>(),
Factory.GetInstance<IRuntimeState>(),
helper,
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return controller;
}
@@ -480,7 +486,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IProfilingLogger>(),
Factory.GetInstance<IRuntimeState>(),
helper,
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return controller;
}
@@ -34,6 +34,7 @@ using Umbraco.Web.Editors;
using Umbraco.Web.Features;
using Umbraco.Web.Models.ContentEditing;
using IUser = Umbraco.Core.Models.Membership.IUser;
using Umbraco.Core.Mapping;
namespace Umbraco.Tests.Web.Controllers
{
@@ -88,7 +89,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IRuntimeState>(),
helper,
Factory.GetInstance<IMediaFileSystem>(),
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return usersController;
}
@@ -154,7 +156,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IRuntimeState>(),
helper,
Factory.GetInstance<IMediaFileSystem>(),
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return usersController;
}
@@ -191,7 +194,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IRuntimeState>(),
helper,
Factory.GetInstance<IMediaFileSystem>(),
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return usersController;
}
@@ -263,7 +267,8 @@ namespace Umbraco.Tests.Web.Controllers
Factory.GetInstance<IRuntimeState>(),
helper,
Factory.GetInstance<IMediaFileSystem>(),
ShortStringHelper);
ShortStringHelper,
Factory.GetInstance<UmbracoMapper>());
return usersController;
}
@@ -15,14 +15,6 @@ namespace Umbraco.Tests.Web.Mvc
_htmlStringUtilities = new HtmlStringUtilities();
}
[Test]
public void ReplaceLineBreaksWithHtmlBreak()
{
var output = _htmlStringUtilities.ReplaceLineBreaksForHtml("<div><h1>hello world</h1><p>hello world\r\nhello world\rhello world\nhello world</p></div>").ToString();
var expected = "<div><h1>hello world</h1><p>hello world<br />hello world<br />hello world<br />hello world</p></div>";
Assert.AreEqual(expected, output);
}
[Test]
public void TruncateWithElipsis()
{
@@ -9,14 +9,13 @@ pre.code {
padding: 0 3px 2px;
#font > #family > .monospace;
font-size: @baseFontSize - 2;
color: @grayDark;
color: @blueExtraDark;
.border-radius(3px);
}
// Inline code
code {
padding: 2px 4px;
color: #d14;
background-color: #f7f7f9;
border: 1px solid #e1e1e8;
white-space: nowrap;
@@ -79,11 +79,9 @@
// Hover/Focus state
// -----------
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus,
.dropdown-menu > li > button:hover,
.dropdown-menu > li > button:focus,
.dropdown-submenu:hover > a,
.dropdown-submenu:focus > a {
.dropdown-submenu:hover > button {
text-decoration: none;
color: @dropdownLinkColorHover;
#gradient > .vertical(@dropdownLinkBackgroundHover, darken(@dropdownLinkBackgroundHover, 5%));
@@ -92,8 +90,7 @@
// Active state
// ------------
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
.dropdown-menu > .active > a:hover {
color: @dropdownLinkColorActive;
text-decoration: none;
outline: 0;
@@ -104,13 +101,11 @@
// --------------
// Gray out text and ensure the hover/focus state remains gray
.dropdown-menu > .disabled > a,
.dropdown-menu > .disabled > a:hover,
.dropdown-menu > .disabled > a:focus {
.dropdown-menu > .disabled > a:hover {
color: @grayLight;
}
// Nuke hover/focus effects
.dropdown-menu > .disabled > a:hover,
.dropdown-menu > .disabled > a:focus {
.dropdown-menu > .disabled > a:hover {
text-decoration: none;
background-color: transparent;
background-image: none; // Remove CSS gradient
@@ -75,6 +75,8 @@
scope.displayLabelOff = "";
function onInit() {
scope.inputId = scope.inputId || "umb-toggle_" + String.CreateGuid();
setLabelText();
// must wait until the current digest cycle is finished before we emit this event on init,
// otherwise other property editors might not yet be ready to receive the event
@@ -46,6 +46,8 @@
vm.change = change;
function onInit() {
vm.inputId = vm.inputId || "umb-check_" + String.CreateGuid();
// If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [
if (vm.labelKey) {
localizationService.localize(vm.labelKey).then(function (data) {
@@ -44,6 +44,8 @@
vm.change = change;
function onInit() {
vm.inputId = vm.inputId || "umb-radio_" + String.CreateGuid();
// If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [
if (vm.labelKey) {
localizationService.localize(vm.labelKey).then(function (data) {
@@ -0,0 +1,119 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbCodeSnippet
@restrict E
@scope
@description
<h3>Markup example</h3>
<pre>
<div ng-controller="My.Controller as vm">
<umb-code-snippet
language="'csharp'">
{{code}}
</umb-code-snippet>
</div>
</pre>
<h3>Controller example</h3>
<pre>
(function () {
"use strict";
function Controller() {
var vm = this;
}
angular.module("umbraco").controller("My.Controller", Controller);
})();
</pre>
@param {string=} language Language of the code snippet, e.g csharp, html, css.
**/
(function () {
'use strict';
var umbCodeSnippet = {
templateUrl: 'views/components/umb-code-snippet.html',
controller: UmbCodeSnippetController,
controllerAs: 'vm',
transclude: true,
bindings: {
language: '<'
}
};
function UmbCodeSnippetController($timeout) {
const vm = this;
vm.page = {};
vm.$onInit = onInit;
vm.copySuccess = copySuccess;
vm.copyError = copyError;
function onInit() {
vm.guid = String.CreateGuid();
if (vm.language)
{
switch (vm.language.toLowerCase()) {
case "csharp":
case "c#":
vm.language = "C#";
break;
case "html":
vm.language = "HTML";
break;
case "css":
vm.language = "CSS";
break;
case "javascript":
vm.language = "JavaScript";
break;
}
}
}
// copy to clip board success
function copySuccess() {
if (vm.page.copyCodeButtonState !== "success") {
$timeout(function () {
vm.page.copyCodeButtonState = "success";
});
$timeout(function () {
resetClipboardButtonState();
}, 1000);
}
}
// copy to clip board error
function copyError() {
if (vm.page.copyCodeButtonState !== "error") {
$timeout(function () {
vm.page.copyCodeButtonState = "error";
});
$timeout(function () {
resetClipboardButtonState();
}, 1000);
}
}
function resetClipboardButtonState() {
vm.page.copyCodeButtonState = "init";
}
}
angular.module('umbraco.directives').component('umbCodeSnippet', umbCodeSnippet);
})();
@@ -312,14 +312,7 @@ Use this directive to generate a thumbnail grid of media items.
scope.onDetailsHover(item, $event, hover);
}
};
scope.clickEdit = function(item, $event) {
if (scope.onClickEdit) {
scope.onClickEdit({"item": item})
$event.stopPropagation();
}
};
var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue) {
if (angular.isArray(newValue)) {
activate();
@@ -341,8 +334,8 @@ Use this directive to generate a thumbnail grid of media items.
onDetailsHover: "=",
onClick: '=',
onClickName: "=",
onClickEdit: "&?",
allowOnClickEdit: "@?",
allowOpenFolder: "=",
allowOpenFile: "=",
filterBy: "=",
itemMaxWidth: "@",
itemMaxHeight: "@",
@@ -0,0 +1,34 @@
/**
* @ngdoc service
* @name umbraco.resources.emailMarketingResource
* @description Used to add a backoffice user to Umbraco's email marketing system, if user opts in
*
*
**/
function emailMarketingResource($http, umbRequestHelper) {
// LOCAL
// http://localhost:7071/api/EmailProxy
// LIVE
// https://emailcollector.umbraco.io/api/EmailProxy
const emailApiUrl = 'https://emailcollector.umbraco.io/api/EmailProxy';
//the factory object returned
return {
postAddUserToEmailMarketing: (user) => {
return umbRequestHelper.resourcePromise(
$http.post(emailApiUrl,
{
name: user.name,
email: user.email,
usergroup: user.userGroups // [ "admin", "sensitiveData" ]
}),
'Failed to add user to email marketing list');
}
};
}
angular.module('umbraco.resources').factory('emailMarketingResource', emailMarketingResource);
@@ -164,69 +164,6 @@ function logResource($q, $http, umbRequestHelper) {
'Failed to retrieve log data for id');
},
/**
* @ngdoc method
* @name umbraco.resources.logResource#getEntityLog
* @methodOf umbraco.resources.logResource
*
* @description
* <strong>[OBSOLETE] use getPagedEntityLog instead</strong><br />
* Gets the log history for a give entity id
*
* ##usage
* <pre>
* logResource.getEntityLog(1234)
* .then(function(log) {
* alert('its here!');
* });
* </pre>
*
* @param {Int} id id of entity to return log history
* @returns {Promise} resourcePromise object containing the log.
*
*/
getEntityLog: function(id) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logApiBaseUrl",
"GetEntityLog",
[{ id: id }])),
'Failed to retrieve user data for id ' + id);
},
/**
* @ngdoc method
* @name umbraco.resources.logResource#getUserLog
* @methodOf umbraco.resources.logResource
*
* @description
* <strong>[OBSOLETE] use getPagedUserLog instead</strong><br />
* Gets the current user's log history for a given type of log entry
*
* ##usage
* <pre>
* logResource.getUserLog("save", new Date())
* .then(function(log) {
* alert('its here!');
* });
* </pre>
*
* @param {String} type logtype to query for
* @param {DateTime} since query the log back to this date, by defalt 7 days ago
* @returns {Promise} resourcePromise object containing the log.
*
*/
getUserLog: function(type, since) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"logApiBaseUrl",
"GetCurrentUserLog",
[{ logtype: type }, { sinceDate: dateToValidIsoString(since) }])),
'Failed to retrieve log data for current user of type ' + type + ' since ' + since);
},
/**
* @ngdoc method
* @name umbraco.resources.logResource#getLog
@@ -367,7 +367,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function contentPicker(editor) {
editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
editor.section = "content";
editor.treeAlias = "content";
open(editor);
@@ -390,7 +390,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function contentTypePicker(editor) {
editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
editor.section = "settings";
editor.treeAlias = "documentTypes";
open(editor);
@@ -413,7 +413,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function mediaTypePicker(editor) {
editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
editor.section = "settings";
editor.treeAlias = "mediaTypes";
open(editor);
@@ -436,7 +436,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function memberTypePicker(editor) {
editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
editor.section = "settings";
editor.treeAlias = "memberTypes";
open(editor);
@@ -457,7 +457,7 @@ When building a custom infinite editor view you can use the same components as a
function copy(editor) {
editor.view = "views/common/infiniteeditors/copy/copy.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -477,7 +477,7 @@ When building a custom infinite editor view you can use the same components as a
function move(editor) {
editor.view = "views/common/infiniteeditors/move/move.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -495,7 +495,7 @@ When building a custom infinite editor view you can use the same components as a
function embed(editor) {
editor.view = "views/common/infiniteeditors/embed/embed.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -514,7 +514,7 @@ When building a custom infinite editor view you can use the same components as a
function rollback(editor) {
editor.view = "views/common/infiniteeditors/rollback/rollback.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -534,7 +534,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function linkPicker(editor) {
editor.view = "views/common/infiniteeditors/linkpicker/linkpicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -577,7 +577,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function mediaPicker(editor) {
editor.view = "views/common/infiniteeditors/mediapicker/mediapicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
editor.updatedMediaNodes = [];
open(editor);
}
@@ -598,7 +598,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function iconPicker(editor) {
editor.view = "views/common/infiniteeditors/iconpicker/iconpicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -692,7 +692,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function treePicker(editor) {
editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -710,7 +710,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function nodePermissions(editor) {
editor.view = "views/common/infiniteeditors/nodepermissions/nodepermissions.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -728,7 +728,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function insertCodeSnippet(editor) {
editor.view = "views/common/infiniteeditors/insertcodesnippet/insertcodesnippet.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -746,7 +746,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function userGroupPicker(editor) {
editor.view = "views/common/infiniteeditors/usergrouppicker/usergrouppicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -782,7 +782,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function sectionPicker(editor) {
editor.view = "views/common/infiniteeditors/sectionpicker/sectionpicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -800,7 +800,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function insertField(editor) {
editor.view = "views/common/infiniteeditors/insertfield/insertfield.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -818,7 +818,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function templateSections(editor) {
editor.view = "views/common/infiniteeditors/templatesections/templatesections.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -836,7 +836,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function userPicker(editor) {
editor.view = "views/common/infiniteeditors/userpicker/userpicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -858,7 +858,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function itemPicker(editor) {
editor.view = "views/common/infiniteeditors/itempicker/itempicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -876,7 +876,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function macroPicker(editor) {
editor.view = "views/common/infiniteeditors/macropicker/macropicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -896,7 +896,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function memberGroupPicker(editor) {
editor.view = "views/common/infiniteeditors/membergrouppicker/membergrouppicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
open(editor);
}
@@ -917,7 +917,7 @@ When building a custom infinite editor view you can use the same components as a
*/
function memberPicker(editor) {
editor.view = "views/common/infiniteeditors/treepicker/treepicker.html";
editor.size = "small";
if (!editor.size) editor.size = "small";
editor.section = "member";
editor.treeAlias = "member";
open(editor);
@@ -148,7 +148,7 @@ angular.module('umbraco.services')
break;
case 1:
//info
this.success(args.header, args.message);
this.info(args.header, args.message);
break;
case 2:
//error
@@ -297,4 +297,4 @@ angular.module('umbraco.services')
};
return service;
});
});
@@ -488,7 +488,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
* @methodOf umbraco.services.tinyMceService
*
* @description
* Creates the umbrco insert embedded media tinymce plugin
* Creates the umbraco insert embedded media tinymce plugin
*
* @param {Object} editor the TinyMCE editor instance
*/
@@ -575,7 +575,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
* @methodOf umbraco.services.tinyMceService
*
* @description
* Creates the umbrco insert media tinymce plugin
* Creates the umbraco insert media tinymce plugin
*
* @param {Object} editor the TinyMCE editor instance
*/
@@ -705,7 +705,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
* @methodOf umbraco.services.tinyMceService
*
* @description
* Creates the insert umbrco macro tinymce plugin
* Creates the insert umbraco macro tinymce plugin
*
* @param {Object} editor the TinyMCE editor instance
*/
@@ -147,7 +147,10 @@
group.groupOrder = item.groupOrder
}
groupExists = true;
group.tours.push(item)
if(item.hidden === false){
group.tours.push(item);
}
}
});
@@ -157,8 +160,11 @@
if(item.groupOrder) {
newGroup.groupOrder = item.groupOrder
}
newGroup.tours.push(item);
groupedTours.push(newGroup);
if(item.hidden === false){
newGroup.tours.push(item);
groupedTours.push(newGroup);
}
}
});
@@ -1,5 +1,5 @@
angular.module('umbraco.services')
.factory('userService', function ($rootScope, eventsService, $q, $location, requestRetryQueue, authResource, $timeout, angularHelper) {
.factory('userService', function ($rootScope, eventsService, $q, $location, requestRetryQueue, authResource, emailMarketingResource, $timeout, angularHelper) {
var currentUser = null;
var lastUserId = null;
@@ -262,6 +262,11 @@ angular.module('umbraco.services')
/** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */
setUserTimeout: function (newTimeout) {
setUserTimeoutInternal(newTimeout);
},
/** Calls out to a Remote Azure Function to deal with email marketing service */
addUserToEmailMarketing: (user) => {
return emailMarketingResource.postAddUserToEmailMarketing(user);
}
};
+27 -3
View File
@@ -1,6 +1,6 @@
/** Executed when the application starts, binds to events and set global state */
app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService',
function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService) {
app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService', 'appState', 'assetsService', 'eventsService', '$cookies', 'tourService', 'localStorageService',
function ($rootScope, $route, $location, urlHelper, navigationService, appState, assetsService, eventsService, $cookies, tourService, localStorageService) {
//This sets the default jquery ajax headers to include our csrf token, we
// need to user the beforeSend method because our token changes per user/login so
@@ -23,11 +23,35 @@ app.run(['$rootScope', '$route', '$location', 'urlHelper', 'navigationService',
appReady(data);
tourService.registerAllTours().then(function () {
// Auto start intro tour
// Start intro tour
tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) {
// start intro tour if it hasn't been completed or disabled
if (introTour && introTour.disabled !== true && introTour.completed !== true) {
tourService.startTour(introTour);
localStorageService.set("introTourShown", true);
}
else {
const introTourShown = localStorageService.get("introTourShown");
if(!introTourShown){
// Go & show email marketing tour (ONLY when intro tour is completed or been dismissed)
tourService.getTourByAlias("umbEmailMarketing").then(function (emailMarketingTour) {
// Only show the email marketing tour one time - dismissing it or saying no will make sure it never appears again
// Unless invoked from tourService JS Client code explicitly.
// Accepted mails = Completed and Declicned mails = Disabled
if (emailMarketingTour && emailMarketingTour.disabled !== true && emailMarketingTour.completed !== true) {
// Only show the email tour once per logged in session
// The localstorage key is removed on logout or user session timeout
const emailMarketingTourShown = localStorageService.get("emailMarketingTourShown");
if(!emailMarketingTourShown){
tourService.startTour(emailMarketingTour);
localStorageService.set("emailMarketingTourShown", true);
}
}
});
}
}
});
});
@@ -1,3 +1,7 @@
*:focus {
outline-color: @ui-outline;
}
.umb-outline {
&:focus {
outline:none;
@@ -10,7 +14,28 @@
left: 0;
right: 0;
border-radius: 3px;
box-shadow: 0 0 2px @blueMid, inset 0 0 2px 1px @blueMid;
box-shadow: 0 0 2px 0px @ui-outline, inset 0 0 2px 2px @ui-outline;
}
}
&.umb-outline--surrounding {
&:focus {
.tabbing-active &::after {
top: -6px;
bottom: -6px;
left: -6px;
right: -6px;
border-radius: 9px;
}
}
}
&.umb-outline--thin {
&:focus {
.tabbing-active &::after {
box-shadow: 0 0 2px @ui-outline, inset 0 0 2px 1px @ui-outline;
}
}
}
}
@@ -132,6 +132,7 @@
@import "components/umb-content-grid.less";
@import "components/umb-contextmenu.less";
@import "components/umb-layout-selector.less";
@import "components/umb-mini-search.less";
@import "components/tooltip/umb-tooltip.less";
@import "components/tooltip/umb-tooltip-list.less";
@import "components/overlays/umb-overlay-backdrop.less";
@@ -140,6 +141,7 @@
@import "components/umb-empty-state.less";
@import "components/umb-property-editor.less";
@import "components/umb-property-actions.less";
@import "components/umb-code-snippet.less";
@import "components/umb-color-swatches.less";
@import "components/check-circle.less";
@import "components/umb-file-icon.less";
@@ -192,6 +194,8 @@
@import "components/contextdialogs/umb-dialog-datatype-delete.less";
@import "components/umbemailmarketing.less";
// Utilities
@import "utilities/layout/_display.less";
+14 -24
View File
@@ -23,8 +23,7 @@
border-radius: 3px;
// Hover/focus state
&:hover,
&:focus {
&:hover {
background: @btnBackgroundHighlight;
color: @gray-4;
background-position: 0 -15px;
@@ -35,11 +34,6 @@
.transition(background-position .1s linear);
}
// Focus state for keyboard and accessibility
&:focus {
.tab-focus();
}
// Active state
&.active,
&:active {
@@ -54,7 +48,7 @@
&:disabled:hover {
cursor: default;
border-color: @btnBorder;
.opacity(65);
.opacity(80);
.box-shadow(none);
}
@@ -219,7 +213,7 @@ input[type="button"] {
}
// Made for Umbraco, 2019, used for buttons that has to stand back.
.btn-white {
.buttonBackground(@btnWhiteBackground, @btnWhiteBackgroundHighlight, @btnWhiteType, @btnWhiteTypeHover);
.buttonBackground(@btnWhiteBackground, @btnWhiteBackgroundHighlight, @btnWhiteType, @btnWhiteTypeHover, @gray-10, @gray-7);
}
// Inverse appears as dark gray
.btn-inverse {
@@ -230,8 +224,7 @@ input[type="button"] {
.buttonBackground(@btnNeutralBackground, @btnNeutralBackgroundHighlight);
color: @gray-5;
// Hover/focus state
&:hover,
&:focus {
&:hover {
color: @gray-5;
}
@@ -261,18 +254,18 @@ input[type="button"] {
.btn-outline {
border: 1px solid;
border-color: @gray-7;
background: @white;
background: transparent;
color: @blueExtraDark;
padding: 5px 13px;
transition: all .2s linear;
transition: border-color .12s linear, color .12s linear;
font-weight: 600;
}
.btn-outline:hover,
.btn-outline:focus,
.btn-outline:active {
.btn-outline:hover {
border-color: @ui-light-type-hover;
color: @ui-light-type-hover;
background: @white;
background: transparent;
transition: border-color .12s linear, color .12s linear;
}
// Cross-browser Jank
@@ -309,14 +302,12 @@ input[type="submit"].btn {
color: @linkColor;
.border-radius(0);
}
.btn-link:hover,
.btn-link:focus {
.btn-link:hover {
color: @linkColorHover;
text-decoration: underline;
background-color: transparent;
}
.btn-link[disabled]:hover,
.btn-link[disabled]:focus {
.btn-link[disabled]:hover {
color: @gray-4;
text-decoration: none;
}
@@ -324,8 +315,7 @@ input[type="submit"].btn {
// Make a reverse type of a button link
.btn-link-reverse{
text-decoration:underline;
&:hover,
&:focus{
&:hover {
text-decoration:none;
}
}
@@ -362,7 +352,7 @@ input[type="submit"].btn {
outline: 0;
-webkit-appearance: none;
&:hover, &:focus {
&:hover {
color: @ui-icon-hover;
}
}
@@ -86,6 +86,7 @@
}
.umb-help-badge__title {
display: block;
font-size: 15px;
font-weight: bold;
color: @black;
@@ -160,6 +161,9 @@
border-radius: 0;
border-bottom: 1px solid @gray-9;
padding: 10px;
background: transparent;
width:100%;
border: 0 none;
}
.umb-help-list-item:last-child {
@@ -19,13 +19,13 @@
box-sizing: border-box;
color: @ui-option-type;
width: 100%;
outline-offset: -3px;
}
.umb-language-picker__expand {
font-size: 14px;
}
.umb-language-picker__toggle:focus,
.umb-language-picker__toggle:hover {
background: @ui-option-hover;
color:@ui-option-type-hover;
@@ -54,10 +54,10 @@
font-size: 14px;
width: 100%;
text-align: left;
outline-offset: -3px;
}
.umb-language-picker__dropdown-item:hover,
.umb-language-picker__dropdown-item:focus {
.umb-language-picker__dropdown-item:hover {
background: @ui-option-hover;
text-decoration: none;
color:@ui-option-type-hover;
@@ -112,3 +112,24 @@
.umb-tour-is-visible .umb-backdrop {
z-index: @zindexTourBackdrop;
}
.umb-tour__popover .underline{
font-size: 13px;
background: transparent;
border: none;
padding: 0;
}
.umb-tour__popover--promotion {
width: 800px;
min-height: 400px;
padding: 40px;
border-radius: @baseBorderRadius * 2;
.umb-tour-step__close {
top: 40px;
right: 40px;
}
a {
text-decoration: underline;
}
}
@@ -8,21 +8,6 @@
position: relative;
}
.umb-button__button:focus {
outline: none;
.tabbing-active &:after {
content: '';
position: absolute;
z-index: 10000;
top: 0;
bottom: 0;
left: 0;
right: 0;
border-radius: 3px;
box-shadow: 0 0 2px @blueMid, inset 0 0 2px 1px @blueMid;
}
}
.umb-button__content {
opacity: 1;
transition: opacity 0.25s ease;
@@ -164,6 +164,7 @@ a.umb-editor-header__close-split-view:hover {
/* variant switcher */
.umb-variant-switcher__toggle {
position: relative;
display: flex;
align-items: center;
padding: 0 10px;
@@ -173,6 +174,8 @@ a.umb-editor-header__close-split-view:hover {
text-decoration: none !important;
font-size: 13px;
color: @ui-action-discreet-type;
background: transparent;
border: none;
max-width: 50%;
white-space: nowrap;
@@ -185,7 +188,7 @@ a.umb-editor-header__close-split-view:hover {
}
}
a.umb-variant-switcher__toggle {
button.umb-variant-switcher__toggle {
transition: color 0.2s ease-in-out;
&:hover {
//background-color: @gray-10;
@@ -242,8 +245,7 @@ a.umb-variant-switcher__toggle {
border-left: 4px solid @ui-active;
}
.umb-variant-switcher__item:hover,
.umb-variant-switcher__item:focus {
.umb-variant-switcher__item:hover {
outline: none;
}
@@ -267,7 +269,7 @@ a.umb-variant-switcher__toggle {
align-items: center;
justify-content: center;
margin-left: 5px;
top: -6px;
top: -3px;
width: 14px;
height: 14px;
border-radius: 7px;
@@ -285,8 +287,10 @@ a.umb-variant-switcher__toggle {
flex: 1;
cursor: pointer;
padding-top: 6px !important;
padding-bottom: 6px !important;
border-left: 2px solid transparent;
padding-bottom: 6px !important;
background-color: transparent;
border: none;
border-left: 2px solid transparent;
}
.umb-variant-switcher__name {
@@ -24,8 +24,9 @@
.umb-editor-sub-header.--state-selection {
padding-left: 10px;
padding-right: 10px;
background-color: @pinkLight;
border-color: @pinkLight;
background-color: @ui-selected-border;
border-color: @ui-selected-border;
color: @white;
border-radius: 3px;
}
@@ -4,16 +4,16 @@
width: auto;
margin-top:1px;
.umb-tree-item__label {
user-select: none;
}
&:hover .umb-tree-item__arrow {
visibility: visible;
cursor: pointer
}
}
.umb-tree-item__label {
user-select: none;
}
.umb-tree-item__arrow {
position: relative;
margin-left: -16px;
@@ -92,18 +92,6 @@
color: @blue;
}
.umb-options {
&:hover i {
opacity: .7;
}
i {
background: @ui-active-type;
transition: opacity 120ms ease;
}
}
a,
.umb-tree-icon,
.umb-tree-item__arrow {
@@ -99,6 +99,7 @@ body.touch .umb-tree {
.umb-tree-item__inner {
border: 2px solid transparent;
overflow: visible;
}
.umb-tree-header {
@@ -176,9 +177,25 @@ body.touch .umb-tree {
cursor: pointer;
border-radius: @baseBorderRadius;
&:hover {
background: @btnBackgroundHighlight;
i {
height: 5px !important;
width: 5px !important;
border-radius: 20px;
display: inline-block;
margin: 0 2px 0 0;
background: @ui-active-type;
&:last-child {
margin: 0;
}
}
&:hover {
background: rgba(255, 255, 255, .5);
i {
background: @ui-active-type-hover;
}
}
// NOTE - We're having to repeat ourselves here due to an .sr-only class appearing in umbraco/lib/font-awesome/css/font-awesome.min.css
&.sr-only--hoverable:hover,
&.sr-only--focusable:focus {
@@ -193,19 +210,6 @@ body.touch .umb-tree {
border-radius: 3px;
}
i {
height: 5px !important;
width: 5px !important;
border-radius: 20px;
background: @black;
display: inline-block;
margin: 0 2px 0 0;
&:last-child {
margin: 0;
}
}
.hide-options & {
display: none !important;
}
@@ -289,9 +293,8 @@ body.touch .umb-tree {
}
.no-access {
.umb-tree-icon,
.root-link,
.umb-tree-item__label {
> .umb-tree-item__inner .umb-tree-icon,
> .umb-tree-item__inner .umb-tree-item__label {
color: @gray-7;
cursor: not-allowed;
}
@@ -4,6 +4,7 @@
margin-left: 0;
display: flex;
flex-wrap: wrap;
user-select: none;
}
.umb-breadcrumbs__ancestor {
@@ -12,10 +13,23 @@
}
.umb-breadcrumbs__action {
position: relative;
background: transparent;
border: 0 none;
padding: 0;
margin-top: -4px;
border-radius: 3px;
padding: 0 4px;
color: @ui-option-type;
&.--current {
font-weight: bold;
pointer-events: none;
}
&:hover {
color: @ui-option-type-hover;
background-color: @white;
}
}
.umb-breadcrumbs__ancestor-link,
@@ -26,6 +40,7 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 4px;
}
.umb-breadcrumbs__ancestor-link {
@@ -39,13 +54,13 @@
.umb-breadcrumbs__separator {
position: relative;
top: 1px;
margin-left: 5px;
margin-right: 5px;
margin: 0 1px;
margin-top: -3px;
color: @gray-7;
}
input.umb-breadcrumbs__add-ancestor {
height: 25px;
margin: 0 0 0 3px;
height: 24px;
margin: -2px 0 -2px 3px;
width: 100px;
}
@@ -2,21 +2,30 @@
border: 2px solid @white;
width: 25px;
height: 25px;
border: 1px solid @gray-7;
border: 1px solid @ui-action-discreet-border;
border-radius: 3px;
box-sizing: border-box;
display: flex;
justify-content: center;
align-items: center;
color: @gray-7;
color: @ui-selected-type;
cursor: pointer;
font-size: 15px;
&:hover {
border-color:@ui-action-discreet-border-hover;
color: @ui-selected-type-hover;
}
}
.umb-checkmark--checked {
background: @ui-active;
border-color: @ui-active;
background: @ui-selected-border;
border-color: @ui-selected-border;
color: @white;
&:hover {
background: @ui-selected-border-hover;
border-color: @ui-selected-border-hover;
color: @white;
}
}
.umb-checkmark--xs {
@@ -45,4 +54,4 @@
width: 50px;
height: 50px;
font-size: 20px;
}
}
@@ -0,0 +1,43 @@
.umb-code-snippet {
.umb-code-snippet__header {
box-sizing: content-box;
background-color: @gray-10;
display: flex;
flex-direction: row;
font-size: .8rem;
border: 1px solid @gray-8;
border-radius: 3px 3px 0 0;
border-bottom: 0;
margin-top: 16px;
min-height: 30px;
.language {
display: flex;
align-items: center;
justify-content: flex-start;
flex-grow: 1;
padding: 2px 10px;
}
button {
background-color: transparent;
border: none;
border-left: 1px solid @gray-8;
border-radius: 0;
color: #000;
&:hover {
background-color: @grayLighter;
}
}
}
.umb-code-snippet__content {
pre {
border-radius: 0 0 3px 3px;
overflow: auto;
white-space: nowrap;
}
}
}
@@ -29,15 +29,15 @@
border-radius: 5px;
box-shadow: 0 0 4px 0 darken(@ui-selected-border, 20), inset 0 0 2px 0 darken(@ui-selected-border, 20);
pointer-events: none;
transition: opacity 100ms;
}
}
}
.umb-content-grid__item:hover {
&::before {
opacity: .33;
opacity: .2;
}
}
.umb-content-grid__item.-selected:hover {
@@ -46,6 +46,7 @@
}
}
.umb-content-grid__icon-container {
height: 75px;
display: flex;
@@ -66,8 +67,10 @@
}
.umb-content-grid__item-name {
position: relative;
padding: 5px;
margin: -5px -5px 15px -5px;
font-weight: bold;
margin-bottom: 15px;
line-height: 1.4em;
display: inline-flex;
@@ -4,6 +4,7 @@
&__action,
> a {
position: relative;
background: transparent;
text-align: center;
cursor: pointer;
@@ -18,26 +19,20 @@
align-items: center;
justify-content: center;
height: calc(~'@{editorHeaderHeight}'- ~'1px'); // need to offset the 1px border-bottom on .umb-editor-header - avoids overflowing top of the container
position: relative;
color: @ui-active-type;
&:focus,
&:hover {
color: @ui-active-type-hover !important;
text-decoration: none;
}
&:focus {
outline: none;
}
&::after {
&::before {
content: "";
position: absolute;
height: 0px;
left: 8px;
right: 8px;
background-color: @ui-light-active-border;
position: absolute;
bottom: 0;
border-radius: 3px 3px 0 0;
opacity: 0;
@@ -47,14 +42,13 @@
&.is-active {
color: @ui-light-active-type;
&::after {
&::before {
opacity: 1;
height: 4px;
}
}
}
&__action:focus,
&__action:active,
& > a:active {
.box-shadow(~"inset 0 2px 4px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.05)");
@@ -111,7 +105,6 @@
&__anchor_dropdown {
// inherits from .dropdown-menu
margin: 0;
overflow: hidden;
// center align horizontal
left: 50%;
@@ -122,7 +115,7 @@
li {
&.is-active a {
border-left-color: @ui-selected-border;
border-left-color: @ui-active;
}
a {
@@ -192,4 +185,4 @@
&::after {
background-color: @red;
}
}
}
@@ -41,12 +41,17 @@
margin-top: 10px;
}
button.umb-grid-selector__item {
width: 169px;
height: 194px;
}
.umb-grid-selector__item-icon {
font-size: 50px;
color: @gray-8;
display: block;
line-height: 50px;
margin-bottom: 15px;
font-size: 50px;
color: @gray-8;
display: block;
line-height: 50px;
margin-bottom: 15px;
}
.umb-grid-selector__item-label {
@@ -6,7 +6,8 @@
.umb-layout-selector__active-layout {
background: transparent;
box-sizing: border-box;
border: 1px solid @inputBorder;
border: 1px solid @ui-action-discreet-border;
color: @ui-action-discreet-type;
cursor: pointer;
height: 30px;
width: 30px;
@@ -17,7 +18,8 @@
}
.umb-layout-selector__active-layout:hover {
border-color: @inputBorderFocus;
border-color: @ui-action-discreet-border-hover;
color: @ui-action-discreet-type-hover;
}
.umb-layout-selector__dropdown {
@@ -31,6 +33,7 @@
flex-direction: column;
transform: translate(-50%,0);
left: 50%;
border-radius: 3px;
}
.umb-layout-selector__dropdown-item {
@@ -46,11 +49,11 @@
}
.umb-layout-selector__dropdown-item:hover {
border: 1px solid @gray-8;
border: 1px solid @ui-action-discreet-border;
}
.umb-layout-selector__dropdown-item.-active {
border: 1px solid @blue;
border: 1px solid @ui-action-discreet-border-hover;
}
.umb-layout-selector__dropdown-item-icon,
@@ -40,25 +40,41 @@
}
}
.umb-media-grid__item.-selectable {
.umb-media-grid__item.-selectable,
.umb-media-grid__item.-folder {// If folders isnt selectable, they opens if clicked, therefor...
cursor: pointer;
.tabbing-active &:focus {
outline: 2px solid @inputBorderTabFocus;
}
}
.umb-media-grid__item.-file {
background-color: @white;
}
.umb-media-grid__item.-folder {
&.-selectable {
.media-grid-item-edit:hover .umb-media-grid__item-name,
.media-grid-item-edit:focus .umb-media-grid__item-name {
text-decoration: underline;
}
}
&.-unselectable {
&:hover, &:focus {
.umb-media-grid__item-name {
text-decoration: underline;
}
}
}
}
.umb-media-grid__item.-selected {
color:@ui-selected-type;
.umb-media-grid__item-overlay {
color: @ui-selected-type;
}
}
.umb-media-grid__item.-selected,
.umb-media-grid__item.-selected,
.umb-media-grid__item.-selectable:hover {
&::before {
content: "";
@@ -139,10 +155,10 @@
background: fade(@white, 92%);
transition: opacity 150ms;
&:hover {
&.-can-open:hover {
text-decoration: underline;
}
.tabbing-active &:focus {
opacity: 1;
}
@@ -190,7 +206,7 @@
align-items: center;
color: @black;
transition: opacity 150ms;
&:hover {
color: @ui-action-discreet-type-hover;
}
@@ -0,0 +1,44 @@
.umb-mini-search {
position: relative;
display: block;
.icon {
position: absolute;
padding: 5px 8px;
pointer-events: none;
top: 2px;
color: @ui-action-discreet-type;
transition: color .1s linear;
}
input {
width: 0px;
padding-left:24px;
margin-bottom: 0px;
background-color: transparent;
border-color: @ui-action-discreet-border;
transition: background-color .1s linear, border-color .1s linear, color .1s linear, width .1s ease-in-out, padding-left .1s ease-in-out;
}
&:focus-within, &:hover {
.icon {
color: @ui-action-discreet-type-hover;
}
input {
color: @ui-action-discreet-border-hover;
border-color: @ui-action-discreet-border-hover;
}
}
input:focus, &:focus-within input {
background-color: white;
color: @ui-action-discreet-border-hover;
border-color: @ui-action-discreet-border-hover;
}
input:focus, &:focus-within input, &.--has-value input {
width: 190px;
padding-left:30px;
}
}
@@ -41,11 +41,8 @@
}
.umb-nested-content__item.ui-sortable-placeholder {
background: @gray-10;
border: 1px solid @gray-9;
margin-top: 1px;
visibility: visible !important;
height: 55px;
margin-top: -1px;
}
.umb-nested-content__item--single > .umb-nested-content__content {
@@ -5,6 +5,7 @@
.umb-progress-circle__view-box {
position: absolute;
transform: rotate(-90deg);
right: 0;
}
// circle highlight on progressbar
@@ -8,7 +8,6 @@
padding-left: 0;
padding-right: 0;
&:focus {
outline: none;
text-decoration: none;
}
}
@@ -34,8 +34,12 @@
text-decoration: none;
padding: 0;
margin-left: 1px;
body:not(.tabbing-active) & {
outline: 0;
}
}
input.umb-table__input {
margin: 0 auto;
}
@@ -47,6 +51,8 @@ input.umb-table__input {
.umb-table-head {
font-size: 14px;
font-weight: bold;
color: @ui-disabled-type;
}
.umb-table-head__link {
@@ -68,10 +74,12 @@ input.umb-table__input {
.umb-table-head__link.sortable {
cursor: pointer;
color: @ui-action-discreet-type;
&:hover {
text-decoration: none;
color: @black;
color: @ui-action-discreet-type-hover;
}
outline-offset: 1px;
}
.umb-table-thead__icon {
@@ -129,6 +137,9 @@ input.umb-table__input {
&::before {
opacity:.66;
}
.umb-table-body__checkicon {
color: @ui-selected-border;
}
}
}
@@ -141,21 +152,19 @@ input.umb-table__input {
}
.umb-table-body__link {
position: relative;
color: @ui-option-type;
font-size: 14px;
font-weight: bold;
text-decoration: none;
&:hover, &:focus {
&:hover {
color: @ui-option-type-hover;
text-decoration: underline;
outline: none;
}
}
.umb-table-body__icon,
.umb-table-body__icon[class^="icon-"],
.umb-table-body__icon[class*=" icon-"] {
.umb-table-body__icon {
margin: 0 auto;
font-size: 20px;
line-height: 20px;
@@ -164,13 +173,11 @@ input.umb-table__input {
text-decoration: none;
}
.umb-table-body__checkicon,
.umb-table-body__checkicon[class^="icon-"],
.umb-table-body__checkicon[class*=" icon-"] {
.umb-table-body__checkicon {
display: none;
font-size: 18px;
line-height: 20px;
color: @green;
color: @ui-selected-border;
}
.umb-table-body .umb-table__name {
@@ -179,7 +186,8 @@ input.umb-table__input {
font-weight: bold;
a {
color: @ui-option-type;
&:hover, &:focus {
outline-offset: 1px;
&:hover {
color: @ui-option-type-hover;
text-decoration: underline;
}
@@ -249,8 +257,8 @@ input.umb-table__input {
flex-flow: row nowrap;
flex: 1 1 5%;
position: relative;
margin: auto 14px;
padding: 6px 2px;
margin: auto 0;
padding: 6px 16px;
text-align: left;
overflow:hidden;
}
@@ -268,8 +276,8 @@ input.umb-table__input {
.umb-table-cell:first-of-type:not(.not-fixed) {
flex: 0 0 25px;
margin: 0 0 0 15px;
padding: 15px 0;
margin: 0;
padding: 15px 0 15px 15px;
}
.umb-table-cell--auto-width {
@@ -0,0 +1,44 @@
.umb-email-marketing {
h2 {
font-weight: 800;
max-width: 26ex;
margin-top: 20px;
}
.layout {
display: flex;
align-items: center;
align-content: stretch;
.primary {
flex-basis: 50%;
padding-right: 40px;
padding-top: 20px;
padding-bottom: 20px;
.notice {
color: @gray-5;
font-style: italic;
a {
color: @gray-5;
&:hover {
color: @ui-action-type-hover;
}
}
}
}
.secondary {
flex-basis: 50%;
svg {
height: 200px;
width: 100%;
margin-top: -60px;
}
}
}
.cta {
text-align: right;
}
}
@@ -7,11 +7,17 @@
display: flex;
margin-bottom: 5px;
padding: 10px;
position: relative;
}
.umb-user-group-picker-list-item:active,
.umb-user-group-picker-list-item:focus {
text-decoration: none;
.umb-user-group-picker__action{
background: transparent;
border: 0 none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.umb-user-group-picker-list-item:hover {
@@ -35,4 +41,4 @@
.umb-user-group-picker-list-item__permission {
font-size: 13px;
color: @gray-4;
}
}
@@ -252,7 +252,7 @@ input[type="color"],
outline: 0;
.tabbing-active & {
outline: 2px solid @inputBorderTabFocus;
outline: 2px solid @ui-outline;
}
}
}
@@ -297,11 +297,11 @@ select[size] {
}
// Focus for select, file, radio, and checkbox
select:focus,
input[type="file"]:focus,
input[type="radio"]:focus,
input[type="checkbox"]:focus {
.tab-focus();
select,
input[type="file"],
input[type="radio"],
input[type="checkbox"] {
.umb-outline();
}
+25 -27
View File
@@ -185,40 +185,38 @@ iframe, .content-column-body {
// Inline code
// 1: Revert border radius to match look and feel of 7.4+
code{
.border-radius(@baseBorderRadius); // 1
code {
.border-radius(@baseBorderRadius); // 1
}
// Blocks of code
// 1: Wrapping code is unreadable on small devices.
pre {
display: block;
padding: (@baseLineHeight - 1) / 2;
margin: 0 0 @baseLineHeight / 2;
font-family: @sansFontFamily;
//font-size: @baseFontSize - 1; // 14px to 13px
color: @gray-2;
line-height: @baseLineHeight;
white-space: pre-wrap; // 1
overflow-x: auto; // 1
background-color: @gray-10;
border: 1px solid @gray-8;
.border-radius(@baseBorderRadius);
display: block;
padding: (@baseLineHeight - 1) / 2;
margin: 0 0 @baseLineHeight / 2;
font-family: @sansFontFamily;
color: @gray-2;
line-height: @baseLineHeight;
white-space: pre-wrap; // 1
overflow-x: auto; // 1
background-color: @brownGrayLight;
border: 1px solid @gray-8;
.border-radius(@baseBorderRadius);
// Make prettyprint styles more spaced out for readability
&.prettyprint {
margin-bottom: @baseLineHeight;
}
// Make prettyprint styles more spaced out for readability
&.prettyprint {
margin-bottom: @baseLineHeight;
}
// Account for some code outputs that place code tags in pre tags
code {
padding: 0;
white-space: pre; // 1
word-wrap: normal; // 1
background-color: transparent;
border: 0;
}
// Account for some code outputs that place code tags in pre tags
code {
padding: 0;
white-space: pre; // 1
word-wrap: normal; // 1
background-color: transparent;
border: 0;
}
}
/* Styling for content/media sort order dialog */
@@ -3,6 +3,7 @@
@import "variables.less"; // Modify this for custom colors, font-sizes, etc
@import "colors.less";
@import "mixins.less";
@import "application/umb-outline.less";
@import "buttons.less";
@import "forms.less";
@@ -1,6 +1,10 @@
// Listview
// -------------------------
.umb-listview {
min-height: 100px;
}
.umb-listview table {
border: 1px solid @gray-8;
}
@@ -43,6 +47,15 @@
/* add padding */
.left-addon input[type="text"] { padding-left: 30px !important; padding-right: 6px; }
.right-addon input[type="text"] { padding-right: 30px; padding-left: 6px !important; }
&__label-icon{
width: 30px;
height: 30px;
position: absolute;
top: -1px;
left:0;
margin:0
}
}
.umb-listview table form {
@@ -136,7 +149,36 @@
/* TEMP */
.umb-minilistview {
.umb-table-row.not-allowed { opacity: 0.6; cursor: not-allowed; }
.umb-table-row.not-allowed {
opacity: 0.6;
cursor: not-allowed;
}
div.umb-mini-list-view__breadcrumb {
margin-bottom: 10px;
}
div.no-display {
display: none
}
div.umb-table-cell-padding {
padding-top: 8px;
padding-bottom: 8px;
}
div.umb-table-cell .form-search {
width: 100%;
margin-right: 0;
input {
width: 100%;
}
.icon-search {
font-size: 14px;
}
}
}
.umb-listview .table-striped tbody td {
@@ -117,6 +117,12 @@ h5.-black {
}
.umb-control-group {
position: relative;
&.umb-control-group__listview {
// position: relative messes up the listview status messages (e.g. "no search results")
position: unset;
}
&::after {
content: '';
display:block;
@@ -30,7 +30,6 @@
outline: thin dotted @gray-3;
// Webkit
outline: 5px auto -webkit-focus-ring-color;
outline-offset: -2px;
}
// Center-align a block level element
@@ -435,7 +434,7 @@
// Button backgrounds
// ------------------
.buttonBackground(@startColor, @hoverColor: @startColor, @textColor: @white, @textColorHover: @textColor) {
.buttonBackground(@startColor, @hoverColor: @startColor, @textColor: @white, @textColorHover: @textColor, @disabledColor: @sand-1, @disabledTextColor: @white) {
color: @textColor;
border-color: @startColor @startColor darken(@startColor, 15%);
@@ -449,14 +448,14 @@
}
// in these cases the gradient won't cover the background, so we override
&:hover, &:focus, &:active, &.active {
&:hover {
color: @textColorHover;
background-color: @hoverColor;
}
&.disabled, &[disabled] {
color: @white;
background-color: @sand-1;
background-color: @disabledColor;
color: @disabledTextColor;
}
}
+15 -25
View File
@@ -233,11 +233,14 @@
}
.dropdown-menu > li > a {
position: relative;
padding: 8px 20px;
color: @ui-option-type;
text-decoration: none;
}
.dropdown-menu > li > button {
position: relative;
background: transparent;
border: 0;
padding: 8px 20px;
@@ -253,11 +256,9 @@
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus,
.dropdown-menu > li > button:hover,
.dropdown-menu > li > button:focus,
.dropdown-submenu:hover > a,
.dropdown-submenu:focus > a {
.dropdown-submenu:hover > button {
color: @ui-option-type-hover;
background: @ui-option-hover;
}
@@ -300,8 +301,7 @@
// Active:hover/:focus dropdown links
// -------------------------
.nav > .dropdown.active > a:hover,
.nav > .dropdown.active > a:focus {
.nav > .dropdown.active > a:hover {
cursor: pointer;
}
@@ -309,24 +309,21 @@
// -------------------------
.nav-tabs .open .dropdown-toggle,
.nav-pills .open .dropdown-toggle,
.nav > li.dropdown.open.active > a:hover,
.nav > li.dropdown.open.active > a:focus {
.nav > li.dropdown.open.active > a:hover {
/*color: @white;*/
background-color: @gray-8;
border-color: @gray-8;
}
.nav li.dropdown.open .caret,
.nav li.dropdown.open.active .caret,
.nav li.dropdown.open a:hover .caret,
.nav li.dropdown.open a:focus .caret {
.nav li.dropdown.open a:hover .caret {
border-top-color: @white;
border-bottom-color: @white;
.opacity(100);
}
// Dropdowns in stacked tabs
.tabs-stacked .open > a:hover,
.tabs-stacked .open > a:focus {
.tabs-stacked .open > a:hover {
border-color: @gray-8;
}
@@ -377,15 +374,13 @@
}
.tabs-below > .nav-tabs > li > a {
.border-radius(0 0 4px 4px);
&:hover,
&:focus {
&:hover {
border-bottom-color: transparent;
border-top-color: @gray-8;
}
}
.tabs-below > .nav-tabs > .active > a,
.tabs-below > .nav-tabs > .active > a:hover,
.tabs-below > .nav-tabs > .active > a:focus {
.tabs-below > .nav-tabs > .active > a:hover {
border-color: transparent @gray-8 @gray-8 @gray-8;
}
@@ -414,13 +409,11 @@
margin-right: -1px;
.border-radius(4px 0 0 4px);
}
.tabs-left > .nav-tabs > li > a:hover,
.tabs-left > .nav-tabs > li > a:focus {
.tabs-left > .nav-tabs > li > a:hover {
border-color: @gray-10 @gray-8 @gray-10 @gray-10;
}
.tabs-left > .nav-tabs .active > a,
.tabs-left > .nav-tabs .active > a:hover,
.tabs-left > .nav-tabs .active > a:focus {
.tabs-left > .nav-tabs .active > a:hover {
border-color: @gray-8 transparent @gray-8 @gray-8;
*border-right-color: @white;
}
@@ -435,13 +428,11 @@
margin-left: -1px;
.border-radius(0 4px 4px 0);
}
.tabs-right > .nav-tabs > li > a:hover,
.tabs-right > .nav-tabs > li > a:focus {
.tabs-right > .nav-tabs > li > a:hover {
border-color: @gray-10 @gray-10 @gray-10 @gray-8;
}
.tabs-right > .nav-tabs .active > a,
.tabs-right > .nav-tabs .active > a:hover,
.tabs-right > .nav-tabs .active > a:focus {
.tabs-right > .nav-tabs .active > a:hover {
border-color: @gray-8 @gray-8 @gray-8 transparent;
*border-left-color: @white;
}
@@ -456,8 +447,7 @@
color: @gray-8;
}
// Nuke hover/focus effects
.nav > .disabled > a:hover,
.nav > .disabled > a:focus {
.nav > .disabled > a:hover {
text-decoration: none;
background-color: transparent;
cursor: default;
@@ -341,6 +341,7 @@
.umb-panel-header-icon {
cursor: pointer;
margin-right: 5px;
margin-top: -6px;
height: 50px;
display: flex;
justify-content: center;
@@ -249,26 +249,11 @@
transition: all 150ms ease-in-out;
&:focus,
&:active,
&:hover {
color: @ui-action-discreet-type-hover;
border-color: @ui-action-discreet-type-hover;
}
&:focus {
outline: none;
.tabbing-active &:after {
content: '';
position: absolute;
z-index: 10000;
top: -6px;
bottom: -6px;
left: -6px;
right: -6px;
border-radius: 3px;
box-shadow: 0 0 2px @blueMid, inset 0 0 2px 1px @blueMid;
}
}
}
.umb-mediapicker .label {
@@ -108,6 +108,7 @@
//@blueLight: #4f89de;
@blue: #2E8AEA;
@blueMid: #2152A3;// updated 2019
@blueMidLight: rgb(99, 174, 236);
@blueDark: #3544b1;// updated 2019
@blueExtraDark: #1b264f;// added 2019
@blueLight: #ADD8E6;
@@ -139,6 +140,9 @@
@ui-option-disabled-type-hover: @gray-5;
@ui-option-disabled-hover: @sand-7;
@ui-disabled-type: @gray-6;
@ui-disabled-border: @gray-6;
//@ui-active: #346ab3;
@ui-active: @pinkLight;
@ui-active-blur: @brownLight;
@@ -149,8 +153,8 @@
@ui-selected-hover: ligthen(@sand-5, 10);
@ui-selected-type: @blueExtraDark;
@ui-selected-type-hover: @blueMid;
@ui-selected-border: @pinkLight;
@ui-selected-border-hover: darken(@pinkLight, 10);
@ui-selected-border: @blueDark;
@ui-selected-border-hover: darken(@blueDark, 10);
@ui-light-border: @pinkLight;
@ui-light-type: @gray-4;
@@ -175,6 +179,8 @@
@ui-action-discreet-border: @gray-7;
@ui-action-discreet-border-hover: @blueMid;
@ui-outline: @blueMidLight;
@type-white: @white;
@type-black: @blueNight;
@@ -255,7 +261,7 @@
// Buttons
// -------------------------
@btnBackground: @gray-9;
@btnBackgroundHighlight: @gray-9;
@btnBackgroundHighlight: @gray-10;
@btnBorder: @gray-9;
@btnPrimaryBackground: @ui-btn-positive;
@@ -293,7 +299,7 @@
@inputBackground: @white;
@inputBorder: @gray-8;
@inputBorderFocus: @gray-7;
@inputBorderTabFocus: @blueExtraDark;
@inputBorderTabFocus: @ui-outline;
@inputBorderRadius: 0;
@inputDisabledBackground: @gray-10;
@formActionsBackground: @gray-9;
@@ -448,7 +454,7 @@
@successBorder: transparent;
@infoText: @white;
@infoBackground: @turquoise-d1;
@infoBackground: @blueDark;
@infoBorder: transparent;
@alertBorderRadius: 0;
@@ -67,13 +67,18 @@ function MainController($scope, $location, appState, treeService, notificationsS
};
var evts = [];
//when a user logs out or timesout
evts.push(eventsService.on("app.notAuthenticated", function (evt, data) {
$scope.authenticated = null;
$scope.user = null;
const isTimedOut = data && data.isTimedOut ? true : false;
$scope.showLoginScreen(isTimedOut);
// Remove the localstorage items for tours shown
// Means that when next logged in they can be re-shown if not already dismissed etc
localStorageService.remove("emailMarketingTourShown");
localStorageService.remove("introTourShown");
}));
evts.push(eventsService.on("app.userRefresh", function(evt) {
@@ -17,17 +17,21 @@
<div class="umb-help-list">
<a href="" class="umb-help-list-item umb-help-list-item__content flex items-center justify-between" ng-click="tourGroup.open = !tourGroup.open">
<h5 class="umb-help-list-item__group-title">
<i ng-class="{'icon-navigation-right': !tourGroup.open, 'icon-navigation-down': tourGroup.open}"></i>
<button
type="button"
class="umb-help-list-item umb-help-list-item__content flex items-center justify-between"
ng-click="tourGroup.open = !tourGroup.open"
aria-expanded="{{tourGroup.open === undefined ? false : tourGroup.open }}">
<span class="umb-help-list-item__group-title bold">
<i ng-class="{'icon-navigation-right': !tourGroup.open, 'icon-navigation-down': tourGroup.open}" aria-hidden="true"></i>
<span ng-if="tourGroup.group !== 'undefined'">{{tourGroup.group}}</span>
<span ng-if="tourGroup.group === 'undefined'">Other</span>
</h5>
</span>
<umb-progress-circle
percentage="{{tourGroup.completedPercentage}}"
size="40">
</umb-progress-circle>
</a>
</button>
<div ng-if="tourGroup.open">
<div data-element="tour-{{tour.alias}}" class="umb-help-list-item" ng-repeat="tour in tourGroup.tours">
@@ -85,9 +89,9 @@
<ul class="umb-help-list">
<li class="umb-help-list-item" ng-repeat="video in vm.videos track by $index">
<a class="umb-help-list-item__content" data-element="help-article-{{video.title}}" target="_blank" ng-href="{{video.link}}?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv">
<i class="umb-help-list-item__icon icon-tv-old"></i>
<i class="umb-help-list-item__icon icon-tv-old" aria-hidden="true"></i>
<span class="umb-help-list-item__title">{{video.title}}</span>
<i class="umb-help-list-item__open-icon icon-out"></i>
<i class="umb-help-list-item__open-icon icon-out" aria-hidden="true"></i>
</a>
</li>
</ul>
@@ -96,7 +100,7 @@
<!-- Links -->
<div class="umb-help-section" data-element="help-links" ng-if="vm.hasAccessToSettings">
<a data-element="help-link-umbraco-tv" class="umb-help-badge" target="_blank" href="https://umbraco.tv?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv">
<i class="umb-help-badge__icon icon-tv-old"></i>
<i class="umb-help-badge__icon icon-tv-old" aria-hidden="true"></i>
<div class="umb-help-badge__title">Visit umbraco.tv</div>
<small>
<localize key="help_theBestUmbracoVideoTutorials">The best Umbraco video tutorials</localize>
@@ -104,7 +108,7 @@
</a>
<a data-element="help-link-our-umbraco" class="umb-help-badge" target="_blank" href="https://our.umbraco.com?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=our">
<i class="umb-help-badge__icon icon-favorite"></i>
<i class="umb-help-badge__icon icon-favorite" aria-hidden="true"></i>
<div class="umb-help-badge__title">Visit our.umbraco.com</div>
<small>
@@ -1,7 +1,7 @@
//used for the media picker dialog
angular.module("umbraco")
.controller("Umbraco.Editors.MediaPickerController",
function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) {
function ($scope, $timeout, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService) {
var vm = this;
@@ -22,7 +22,6 @@ angular.module("umbraco")
vm.clickHandler = clickHandler;
vm.clickItemName = clickItemName;
vm.editMediaItem = editMediaItem;
vm.gotoFolder = gotoFolder;
var dialogOptions = $scope.model;
@@ -37,8 +36,7 @@ angular.module("umbraco")
$scope.cropSize = dialogOptions.cropSize;
$scope.lastOpenedNode = localStorageService.get("umbLastOpenedMediaNodeId");
$scope.lockedFolder = true;
$scope.allowMediaEdit = dialogOptions.allowMediaEdit ? dialogOptions.allowMediaEdit : false;
var userStartNodes = [];
var umbracoSettings = Umbraco.Sys.ServerVariables.umbracoSettings;
@@ -132,7 +130,7 @@ angular.module("umbraco")
// ID of a UDI or legacy int ID still could be null/undefinied here
// As user may dragged in an image that has not been saved to media section yet
if(id){
if (id) {
entityResource.getById(id, "Media")
.then(function (node) {
$scope.target = node;
@@ -144,8 +142,7 @@ angular.module("umbraco")
openDetailsDialog();
}
}, gotoStartNode);
}
else {
} else {
// No ID set - then this is going to be a tmpimg that has not been uploaded
// User editing this will want to be changing the ALT text
openDetailsDialog();
@@ -254,11 +251,14 @@ angular.module("umbraco")
}
}
function clickItemName(item) {
function clickItemName(item, event, index) {
if (item.isFolder) {
gotoFolder(item);
}
}
else {
$scope.clickHandler(item, event, index);
}
};
function selectMedia(media) {
if (!media.selectable) {
@@ -510,30 +510,6 @@ angular.module("umbraco")
}
}
function editMediaItem(item) {
var mediaEditor = {
id: item.id,
submit: function (model) {
editorService.close()
// update the media picker item in the picker so it matched the saved media item
// the media picker is using media entities so we get the
// entity so we easily can format it for use in the media grid
if (model && model.mediaNode) {
entityResource.getById(model.mediaNode.id, "media")
.then(function (mediaEntity) {
angular.extend(item, mediaEntity);
setMediaMetaData(item);
setUpdatedMediaNodes(item);
});
}
},
close: function (model) {
setUpdatedMediaNodes(item);
editorService.close();
}
};
editorService.mediaEditor(mediaEditor);
};
/**
* Called when the umbImageGravity component updates the focal point value
@@ -56,19 +56,19 @@
<div class="row umb-control-group" ng-show="!vm.searchOptions.filter">
<ul class="umb-breadcrumbs">
<li ng-hide="startNodeId != -1" class="umb-breadcrumbs__ancestor">
<button type="button" class="umb-breadcrumbs__action" ng-click="vm.gotoFolder()">
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-click="vm.gotoFolder()" ng-class="{'--current':path.length === 0}">
<localize key="treeHeaders_media">Media</localize>
</button>
<span class="umb-breadcrumbs__separator" aria-hidden="true">&#47;</span>
</li>
<li ng-repeat="item in path" class="umb-breadcrumbs__ancestor">
<button type="button" class="umb-breadcrumbs__action" ng-click="vm.gotoFolder(item)">{{item.name}}</button>
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-click="vm.gotoFolder(item)" ng-class="{'--current':$last}">{{item.name}}</button>
<span class="umb-breadcrumbs__separator" aria-hidden="true">&#47;</span>
</li>
<li class="umb-breadcrumbs__ancestor" ng-show="!lockedFolder">
<button type="button" class="umb-breadcrumbs__action" ng-hide="model.showFolderInput" ng-click="model.showFolderInput = true">
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-hide="model.showFolderInput" ng-click="model.showFolderInput = true">
<i class="icon icon-add small" aria-hidden="true"></i>
<span class="sr-only">
<localize key="visuallyHiddenTexts_createNewFolder">Create new folder</localize>
@@ -105,8 +105,6 @@
items="images"
on-click="vm.clickHandler"
on-click-name="vm.clickItemName"
on-click-edit="vm.editMediaItem(item)"
allow-on-click-edit="{{allowMediaEdit}}"
item-max-width="150"
item-max-height="150"
item-min-width="100"
@@ -115,7 +113,8 @@
only-images={{onlyImages}}
only-folders={{onlyFolders}}
include-sub-folders={{showChilds}}
current-folder-id="{{currentFolder.id}}">
current-folder-id="{{currentFolder.id}}"
allow-open-folder="!disableFolderSelect">
</umb-media-grid>
@@ -31,7 +31,6 @@
vm.datePickerChange = datePickerChange;
vm.submit = submit;
vm.close = close;
vm.copyQuery = copyQuery;
function onInit() {
@@ -121,11 +120,6 @@
query.filters.push({});
}
function copyQuery() {
var copyText = $scope.model.result.queryExpression;
navigator.clipboard.writeText(copyText);
}
function trashFilter(query, filter) {
for (var i = 0; i < query.filters.length; i++) {
if (query.filters[i] == filter) {

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