Merge remote-tracking branch 'origin/v8/dev' into netcore/dev

# Conflicts:
#	src/Umbraco.ModelsBuilder.Embedded/Compose/ModelsBuilderComposer.cs
#	src/Umbraco.ModelsBuilder.Embedded/Configuration/ModelsBuilderConfig.cs
#	src/Umbraco.Web.UI/Umbraco/config/lang/en.xml
#	src/Umbraco.Web.UI/web.Template.Debug.config
#	src/Umbraco.Web/Editors/ContentController.cs
#	src/Umbraco.Web/Editors/DataTypeController.cs
#	src/Umbraco.Web/Editors/MacrosController.cs
#	src/Umbraco.Web/Models/Mapping/ContentPropertyBasicMapper.cs
#	src/Umbraco.Web/PropertyEditors/GridPropertyEditor.cs
This commit is contained in:
Bjarke Berg
2020-01-21 10:18:03 +01:00
162 changed files with 4979 additions and 3284 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);
}
}
}
@@ -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 -1
View File
@@ -49,7 +49,7 @@
</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-19554-01</Version>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -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.
@@ -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.
+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>
@@ -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
+3 -3
View File
@@ -9252,9 +9252,9 @@
"dev": true
},
"nouislider": {
"version": "14.0.2",
"resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.0.2.tgz",
"integrity": "sha512-N4AQStV4frh+XcLUwMI/hZpBP6tRboDE/4LZ7gzfxMVXFi/2J9URphnm40Ff4KEyrAVGSGaWApvljoMzTNWBlA=="
"version": "14.1.1",
"resolved": "https://registry.npmjs.org/nouislider/-/nouislider-14.1.1.tgz",
"integrity": "sha512-3/+Z/pTBoWoJf2YXSEWRmS27LW2XxOBmGEzkPyRzB/J6QvL+0mS3QwcQp0SmWhgO5CMzbSxPmb1lDDD4HP12bg=="
},
"now-and-later": {
"version": "2.0.1",
@@ -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);
@@ -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) {
@@ -113,11 +113,11 @@
</span>
<a href ng-click="vm.addFilter(vm.query)">
<i class="icon-add"></i>
<i class="icon-add" aria-hidden="true"></i>
</a>
<a href ng-click="vm.trashFilter(vm.query, filter)">
<i class="icon-trash"></i>
<i class="icon-trash" aria-hidden="true"></i>
</a>
</div>
@@ -159,14 +159,11 @@
<ul class="nav unstyled">
<li ng-repeat="item in model.result.sampleResults">
<i class="icon icon-document turquoise-d1"></i> {{item.name}}
<i class="icon icon-document turquoise-d1" aria-hidden="true"></i> {{item.name}}
</li>
</ul>
<pre>{{model.result.queryExpression}}</pre>
<a href ng-click="vm.copyQuery()">
<i class="icon-document"></i> <localize key="template_copyToClipboard">copy to clipboard</localize>
</a>
<umb-code-snippet language="'csharp'">{{model.result.queryExpression}}</umb-code-snippet>
</div>
@@ -13,10 +13,10 @@
<umb-editor-container>
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<umb-box>
<umb-box-content>
<div class="form-search" style="margin-bottom: 15px;">
<i class="icon-search"></i>
<input type="text"
@@ -27,26 +27,31 @@
umb-auto-focus
no-dirty-check />
</div>
<div class="umb-user-group-picker-list">
<a href="" class="umb-user-group-picker-list-item" ng-repeat="userGroup in vm.userGroups | filter:searchTerm" ng-click="vm.selectUserGroup(userGroup)">
<div class="umb-user-group-picker-list-item" ng-repeat="userGroup in vm.userGroups | filter:searchTerm">
<button type="button" class="umb-user-group-picker__action" ng-click="vm.selectUserGroup(userGroup)">
<span class="sr-only" ng-if="!userGroup.selected"><localize key="buttons_select">Select</localize> {{userGroup.name}}</span>
<span class="sr-only" ng-if="userGroup.selected">{{userGroup.name}} <localize key="general_selected">Selected</localize></span>
</button>
<div class="umb-user-group-picker-list-item__icon">
<i ng-if="!userGroup.selected" class="{{userGroup.icon}}"></i>
<i ng-if="!userGroup.selected" class="{{userGroup.icon}}" aria-hidden="true"></i>
<umb-checkmark ng-if="userGroup.selected" checked="userGroup.selected" size="xs"></umb-checkmark>
</div>
<div>
<div class="umb-user-group-picker-list-item__name">{{ userGroup.name }}</div>
<div class="umb-user-group-picker-list-item__permission" ng-if="userGroup.sections">
<span>
<span class="bold"><localize key="main_sections">Sections</localize>:</span>
<span ng-repeat="section in userGroup.sections">{{ section.name }}<span ng-if="!$last">, </span></span>
</span>
</div>
<div class="umb-user-group-picker-list-item__permission">
<span>
<span class="bold"><localize key="user_startnode">Content start node</localize>:</span>
@@ -54,7 +59,7 @@
<span ng-if="userGroup.contentStartNode">{{ userGroup.contentStartNode.name }}</span>
</span>
</div>
<div class="umb-user-group-picker-list-item__permission">
<span>
<span class="bold"><localize key="user_mediastartnode">Media start node</localize>:</span>
@@ -63,16 +68,16 @@
</span>
</div>
</div>
</a>
</div>
</div>
<umb-empty-state
ng-if="vm.userGroups.length === 0 && !vm.loading"
position="center">
No user groups have been added
<localize key="user_noUserGroupsAdded">No user groups have been added</localize>
</umb-empty-state>
</umb-box-content>
</umb-box>
@@ -0,0 +1,24 @@
(function () {
"use strict";
function EmailsController($scope, userService) {
var vm = this;
vm.optIn = function() {
// Get the current user in backoffice
userService.getCurrentUser().then(function(user){
// Send this user along to opt in
// It's a fire & forget - not sure we need to check the response
userService.addUserToEmailMarketing(user);
});
// Mark Tour as complete
// This is also can help us indicate that the user accepted
// Where disabled is set if user closes modal or chooses NO
$scope.model.completeTour();
}
}
angular.module("umbraco").controller("Umbraco.Tours.UmbEmailMarketing.EmailsController", EmailsController);
})();
@@ -0,0 +1,26 @@
<div ng-controller="Umbraco.Tours.UmbEmailMarketing.EmailsController as vm" class="umb-email-marketing">
<umb-tour-step on-close="model.endTour()">
<h2>{{ model.currentStep.title }}</h2>
<div class="layout">
<!-- HTML Content stored in tour JSON config file -->
<div class="primary">
<div ng-bind-html="model.currentStep.content"></div>
</div>
<!-- Secondary Column with paperplane -->
<div class="secondary">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 220 1080 640"><defs><style>.cls-1{fill:#fba0a2;}.cls-2{fill:#3544b1;}</style></defs><title>paperplane</title><g id="Layer_54" data-name="Layer 54"><polygon class="cls-1" points="1012.92 370.19 894.84 709.81 832.53 529.1 691.24 457.68 1012.92 370.19"/><polygon class="cls-2" points="1012.92 370.19 832.53 529.1 894.84 709.81 1012.92 370.19"/><path class="cls-2" d="M784.24,590.07s-34.16,66.82-88,70.95l-11.65-32s46.67-12.34,79.51-42.3Z"/><path class="cls-2" d="M633.49,670s-66.81,25.65-94.62,7l-3.5-26s35.92,19.8,79.74-9.67Z"/><path class="cls-2" d="M475.1,673.19S405.19,650.45,383,623.94l4.21-15.08,95.94,33.81Z"/><path class="cls-2" d="M311.51,601.88s-36.8-46.78-77.3-41.65l3.14-21.17s36.46-21.71,82.28,31.89Z"/><path class="cls-2" d="M183.21,562.23S119,565.32,89.29,588.69L67.08,569.22s61.6-41.75,103.69-36.05Z"/></g></svg>
</div>
</div>
<div class="cta">
<umb-button size="m" button-style="link" type="button" label="No thanks" action="model.disableTour()"></umb-button>
<umb-button size="m" button-style="action" type="button" action="vm.optIn()" label="Keep me updated!"></umb-button>
</div>
</umb-tour-step>
</div>
@@ -4,7 +4,7 @@
<div class="umb-tour__pulse"></div>
<div class="umb-tour__popover shadow-depth-2" ng-class="{'umb-tour__popover--l': model.currentStep.type === 'intro' || model.currentStepIndex === model.steps.length}">
<div class="umb-tour__popover shadow-depth-2" ng-class="{'umb-tour__popover--l': model.currentStep.type === 'intro' || model.currentStepIndex === model.steps.length, 'umb-tour__popover--promotion': model.currentStep.type === 'promotion'}">
<div ng-if="!configuredView && !elementNotFound">
@@ -29,7 +29,7 @@
total-steps="model.steps.length">
</umb-tour-step-counter>
<div ng-if="model.allowDisable && model.currentStep.type === 'intro'">
<button type="button" class="underline" ng-click="model.disableTour()" style="font-size: 13px; background: transparent; border: none; padding: 0;" prevent-default>Don't show this tour again</button>
<button type="button" class="underline" ng-click="model.disableTour()" prevent-default>Don't show this tour again</button>
</div>
</div>
@@ -7,7 +7,12 @@
<div ng-if="vm.innerState !== 'init'" class="umb-button__overlay"></div>
</div>
<a ng-if="vm.type === 'link'" ng-href="{{vm.href}}" class="btn umb-button__button {{vm.style}} umb-button--{{vm.size}}" ng-click="vm.clickButton($event)" hotkey="{{vm.shortcut}}" hotkey-when-hidden="{{vm.shortcutWhenHidden}}">
<a ng-if="vm.type === 'link'"
ng-href="{{vm.href}}"
class="btn umb-button__button {{vm.style}} umb-button--{{vm.size}} umb-outline"
ng-click="vm.clickButton($event)"
hotkey="{{vm.shortcut}}"
hotkey-when-hidden="{{vm.shortcutWhenHidden}}">
<span class="umb-button__content" ng-class="{'-hidden': vm.innerState !== 'init'}">
<i ng-if="vm.icon" class="{{vm.icon}} umb-button__icon" aria-hidden="true"></i>
{{vm.buttonLabel}}
@@ -18,7 +23,7 @@
<button
ng-if="vm.type === 'button'"
type="button"
class="btn umb-button__button {{vm.style}} umb-button--{{vm.size}}"
class="btn umb-button__button {{vm.style}} umb-button--{{vm.size}} umb-outline"
ng-click="vm.clickButton($event)"
hotkey="{{vm.shortcut}}"
hotkey-when-hidden="{{vm.shortcutWhenHidden}}"
@@ -34,12 +39,20 @@
</span>
</button>
<button ng-if="vm.type === 'submit'" type="submit" class="btn umb-button__button {{vm.style}} umb-button--{{vm.size}}" hotkey="{{vm.shortcut}}" hotkey-when-hidden="{{vm.shortcutWhenHidden}}" ng-disabled="vm.disabled" umb-auto-focus="{{vm.autoFocus && !vm.disabled ? 'true' : 'false'}}">
<button
ng-if="vm.type === 'submit'"
type="submit"
class="btn umb-button__button {{vm.style}} umb-button--{{vm.size}} umb-outline"
hotkey="{{vm.shortcut}}"
hotkey-when-hidden="{{vm.shortcutWhenHidden}}"
ng-disabled="vm.disabled"
umb-auto-focus="{{vm.autoFocus && !vm.disabled ? 'true' : 'false'}}">
<span class="umb-button__content" ng-class="{'-hidden': vm.innerState !== 'init'}">
<i ng-if="vm.icon" class="{{vm.icon}} umb-button__icon" aria-hidden="true"></i>
{{vm.buttonLabel}}
<span ng-if="vm.showCaret" class="umb-button__caret caret" aria-hidden="true"></span>
</span>
</button>
</button
</div>
@@ -169,7 +169,7 @@
ng-change="updateTemplate(node.template)">
<option value="">{{chooseLabel}}...</option>
</select>
<button ng-show="allowChangeTemplate && node.template !== null" class="umb-node-preview__action" style="margin-left:15px;" ng-click="openTemplate()">
<button type="button" ng-show="allowChangeTemplate && node.template !== null" class="umb-node-preview__action" style="margin-left:15px;" ng-click="openTemplate()">
<localize key="general_open">Open</localize>
</button>
</div>
@@ -39,10 +39,10 @@
autocomplete="off" maxlength="255" />
</ng-form>
<a ng-if="content.variants.length > 0 && hideChangeVariant !== true" class="umb-variant-switcher__toggle" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen" ng-class="{'--error': vm.errorsOnOtherVariants}">
<button type="button" ng-if="content.variants.length > 0 && hideChangeVariant !== true" class="umb-variant-switcher__toggle umb-outline" href="" ng-click="vm.dropdownOpen = !vm.dropdownOpen" ng-class="{'--error': vm.errorsOnOtherVariants}">
<span>{{vm.currentVariant.language.name}}</span>
<ins class="umb-variant-switcher__expand" ng-class="{'icon-navigation-down': !vm.dropdownOpen, 'icon-navigation-up': vm.dropdownOpen}">&nbsp;</ins>
</a>
</button>
<span ng-if="hideChangeVariant" class="umb-variant-switcher__toggle">
<span>{{vm.currentVariant.language.name}}</span>
@@ -50,10 +50,10 @@
<umb-dropdown ng-if="vm.dropdownOpen" style="min-width: 100%; max-height: 250px; overflow-y: auto; margin-top: 5px;" on-close="vm.dropdownOpen = false" umb-keyboard-list>
<umb-dropdown-item class="umb-variant-switcher__item" ng-class="{'--current': variant.active, '--not-allowed': variantIsOpen(variant.language.culture), '--error': variantHasError(variant.language.culture)}" ng-repeat="variant in content.variants">
<a href="" class="umb-variant-switcher__name-wrapper" ng-click="selectVariant($event, variant)" prevent-default>
<button class="umb-variant-switcher__name-wrapper umb-outline umb-outline--thin" ng-click="selectVariant($event, variant)" prevent-default>
<span class="umb-variant-switcher__name">{{variant.language.name}}</span>
<umb-variant-state variant="variant" class="umb-variant-switcher__state"></umb-variant-state>
</a>
</button>
<div ng-if="splitViewOpen !== true && !variant.active" class="umb-variant-switcher__split-view" ng-click="openInSplitView($event, variant)">Open in split view</div>
</umb-dropdown-item>
</umb-dropdown>
@@ -12,13 +12,17 @@
<div class="flex items-center" style="flex: 1;">
<ng-form data-element="editor-icon" name="iconForm">
<div class="umb-panel-header-icon" ng-if="!hideIcon" ng-click="openIconPicker()" ng-class="{'-placeholder': $parent.icon==='' || $parent.icon===null}"
title="{{$parent.icon}}">
<i class="icon {{$parent.icon}}" ng-if="$parent.icon!=='' && $parent.icon!==null"></i>
<div class="umb-panel-header-icon-text" ng-if="$parent.icon==='' || $parent.icon===null">
<localize key="settings_addIcon"></localize>
</div>
</div>
<button
type="button" class="umb-panel-header-icon"
ng-if="!hideIcon"
ng-click="openIconPicker()"
ng-class="{'-placeholder': $parent.icon==='' || $parent.icon===null}"
title="{{$parent.icon}}">
<i class="icon {{$parent.icon}}" ng-if="$parent.icon!=='' && $parent.icon!==null" aria-hidden="true"></i>
<div class="umb-panel-header-icon-text" ng-if="$parent.icon==='' || $parent.icon===null">
<localize key="settings_addIcon">Add icon</localize>
</div>
</button>
</ng-form>
<div id="nameField" class="umb-editor-header__name-and-description" style="flex: 1 1 auto;">
@@ -4,7 +4,7 @@
hotkey="{{::vm.hotkey}}"
hotkey-when-hidden="true"
ng-class="{'is-active': vm.item.active, '-has-error': vm.item.hasError}"
class="umb-sub-views-nav-item__action">
class="umb-sub-views-nav-item__action umb-outline umb-outline--thin">
<i class="icon {{ vm.item.icon }}" aria-hidden="true"></i>
<span class="umb-sub-views-nav-item-text">{{ vm.item.name }}</span>
<div ng-show="vm.item.badge" class="badge -type-{{vm.item.badge.type}}">{{vm.item.badge.count}}</div>
@@ -1,6 +1,6 @@
<div class="umb-property">
<ng-form name="propertyForm">
<div class="control-group umb-control-group" ng-class="{hidelabel:property.hideLabel}">
<div class="control-group umb-control-group" ng-class="{hidelabel:property.hideLabel, 'umb-control-group__listview': property.alias === '_umb_containerView'}">
<val-property-msg></val-property-msg>
@@ -11,7 +11,7 @@
<i class="icon umb-tree-icon sprTree" ng-class="::node.cssClass" title="{{::node.title}}" ng-click="select(node, $event)" ng-style="::node.style" tabindex="-1"></i>
<span class="umb-tree-item__annotation"></span>
<a class="umb-tree-item__label umb-outline" ng-href="#/{{::node.routePath}}" ng-click="select(node, $event)" title="{{::node.title}}">{{node.name}}</a>
<a class="umb-tree-item__label" ng-href="#/{{::node.routePath}}" ng-click="select(node, $event)" title="{{::node.title}}">{{node.name}}</a>
<!-- NOTE: These are the 'option' elipses -->
<button data-element="tree-item-options" class="umb-options btn-reset sr-only sr-only--focusable sr-only--hoverable" ng-click="options(node, $event)" ng-if="::node.menuUrl" aria-label="{{optionsText}} {{node.name}}"><i></i><i></i><i></i></button>
@@ -1 +1 @@
<i class="icon-check umb-checkmark umb-checkmark--{{size}}" ng-class="{'umb-checkmark--checked': checked, 'cursor-auto': readonly}"></i>
<i class="icon-check umb-checkmark umb-checkmark--{{size}} umb-outline" ng-class="{'umb-checkmark--checked': checked, 'cursor-auto': readonly}"></i>
@@ -0,0 +1,23 @@
<div class="umb-code-snippet">
<div class="umb-code-snippet__header">
<span class="language" ng-if="vm.language">{{vm.language}}</span>
<umb-button umb-clipboard
umb-clipboard-success="vm.copySuccess()"
umb-clipboard-error="vm.copyError()"
umb-clipboard-target="#umbCodeSnippet_{{vm.guid}}"
state="vm.page.copyCodeButtonState"
icon="icon-documents"
type="button"
size="s"
label="Copy"
label-key="general_copy">
</umb-button>
</div>
<div class="umb-code-snippet__content">
<pre id="umbCodeSnippet_{{vm.guid}}">
<code ng-transclude></code>
</pre>
</div>
</div>
@@ -1,14 +1,14 @@
<div class="umb-content-grid">
<div
class="umb-content-grid__item"
class="umb-content-grid__item umb-outline umb-outline--surrounding"
ng-repeat="item in content"
ng-class="{'-selected': item.selected}"
ng-click="clickItem(item, $event, $index)">
<div class="umb-content-grid__content">
<a class="umb-content-grid__item-name"
<a class="umb-content-grid__item-name umb-outline"
ng-href="{{'#' + item.editPath}}"
ng-click="clickItemName(item, $event, $index)"
ng-class="{'-light': !item.published && item.updater != null}">
@@ -4,30 +4,34 @@
<div class="umb-grid-selector__item -default" ng-if="defaultItem !== null">
<div class="umb-grid-selector__item-content">
<i class="umb-grid-selector__item-icon {{ defaultItem.icon }}"></i>
<i class="umb-grid-selector__item-icon {{ defaultItem.icon }}" aria-hidden="true"></i>
<div class="umb-grid-selector__item-label">{{ defaultItem.name }}</div>
<div ng-show="defaultItem.id"><a href="" class="umb-grid-selector__item-default-label -blue" ng-click="openTemplate(defaultItem)"><localize key="general_open">Open</localize></a></div>
<div ng-show="defaultItem.id"><button class="umb-grid-selector__item-default-label btn-link -blue" ng-click="openTemplate(defaultItem)"><localize key="general_open">Open</localize></button></div>
<span class="umb-grid-selector__item-default-label">(<localize key="general_default">Default</localize> {{itemLabel}})</span>
</div>
<i class="umb-grid-selector__item-remove icon-trash" ng-if="selectedItems.length === 1" ng-click="removeDefaultItem()"></i>
</div>
<i class="umb-grid-selector__item-remove icon-trash" ng-if="selectedItems.length === 1" ng-click="removeDefaultItem()" aria-role="button">
<span class="sr-only">Remove Template</span>
</i>
</div>
<div class="umb-grid-selector__item" ng-repeat="selectedItem in selectedItems | filter: { alias:'!'+defaultItem.alias }:true">
<div class="umb-grid-selector__item-content">
<i class="umb-grid-selector__item-icon {{ selectedItem.icon }}"></i>
<i class="umb-grid-selector__item-icon {{ selectedItem.icon }}" aria-hidden="true"></i>
<div class="umb-grid-selector__item-label">{{ selectedItem.name }}</div>
<div><a href="" class="umb-grid-selector__item-default-label -blue" ng-click="openTemplate(selectedItem)"><localize key="general_open">Open</localize></a></div>
<div><a href="" class="umb-grid-selector__item-default-label -blue" ng-click="setAsDefaultItem(selectedItem)"><localize key="grid_setAsDefault">Set as default</localize></a></div>
<div><button class="umb-grid-selector__item-default-label btn-link -blue" ng-click="openTemplate(selectedItem)"><localize key="general_open">Open</localize></button></div>
<div><button class="umb-grid-selector__item-default-label btn-link -blue" ng-click="setAsDefaultItem(selectedItem)"><localize key="grid_setAsDefault">Set as default</localize></button></div>
</div>
<i class="umb-grid-selector__item-remove icon-trash" ng-click="removeItem(selectedItem)"></i>
<i class="umb-grid-selector__item-remove icon-trash" ng-click="removeItem(selectedItem)" aria-role="button">
<span class="sr-only">Remove Template</span>
</i>
</div>
<a href="" class="umb-grid-selector__item -placeholder" ng-if="(availableItems | compareArrays:selectedItems:'alias').length > 0" ng-click="openItemPicker($event)" hotkey="alt+shift+g">
<button class="umb-grid-selector__item -placeholder" ng-if="(availableItems | compareArrays:selectedItems:'alias').length > 0" ng-click="openItemPicker($event)" hotkey="alt+shift+g">
<div class="umb-grid-selector__item-content">
<div class="umb-grid-selector__item-label -blue" ng-if="defaultItem !== null"><localize key="grid_chooseExtra">Choose extra</localize> {{ itemLabel }}</div>
<div class="umb-grid-selector__item-label -blue" ng-if="defaultItem === null"><localize key="grid_chooseDefault">Choose default</localize> {{ itemLabel }}</div>
</div>
</a>
</button>
</div>
@@ -1,6 +1,6 @@
<div class="umb-layout-selector" ng-show="vm.showLayoutSelector">
<button type="button" aria-expanded="{{vm.layoutDropDownIsOpen}}" class="umb-layout-selector__active-layout" ng-click="vm.toggleLayoutDropdown()" prevent-default>
<button type="button" aria-expanded="{{vm.layoutDropDownIsOpen}}" class="umb-layout-selector__active-layout umb-outline" ng-click="vm.toggleLayoutDropdown()" prevent-default>
<i class="{{ vm.activeLayout.icon }}" aria-hidden="true"></i>
<span class="sr-only">
<localize key="visuallyHiddenTexts_activeListLayout">Active layout:</localize>&nbsp;
@@ -1,13 +1,23 @@
<div data-element="media-grid" class="umb-media-grid">
<div data-element="media-grid-item-{{$index}}" class="umb-media-grid__item" title="{{item.name}}" ng-click="clickItem(item, $event, $index)" ng-repeat="item in items | filter:filterBy" ng-style="item.flexStyle" ng-class="{'-selected': item.selected, '-file': !item.thumbnail, '-svg': item.extension == 'svg', '-selectable': item.selectable, '-unselectable': !item.selectable}">
<div data-element="media-grid-item-{{$index}}"
class="umb-media-grid__item umb-outline umb-outline--surrounding"
title="{{item.name}}"
ng-click="clickItem(item, $event, $index)"
ng-repeat="item in items | filter:filterBy"
ng-style="item.flexStyle"
ng-class="{'-selected': item.selected, '-file': !item.thumbnail, '-folder': item.isFolder, '-svg': item.extension == 'svg', '-selectable': item.selectable, '-unselectable': !item.selectable}"
>
<div>
<!--<i ng-show="item.selected" class="icon-check umb-media-grid__checkmark"></i>-->
<a ng-if="allowOnClickEdit === 'true'" ng-click="clickEdit(item, $event)" ng-href="" class="icon-edit umb-media-grid__edit"></a>
<button data-element="media-grid-item-edit" class="umb-media-grid__item-overlay btn-reset" ng-class="{'-locked': item.selected || !item.file || !item.thumbnail}" ng-click="clickItemName(item, $event, $index)" type="button">
<div data-element="media-grid-item-edit"
class="umb-media-grid__item-overlay umb-outline"
ng-class="{'-locked': item.selected || !item.file || !item.thumbnail, '-can-open': (item.isFolder ? allowOpenFolder : allowOpenFile)}"
ng-click="clickItemName(item, $event, $index)"
tabindex="{{item.isFolder && item.selectable ? '0' : '-1'}}"
>
<i ng-if="onDetailsHover" class="icon-info umb-media-grid__info" ng-mouseover="hoverItemDetails(item, $event, true)" ng-mouseleave="hoverItemDetails(item, $event, false)"></i>
<div class="umb-media-grid__item-name">{{item.name}}</div>
</button>
</div>
<!-- Check backgrund -->
<div class="umb-media-grid__image-background" ng-if="item.thumbnail || item.extension === 'svg'"></div>
@@ -1,18 +1,18 @@
<div class="umb-minilistview">
<div class="umb-mini-list-view umb-animated"
ng-class="{'umb-mini-list-view--forward': listViewAnimation === 'in', 'umb-mini-list-view--backwards': listViewAnimation === 'out'}"
<div class="umb-mini-list-view umb-animated"
ng-class="{'umb-mini-list-view--forward': listViewAnimation === 'in', 'umb-mini-list-view--backwards': listViewAnimation === 'out'}"
ng-repeat="miniListView in miniListViews">
<div class="umb-mini-list-view__title">
<i class="umb-mini-list-view__title-icon {{ miniListView.node.icon }}"></i>
<i class="umb-mini-list-view__title-icon {{ miniListView.node.icon }}" aria-hidden="true"></i>
<h4 class="umb-mini-list-view__title-text">{{ miniListView.node.name }}</h4>
</div>
<div class="umb-mini-list-view__breadcrumb">
<a ng-if="showBackButton()" href="" class="umb-mini-list-view__back" ng-click="exitMiniListView()">
<i class="icon-arrow-left umb-mini-list-view__back-icon"></i>
<i class="icon-arrow-left umb-mini-list-view__back-icon" aria-hidden="true"></i>
<span class="umb-mini-list-view__back-text"><localize key="general_back">Back</localize></span> /
</a>
@@ -30,13 +30,12 @@
<!-- Head -->
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell" style="display: none;"></div>
<div class="umb-table-cell" style="padding-top: 8px; padding-bottom: 8px;">
<form class="form-search -no-margin-bottom" style="width: 100%; margin-right: 0;" novalidate>
<div class="umb-table-cell no-display"></div>
<div class="umb-table-cell umb-table-cell-padding">
<form class="form-search -no-margin-bottom" novalidate>
<div class="inner-addon left-addon">
<i class="icon icon-search" style="font-size: 14px;"></i>
<i class="icon icon-search" aria-hidden="true"></i>
<input
style="width: 100%;"
class="form-control search-input"
type="text"
localize="placeholder"
@@ -60,15 +59,15 @@
</div>
<!-- Items -->
<div class="umb-table-row cursor-pointer"
<div class="umb-table-row cursor-pointer umb-outline"
ng-repeat="child in miniListView.children"
ng-click="selectNode(child)"
ng-class="{'-selected':child.selected, 'not-allowed':!child.allowed}">
<div class="umb-table-cell umb-table-cell--auto-width" ng-class="{'umb-table-cell--faded':child.published === false}">
<div class="flex items-center">
<ins class="icon-navigation-right umb-table__row-expand" ng-click="openNode($event, child)" ng-class="{'umb-table__row-expand--hidden': child.metaData.hasChildren !== true}">&nbsp;</ins>
<i class="umb-table-body__icon umb-table-body__fileicon {{child.icon}}"></i>
<i class="umb-table-body__icon umb-table-body__checkicon icon-check"></i>
<i class="umb-table-body__icon umb-table-body__fileicon {{child.icon}}" aria-hidden="true"></i>
<i class="umb-table-body__icon umb-table-body__checkicon icon-check" aria-hidden="true"></i>
</div>
</div>
<div class="umb-table-cell black" ng-class="{'umb-table-cell--faded':child.published === false}">{{ child.name }}</div>
@@ -76,8 +75,8 @@
<!-- Load indicator when the list doesn't have items -->
<div ng-if="!miniListView.loading && !miniListView.children" class="umb-table-row umb-table-row--empty">
<span ng-if="search === ''"><localize key="general_noItemsInList"></localize></span>
<span ng-if="search !== ''"><localize key="general_searchNoResult"></localize></span>
<span ng-if="search === ''"><localize key="general_noItemsInList">No items have been added</localize></span>
<span ng-if="search !== ''"><localize key="general_searchNoResult">Sorry, we can not find what you are looking for.</localize></span>
</div>
<!-- Load indicator when the list doesn't have items -->
@@ -0,0 +1,14 @@
<ng-form class="umb-mini-search" ng-class="{'--has-value': vm.model !== null && vm.model !== ''}" novalidate>
<i class="icon icon-search"></i>
<input
class="form-control search-input"
type="text"
localize="placeholder,label"
label="@general_typeToSearch"
placeholder="@general_typeToSearch"
ng-model="vm.model"
ng-change="vm.onChange()"
ng-keydown="vm.onKeyDown($event)"
prevent-enter-submit
no-dirty-check>
</ng-form>
@@ -0,0 +1,49 @@
(function () {
'use strict';
angular
.module('umbraco')
.component('umbMiniSearch', {
templateUrl: 'views/components/umb-mini-search/umb-mini-search.html',
controller: UmbMiniSearchController,
controllerAs: 'vm',
bindings: {
model: "=",
onStartTyping: "&?",
onSearch: "&?"
}
});
function UmbMiniSearchController($scope) {
var vm = this;
var searchDelay = _.debounce(function () {
$scope.$apply(function () {
if (vm.onSearch) {
vm.onSearch();
}
});
}, 500);
vm.onKeyDown = function (ev) {
//13: enter
switch (ev.keyCode) {
case 13:
if (vm.onSearch) {
vm.onSearch();
}
break;
}
};
vm.onChange = function () {
if (vm.onStartTyping) {
vm.onStartTyping();
}
searchDelay();
};
}
})();
@@ -1,3 +1,3 @@
<div class="umb-progress-bar umb-progress-bar--{{size}}">
<span class="umb-progress-bar umb-progress-bar--{{size}}">
<span class="umb-progress-bar__progress" style="width: {{percentage}}%"></span>
</div>
</span>
@@ -37,7 +37,7 @@
</div>
<!-- Listview body section -->
<div class="umb-table-body">
<div class="umb-table-row -selectable"
<div class="umb-table-row -selectable umb-outline"
ng-repeat="item in vm.items track by $index"
ng-class="{'-selected':item.selected, '-light':!item.published && item.updater != null}"
ng-click="vm.selectItem(item, $index, $event)">
@@ -44,9 +44,10 @@
</div>
</div>
</div>
<div>
<a href class="btn btn-link btn-crop-delete" ng-click="vm.clear()"><i class="icon-delete red"></i> <localize key="content_uploadClear">Remove file</localize></a>
<button class="btn btn-link btn-crop-delete" aria-hidden="true" ng-click="vm.clear()"><i class="icon-delete red"></i> <localize key="content_uploadClear">Remove file</localize></button>
<button class="sr-only" ng-if="file.isImage" ng-click="vm.clear()"><localize key="content_uploadClearImageContext">Click here to remove the image from the media item</localize></button>
<button class="sr-only" ng-if="!file.isImage" ng-click="vm.clear()"><localize key="content_uploadClearFileContext">Click here to remove the file from the media item</localize></button>
</div>
</div>
@@ -1,7 +1,7 @@
<div>
<div ng-switch="vm.changing">
<div ng-switch-when="false">
<button type="button" ng-click="vm.doChange()" class="btn umb-button__button btn-action">
<button type="button" ng-click="vm.doChange()" class="btn umb-button__button btn-action umb-outline umb-outline--thin">
<localize key="general_changePassword">Change password</localize>
</button>
</div>
@@ -46,7 +46,7 @@
</span>
</umb-control-group>
<button ng-click="vm.cancelChange()" ng-show="vm.showCancelBtn()" class="btn umb-button__button btn-cancel umb-button--">
<button ng-click="vm.cancelChange()" ng-show="vm.showCancelBtn()" class="btn umb-button__button btn-cancel umb-button-- umb-outline umb-outline--thin">
<localize key="general_cancel">Cancel</localize>
</button>
@@ -39,8 +39,8 @@
</div>
<div class="umb-user-group-preview__actions">
<a class="umb-user-group-preview__action" title="Edit" href="" ng-if="allowEdit" ng-click="onEdit()"><localize key="general_edit">Edit</localize></a>
<a class="umb-user-group-preview__action umb-user-group-preview__action--red" title="Remove" href="" ng-if="allowRemove" ng-click="onRemove()"><localize key="general_remove">Remove</localize></a>
<button class="btn-link umb-user-group-preview__action" title="Edit" ng-if="allowEdit" ng-click="onEdit()"><localize key="general_edit">Edit</localize></button>
<button class="btn-link umb-user-group-preview__action umb-user-group-preview__action--red" title="Remove" ng-if="allowRemove" ng-click="onRemove()"><localize key="general_remove">Remove</localize></button>
</div>
</div>
@@ -2,7 +2,7 @@
<umb-load-indicator ng-show="vm.loading"></umb-load-indicator>
<form name="vm.domainForm" ng-submit="vm.save()" novalidate>
<form name="vm.domainForm" ng-submit="vm.save()" id="assignDomain" novalidate>
<div ng-hide="vm.loading" class="umb-dialog-body">
@@ -24,11 +24,11 @@
</div>
<h5 class="umb-pane-title"><localize key="assignDomain_setDomains">Domains</localize></h5>
<small class="db" style="margin-bottom: 10px;">
<small class="db mb3">
<localize key="assignDomain_domainHelpWithVariants"></localize>
</small>
<div class="umb-el-wrap hidelabel">
<table class="table table-condensed table-bordered domains" style="margin-bottom: 10px;" ng-if="vm.domains.length > 0">
<table class="table table-condensed table-bordered domains mb3" ng-if="vm.domains.length > 0">
<thead>
<tr>
<th>
@@ -45,7 +45,9 @@
<tbody>
<tr ng-repeat="domain in vm.domains">
<td>
<input style="width: 100%; margin-bottom: 0;" type="text" ng-model="domain.name" name="domain_name_{{$index}}" required umb-auto-focus />
<input type="text" class="w-100" ng-model="domain.name" name="domain_name_{{$index}}" required umb-auto-focus />
<span ng-if="vm.domainForm.$submitted" ng-messages="vm.domainForm['domain_name_' + $index].$error">
<span class="help-inline" ng-message="required"><localize key="validation_invalidEmpty"></localize></span>
</span>
@@ -53,9 +55,8 @@
</td>
<td>
<select
style="width: 100%; margin-bottom: 0;"
name="domain_language_{{$index}}"
class="language"
class="language w-100"
ng-model="domain.lang"
ng-options="lang.name for lang in vm.languages"
required>
@@ -1,7 +1,7 @@
(function () {
"use strict";
function AssignDomainController($scope, localizationService, languageResource, contentResource, navigationService) {
function AssignDomainController($scope, localizationService, languageResource, contentResource, navigationService, notificationsService) {
var vm = this;
vm.closeDialog = closeDialog;
@@ -114,8 +114,10 @@
// validation is interesting. Check if response is valid
if(response.valid) {
vm.submitButtonState = "success";
localizationService.localize('speechBubbles_editCulturesAndHostnamesSaved').then(function(value) {
notificationsService.success(value);
});
closeDialog();
// show validation messages for each domain
@@ -129,6 +131,9 @@
});
});
vm.submitButtonState = "error";
localizationService.localize('speechBubbles_editCulturesAndHostnamesError').then(function(value) {
notificationsService.error(value);
});
}
}, function (e) {
@@ -5,7 +5,7 @@
<div ng-if="!loading" class="umb-dialog-body with-footer" ng-cloak>
<div class="umb-pane">
<h5 ng-show="selectContentType"><localize key="create_createUnder">Create a page under</localize> {{currentNode.name}}</h5>
<h5 ng-show="selectContentType" id="selectContentType"><localize key="create_createUnder">Create a page under</localize> {{currentNode.name}}</h5>
<h5 ng-show="selectBlueprint"><localize key="blueprints_selectBlueprint">Select a blueprint</localize></h5>
<div ng-if="allowedTypes && allowedTypes.length === 0">
@@ -34,7 +34,7 @@
</div>
</div>
<ul class="umb-actions umb-actions-child" ng-if="selectContentType && allowedTypes.length > 0">
<ul class="umb-actions umb-actions-child" ng-if="selectContentType && allowedTypes.length > 0" aria-labelledby="selectContentType">
<li class="umb-action" data-element="action-create-{{docType.alias}}" ng-repeat="docType in allowedTypes | orderBy:'name':false">
<button class="umb-action-link umb-outline btn-reset" ng-click="createOrSelectBlueprintIfAny(docType)" umb-auto-focus ng-if="$index === 0">
@@ -58,6 +58,7 @@
</li>
</ul>
<ul class="umb-actions umb-actions-child" ng-if="selectBlueprint">
<li class="umb-action" ng-repeat="blueprint in selectableBlueprints | orderBy:'name':false track by $index">
@@ -4,10 +4,10 @@
<div ng-show="vm.viewState === 'manageGroups'">
<div class="umb-dialog-body" ng-cloak>
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<div class="umb-pane" ng-show="!vm.loading">
<form name="permissionsForm">
<div ng-show="vm.saveError">
@@ -27,21 +27,22 @@
<p class="abstract" style="margin-bottom: 20px;"><localize key="defaultdialogs_permissionsHelp"></localize></p>
<div style="position: relative; display: inline-block; margin-bottom: 20px;">
<umb-button
type="button"
button-style="info"
label-key="defaultdialogs_permissionsSet"
label-key="defaultdialogs_permissionsSet"
action="vm.groupsDropdownOpen = !vm.groupsDropdownOpen"
has-popup="true"
is-expanded="vm.groupsDropdownOpen === undefined ? false : vm.groupsDropdownOpen"
add-ellipsis="true">
</umb-button>
<umb-dropdown ng-if="vm.groupsDropdownOpen" on-close="vm.groupsDropdownOpen = false">
<umb-dropdown ng-if="vm.groupsDropdownOpen" on-close="vm.groupsDropdownOpen = false" deep-blur="vm.groupsDropdownOpen = !vm.groupsDropdownOpen">
<umb-dropdown-item ng-repeat="group in vm.availableUserGroups | filter:{selected: '!true'}">
<a href="" ng-click="vm.editPermissions(group)" prevent-default>
<i class="{{group.icon}}"></i>
<button type="button" ng-click="vm.editPermissions(group)">
<i class="{{group.icon}}" aria-hidden="true"></i>
{{group.name}}
</a>
</button>
</umb-dropdown-item>
</umb-dropdown>
@@ -61,7 +62,7 @@
on-edit="vm.editPermissions(group)">
</umb-user-group-preview>
</div>
</form>
</div>

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