Merge branch 'netcore/dev' into bug/use-sql-main-dom-lock-when-linux
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
##############################################################
|
||||
## V8 CMS - .NET & AngularJS Doc sites ##
|
||||
## Built on demand only, NO automatic PR/branch triggers ##
|
||||
## ##
|
||||
## This build pipeline has a webhook for sucessful ##
|
||||
## builds, that sends the ZIP artifacts to our.umb to host ##
|
||||
##############################################################
|
||||
|
||||
# Name != name of pipeline but the build number format
|
||||
# https://docs.microsoft.com/en-us/azure/devops/pipelines/process/run-number?view=azure-devops&tabs=yaml
|
||||
|
||||
# Build Pipeline triggers
|
||||
# https://docs.microsoft.com/en-us/azure/devops/pipelines/repos/github?view=azure-devops&tabs=yaml#ci-triggers
|
||||
trigger: none
|
||||
pr: none
|
||||
|
||||
# Variables & their default values
|
||||
variables:
|
||||
buildPlatform: 'Any CPU'
|
||||
buildConfiguration: 'Release'
|
||||
|
||||
# VM to run the build on & it's installed software
|
||||
# https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/hosted?view=azure-devops
|
||||
# https://github.com/actions/virtual-environments/blob/master/images/win/Windows2019-Readme.md
|
||||
pool:
|
||||
vmImage: 'windows-2019'
|
||||
|
||||
jobs:
|
||||
- job: buildDocs
|
||||
displayName: 'Build static docs site as ZIPs'
|
||||
steps:
|
||||
|
||||
- task: PowerShell@2
|
||||
displayName: 'Prep build tool, build C# & JS Docs'
|
||||
inputs:
|
||||
targetType: 'inline'
|
||||
script: |
|
||||
$uenv=./build.ps1 -get -doc
|
||||
$uenv.SandboxNode()
|
||||
$uenv.CompileBelle()
|
||||
$uenv.PrepareAngularDocs()
|
||||
$nugetsourceUmbraco = "https://api.nuget.org/v3/index.json"
|
||||
$uenv.PrepareCSharpDocs()
|
||||
$uenv.RestoreNode()
|
||||
errorActionPreference: 'continue'
|
||||
workingDirectory: 'build'
|
||||
|
||||
- task: PublishPipelineArtifact@1
|
||||
inputs:
|
||||
targetPath: '$(Build.Repository.LocalPath)\build.out\'
|
||||
artifact: 'docs'
|
||||
publishLocation: 'pipeline'
|
||||
+9
-7
@@ -438,14 +438,11 @@
|
||||
Write-Host "Prepare C# Documentation"
|
||||
|
||||
$src = "$($this.SolutionRoot)\src"
|
||||
$tmp = $this.BuildTemp
|
||||
$out = $this.BuildOutput
|
||||
$tmp = $this.BuildTemp
|
||||
$out = $this.BuildOutput
|
||||
$DocFxJson = Join-Path -Path $src "\ApiDocs\docfx.json"
|
||||
$DocFxSiteOutput = Join-Path -Path $tmp "\_site\*.*"
|
||||
|
||||
|
||||
#restore nuget packages
|
||||
$this.RestoreNuGet()
|
||||
# run DocFx
|
||||
$DocFx = $this.BuildEnv.DocFx
|
||||
|
||||
@@ -463,17 +460,22 @@
|
||||
$src = "$($this.SolutionRoot)\src"
|
||||
$out = $this.BuildOutput
|
||||
|
||||
# Check if the solution has been built
|
||||
if (!(Test-Path "$src\Umbraco.Web.UI.Client\node_modules")) {throw "Umbraco needs to be built before generating the Angular Docs"}
|
||||
|
||||
"Moving to Umbraco.Web.UI.Docs folder"
|
||||
cd ..\src\Umbraco.Web.UI.Docs
|
||||
cd $src\Umbraco.Web.UI.Docs
|
||||
|
||||
"Generating the docs and waiting before executing the next commands"
|
||||
& npm install
|
||||
& npx gulp docs
|
||||
|
||||
Pop-Location
|
||||
|
||||
# change baseUrl
|
||||
$BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/"
|
||||
$IndexPath = "./api/index.html"
|
||||
(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath
|
||||
(Get-Content $IndexPath).replace('origin + location.href.substr(origin.length).replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath
|
||||
|
||||
# zip it
|
||||
& $this.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Docs\api\*.*"
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using System.Net.Mail;
|
||||
using Umbraco.Core.Configuration;
|
||||
|
||||
namespace Umbraco.Configuration
|
||||
@@ -8,5 +9,10 @@ namespace Umbraco.Configuration
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string PickupDirectoryLocation { get; set; }
|
||||
public SmtpDeliveryMethod DeliveryMethod { get; set; }
|
||||
|
||||
public string Username { get; set; }
|
||||
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Mail;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration;
|
||||
@@ -72,10 +73,10 @@ namespace Umbraco.Configuration.Models
|
||||
_configuration.GetValue(Prefix + "NoNodesViewPath", "~/config/splashes/NoNodes.cshtml");
|
||||
|
||||
public bool IsSmtpServerConfigured =>
|
||||
_configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp")?.GetChildren().Any() ?? false;
|
||||
_configuration.GetSection(Constants.Configuration.ConfigGlobalPrefix + "Smtp")?.GetChildren().Any() ?? false;
|
||||
|
||||
public ISmtpSettings SmtpSettings =>
|
||||
new SmtpSettingsImpl(_configuration.GetSection(Constants.Configuration.ConfigPrefix + "Smtp"));
|
||||
new SmtpSettingsImpl(_configuration.GetSection(Constants.Configuration.ConfigGlobalPrefix + "Smtp"));
|
||||
|
||||
private class SmtpSettingsImpl : ISmtpSettings
|
||||
{
|
||||
@@ -90,6 +91,11 @@ namespace Umbraco.Configuration.Models
|
||||
public string Host => _configurationSection.GetValue<string>("Host");
|
||||
public int Port => _configurationSection.GetValue<int>("Port");
|
||||
public string PickupDirectoryLocation => _configurationSection.GetValue<string>("PickupDirectoryLocation");
|
||||
public SmtpDeliveryMethod DeliveryMethod => _configurationSection.GetValue<SmtpDeliveryMethod>("DeliveryMethod");
|
||||
|
||||
public string Username => _configurationSection.GetValue<string>("Username");
|
||||
|
||||
public string Password => _configurationSection.GetValue<string>("Password");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Net.Mail;
|
||||
|
||||
namespace Umbraco.Core.Configuration
|
||||
{
|
||||
public interface ISmtpSettings
|
||||
@@ -6,5 +8,8 @@ namespace Umbraco.Core.Configuration
|
||||
string Host { get; }
|
||||
int Port{ get; }
|
||||
string PickupDirectoryLocation { get; }
|
||||
SmtpDeliveryMethod DeliveryMethod { get; }
|
||||
string Username { get; }
|
||||
string Password { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Umbraco.Core.Composing;
|
||||
@@ -77,15 +78,28 @@ namespace Umbraco.Core
|
||||
|
||||
var ctorParameters = ctor.GetParameters();
|
||||
var ctorArgs = new object[ctorParameters.Length];
|
||||
var availableArgs = new List<object>(args);
|
||||
var i = 0;
|
||||
foreach (var parameter in ctorParameters)
|
||||
{
|
||||
// no! IsInstanceOfType is not ok here
|
||||
// ReSharper disable once UseMethodIsInstanceOfType
|
||||
var arg = args?.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType()));
|
||||
ctorArgs[i++] = arg ?? factory.GetInstance(parameter.ParameterType);
|
||||
var idx = availableArgs.FindIndex(a => parameter.ParameterType.IsAssignableFrom(a.GetType()));
|
||||
if(idx >= 0)
|
||||
{
|
||||
// Found a suitable supplied argument
|
||||
ctorArgs[i++] = availableArgs[idx];
|
||||
|
||||
// A supplied argument can be used at most once
|
||||
availableArgs.RemoveAt(idx);
|
||||
}
|
||||
else
|
||||
{
|
||||
// None of the provided arguments is suitable: get an instance from the factory
|
||||
ctorArgs[i++] = factory.GetInstance(parameter.ParameterType);
|
||||
}
|
||||
}
|
||||
return ctor.Invoke(ctorArgs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
/// </summary>
|
||||
public string Culture { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// When dealing with content variants, this is the segment for the variant
|
||||
/// </summary>
|
||||
public string Segment { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// An array of metadata that is parsed out from the file info posted to the server which is set on the client.
|
||||
/// </summary>
|
||||
|
||||
@@ -25,6 +25,7 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content is published.
|
||||
/// </summary>
|
||||
/// <remarks>The <see cref="PublishedVersionId"/> property tells you which version of the content is currently published.</remarks>
|
||||
bool Published { get; set; }
|
||||
|
||||
PublishedState PublishedState { get; set; }
|
||||
@@ -32,10 +33,11 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the content has been edited.
|
||||
/// </summary>
|
||||
/// <remarks>Will return `true` once unpublished edits have been made after the version with <see cref="PublishedVersionId"/> has been published.</remarks>
|
||||
bool Edited { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the published version identifier.
|
||||
/// Gets the version identifier for the currently published version of the content.
|
||||
/// </summary>
|
||||
int PublishedVersionId { get; set; }
|
||||
|
||||
@@ -129,6 +131,6 @@ namespace Umbraco.Core.Models
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IContent DeepCloneWithResetIdentities();
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public static class RelationTypeExtensions
|
||||
{
|
||||
public static bool IsSystemRelationType(this IRelationType relationType) =>
|
||||
relationType.Alias == Constants.Conventions.RelationTypes.RelatedDocumentAlias
|
||||
|| relationType.Alias == Constants.Conventions.RelationTypes.RelatedMediaAlias
|
||||
|| relationType.Alias == Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias
|
||||
|| relationType.Alias == Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias
|
||||
|| relationType.Alias == Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ using System.Collections.Generic;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Actions;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Web.Models.Trees
|
||||
{
|
||||
@@ -28,12 +29,15 @@ namespace Umbraco.Web.Models.Trees
|
||||
Name = name;
|
||||
}
|
||||
|
||||
|
||||
public MenuItem(string alias, ILocalizedTextService textService)
|
||||
: this()
|
||||
{
|
||||
var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture);
|
||||
values.TryGetValue($"visuallyHiddenTexts/{alias}_description", out var textDescription);
|
||||
|
||||
Alias = alias;
|
||||
Name = textService.Localize($"actions/{Alias}");
|
||||
TextDescription = textDescription;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -74,6 +78,9 @@ namespace Umbraco.Web.Models.Trees
|
||||
[Required]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[DataMember(Name = "textDescription")]
|
||||
public string TextDescription { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Ensures a menu separator will exist before this menu item
|
||||
/// </summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Actions;
|
||||
|
||||
@@ -50,14 +51,17 @@ namespace Umbraco.Web.Models.Trees
|
||||
var item = _actionCollection.GetAction<T>();
|
||||
if (item == null) return null;
|
||||
|
||||
var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture);
|
||||
values.TryGetValue($"visuallyHiddenTexts/{item.Alias}Description", out var textDescription);
|
||||
|
||||
var menuItem = new MenuItem(item, textService.Localize($"actions/{item.Alias}"))
|
||||
{
|
||||
SeparatorBefore = hasSeparator,
|
||||
OpensDialog = opensDialog
|
||||
OpensDialog = opensDialog,
|
||||
TextDescription = textDescription,
|
||||
};
|
||||
|
||||
return menuItem;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,5 +29,7 @@ namespace Umbraco.Web.Models
|
||||
public string EventElement { get; set; }
|
||||
[DataMember(Name = "customProperties")]
|
||||
public JObject CustomProperties { get; set; }
|
||||
[DataMember(Name = "skipStepIfVisible")]
|
||||
public string SkipStepIfVisible { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,9 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
Notifications = new List<BackOfficeNotification>();
|
||||
}
|
||||
|
||||
[DataMember(Name = "isSystemRelationType")]
|
||||
public bool IsSystemRelationType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false)
|
||||
/// </summary>
|
||||
|
||||
@@ -39,6 +39,8 @@ namespace Umbraco.Web.Models.Mapping
|
||||
target.Udi = Udi.Create(Constants.UdiEntityType.RelationType, source.Key);
|
||||
target.Path = "-1," + source.Id;
|
||||
|
||||
target.IsSystemRelationType = source.IsSystemRelationType();
|
||||
|
||||
// Set the "friendly" and entity names for the parent and child object types
|
||||
if (source.ParentObjectType.HasValue)
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
private readonly IIOHelper _ioHelper;
|
||||
|
||||
internal const string ContentTypeAliasPropertyKey = "ncContentTypeAlias";
|
||||
public const string ContentTypeAliasPropertyKey = "ncContentTypeAlias";
|
||||
|
||||
public NestedContentPropertyEditor(
|
||||
ILogger logger,
|
||||
|
||||
@@ -320,6 +320,7 @@ namespace Umbraco.Core.Runtime
|
||||
composition.RegisterUnique<IEventMessagesAccessor, HybridEventMessagesAccessor>();
|
||||
composition.RegisterUnique<ITreeService, TreeService>();
|
||||
composition.RegisterUnique<ISectionService, SectionService>();
|
||||
composition.RegisterUnique<IEmailSender, EmailSender>();
|
||||
|
||||
composition.RegisterUnique<IExamineManager, ExamineManager>();
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@ using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Security.Cryptography;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Configuration;
|
||||
using System.Web;
|
||||
using Umbraco.Core.Hosting;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
@@ -17,9 +20,10 @@ namespace Umbraco.Core.Runtime
|
||||
public class SqlMainDomLock : IMainDomLock
|
||||
{
|
||||
private string _lockId;
|
||||
private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom";
|
||||
private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom";
|
||||
private const string UpdatedSuffix = "_updated";
|
||||
private readonly ILogger _logger;
|
||||
private readonly IHostingEnvironment _hostingEnvironment;
|
||||
private IUmbracoDatabase _db;
|
||||
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider();
|
||||
@@ -28,17 +32,20 @@ namespace Umbraco.Core.Runtime
|
||||
private bool _hasError;
|
||||
private object _locker = new object();
|
||||
|
||||
public SqlMainDomLock(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator)
|
||||
public SqlMainDomLock(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator, IHostingEnvironment hostingEnvironment)
|
||||
{
|
||||
// unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer
|
||||
_lockId = Guid.NewGuid().ToString();
|
||||
_logger = logger;
|
||||
_hostingEnvironment = hostingEnvironment;
|
||||
_dbFactory = new UmbracoDatabaseFactory(_logger,
|
||||
globalSettings,
|
||||
connectionStrings,
|
||||
Constants.System.UmbracoConnectionName,
|
||||
new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())),
|
||||
dbProviderFactoryCreator);
|
||||
|
||||
MainDomKey = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId(_hostingEnvironment)).GenerateHash<SHA1>();
|
||||
}
|
||||
|
||||
public async Task<bool> AcquireLockAsync(int millisecondsTimeout)
|
||||
@@ -128,6 +135,16 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the keyvalue table key for the current server/app
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The key is the the normal MainDomId which takes into account the AppDomainAppId and the physical file path of the app and this is
|
||||
/// combined with the current machine name. The machine name is required because the default semaphore lock is machine wide so it implicitly
|
||||
/// takes into account machine name whereas this needs to be explicitly per machine.
|
||||
/// </remarks>
|
||||
private string MainDomKey { get; }
|
||||
|
||||
private void ListeningLoop()
|
||||
{
|
||||
while (true)
|
||||
|
||||
+11
-1
@@ -508,6 +508,16 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
// delete content
|
||||
DeleteItemsOfTypes(descendantsAndSelf.Select(x => x.Id));
|
||||
|
||||
// Next find all other document types that have a reference to this content type
|
||||
var referenceToAllowedContentTypes = GetAll().Where(q => q.AllowedContentTypes.Any(p=>p.Id.Value==item.Id));
|
||||
foreach (var reference in referenceToAllowedContentTypes)
|
||||
{
|
||||
reference.AllowedContentTypes = reference.AllowedContentTypes.Where(p => p.Id.Value != item.Id);
|
||||
var changedRef = new List<ContentTypeChange<TItem>>() { new ContentTypeChange<TItem>(reference, ContentTypeChangeTypes.RefreshMain) };
|
||||
// Fire change event
|
||||
OnChanged(scope, changedRef.ToEventArgs());
|
||||
}
|
||||
|
||||
// finally delete the content type
|
||||
// - recursively deletes all descendants
|
||||
@@ -515,7 +525,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// (contents of any descendant type have been deleted but
|
||||
// contents of any composed (impacted) type remain but
|
||||
// need to have their property data cleared)
|
||||
Repository.Delete(item);
|
||||
Repository.Delete(item);
|
||||
|
||||
//...
|
||||
var changes = descendantsAndSelf.Select(x => new ContentTypeChange<TItem>(x, ContentTypeChangeTypes.Remove))
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
<PackageReference Include="LightInject.Annotation" Version="1.1.0" />
|
||||
<PackageReference Include="LightInject.Microsoft.DependencyInjection" Version="3.3.0" />
|
||||
<PackageReference Include="LightInject.Microsoft.Hosting" Version="1.2.0" />
|
||||
<PackageReference Include="MailKit" Version="2.6.0" />
|
||||
<PackageReference Include="Markdown" Version="2.2.1" />
|
||||
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.2" />
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Mail;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core.Composing;
|
||||
using MimeKit;
|
||||
using MimeKit.Text;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Events;
|
||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
@@ -21,7 +24,7 @@ namespace Umbraco.Core
|
||||
{
|
||||
}
|
||||
|
||||
internal EmailSender(IGlobalSettings globalSettings, bool enableEvents)
|
||||
public EmailSender(IGlobalSettings globalSettings, bool enableEvents)
|
||||
{
|
||||
_globalSettings = globalSettings;
|
||||
_enableEvents = enableEvents;
|
||||
@@ -45,7 +48,17 @@ namespace Umbraco.Core
|
||||
{
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
client.Send(message);
|
||||
|
||||
client.Connect(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port);
|
||||
|
||||
if (!(_globalSettings.SmtpSettings.Username is null &&
|
||||
_globalSettings.SmtpSettings.Password is null))
|
||||
{
|
||||
client.Authenticate(_globalSettings.SmtpSettings.Username, _globalSettings.SmtpSettings.Password);
|
||||
}
|
||||
|
||||
client.Send(ConstructEmailMessage(message));
|
||||
client.Disconnect(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -65,14 +78,25 @@ namespace Umbraco.Core
|
||||
{
|
||||
using (var client = new SmtpClient())
|
||||
{
|
||||
if (client.DeliveryMethod == SmtpDeliveryMethod.Network)
|
||||
await client.ConnectAsync(_globalSettings.SmtpSettings.Host, _globalSettings.SmtpSettings.Port);
|
||||
|
||||
if (!(_globalSettings.SmtpSettings.Username is null &&
|
||||
_globalSettings.SmtpSettings.Password is null))
|
||||
{
|
||||
await client.SendMailAsync(message);
|
||||
await client.AuthenticateAsync(_globalSettings.SmtpSettings.Username, _globalSettings.SmtpSettings.Password);
|
||||
}
|
||||
|
||||
var mailMessage = ConstructEmailMessage(message);
|
||||
if (_globalSettings.SmtpSettings.DeliveryMethod == SmtpDeliveryMethod.Network)
|
||||
{
|
||||
await client.SendAsync(mailMessage);
|
||||
}
|
||||
else
|
||||
{
|
||||
client.Send(message);
|
||||
client.Send(mailMessage);
|
||||
}
|
||||
|
||||
await client.DisconnectAsync(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,7 +107,7 @@ namespace Umbraco.Core
|
||||
/// <remarks>
|
||||
/// We assume this is possible if either an event handler is registered or an smtp server is configured
|
||||
/// </remarks>
|
||||
internal static bool CanSendRequiredEmail(IGlobalSettings globalSettings) => EventHandlerRegistered || globalSettings.IsSmtpServerConfigured;
|
||||
public static bool CanSendRequiredEmail(IGlobalSettings globalSettings) => EventHandlerRegistered || globalSettings.IsSmtpServerConfigured;
|
||||
|
||||
/// <summary>
|
||||
/// returns true if an event handler has been registered
|
||||
@@ -103,5 +127,22 @@ namespace Umbraco.Core
|
||||
var handler = SendEmail;
|
||||
if (handler != null) handler(null, e);
|
||||
}
|
||||
|
||||
private MimeMessage ConstructEmailMessage(MailMessage mailMessage)
|
||||
{
|
||||
var fromEmail = mailMessage.From?.Address;
|
||||
if(string.IsNullOrEmpty(fromEmail))
|
||||
fromEmail = _globalSettings.SmtpSettings.From;
|
||||
|
||||
var messageToSend = new MimeMessage
|
||||
{
|
||||
Subject = mailMessage.Subject,
|
||||
From = { new MailboxAddress(fromEmail)},
|
||||
Body = new TextPart(mailMessage.IsBodyHtml ? TextFormat.Html : TextFormat.Plain) { Text = mailMessage.Body }
|
||||
};
|
||||
messageToSend.To.AddRange(mailMessage.To.Select(x=>new MailboxAddress(x.Address)));
|
||||
|
||||
return messageToSend;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,15 +9,28 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
/// </summary>
|
||||
internal class ContentNestedData
|
||||
{
|
||||
[JsonProperty("properties")]
|
||||
//dont serialize empty properties
|
||||
[JsonProperty("pd")]
|
||||
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<PropertyData[]>))]
|
||||
public Dictionary<string, PropertyData[]> PropertyData { get; set; }
|
||||
|
||||
[JsonProperty("cultureData")]
|
||||
[JsonProperty("cd")]
|
||||
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<CultureVariation>))]
|
||||
public Dictionary<string, CultureVariation> CultureData { get; set; }
|
||||
|
||||
[JsonProperty("urlSegment")]
|
||||
[JsonProperty("us")]
|
||||
public string UrlSegment { get; set; }
|
||||
|
||||
//Legacy properties used to deserialize existing nucache db entries
|
||||
[JsonProperty("properties")]
|
||||
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<PropertyData[]>))]
|
||||
private Dictionary<string, PropertyData[]> LegacyPropertyData { set { PropertyData = value; } }
|
||||
|
||||
[JsonProperty("cultureData")]
|
||||
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<CultureVariation>))]
|
||||
private Dictionary<string, CultureVariation> LegacyCultureData { set { CultureData = value; } }
|
||||
|
||||
[JsonProperty("urlSegment")]
|
||||
private string LegacyUrlSegment { set { UrlSegment = value; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,16 +8,29 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
/// </summary>
|
||||
public class CultureVariation
|
||||
{
|
||||
[JsonProperty("name")]
|
||||
[JsonProperty("nm")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[JsonProperty("urlSegment")]
|
||||
[JsonProperty("us")]
|
||||
public string UrlSegment { get; set; }
|
||||
|
||||
[JsonProperty("date")]
|
||||
[JsonProperty("dt")]
|
||||
public DateTime Date { get; set; }
|
||||
|
||||
[JsonProperty("isDraft")]
|
||||
[JsonProperty("isd")]
|
||||
public bool IsDraft { get; set; }
|
||||
|
||||
//Legacy properties used to deserialize existing nucache db entries
|
||||
[JsonProperty("name")]
|
||||
private string LegacyName { set { Name = value; } }
|
||||
|
||||
[JsonProperty("urlSegment")]
|
||||
private string LegacyUrlSegment { set { UrlSegment = value; } }
|
||||
|
||||
[JsonProperty("date")]
|
||||
private DateTime LegacyDate { set { Date = value; } }
|
||||
|
||||
[JsonProperty("isDraft")]
|
||||
private bool LegacyIsDraft { set { IsDraft = value; } }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
@@ -8,21 +9,43 @@ namespace Umbraco.Web.PublishedCache.NuCache.DataSource
|
||||
private string _culture;
|
||||
private string _segment;
|
||||
|
||||
[JsonProperty("culture")]
|
||||
[DefaultValue("")]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "c")]
|
||||
public string Culture
|
||||
{
|
||||
get => _culture;
|
||||
set => _culture = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null
|
||||
}
|
||||
|
||||
[JsonProperty("seg")]
|
||||
[DefaultValue("")]
|
||||
[JsonProperty(DefaultValueHandling = DefaultValueHandling.IgnoreAndPopulate, PropertyName = "s")]
|
||||
public string Segment
|
||||
{
|
||||
get => _segment;
|
||||
set => _segment = value ?? throw new ArgumentNullException(nameof(value)); // TODO: or fallback to string.Empty? CANNOT be null
|
||||
}
|
||||
|
||||
[JsonProperty("val")]
|
||||
[JsonProperty("v")]
|
||||
public object Value { get; set; }
|
||||
|
||||
|
||||
//Legacy properties used to deserialize existing nucache db entries
|
||||
[JsonProperty("culture")]
|
||||
private string LegacyCulture
|
||||
{
|
||||
set => Culture = value;
|
||||
}
|
||||
|
||||
[JsonProperty("seg")]
|
||||
private string LegacySegment
|
||||
{
|
||||
set => Segment = value;
|
||||
}
|
||||
|
||||
[JsonProperty("val")]
|
||||
private object LegacyValue
|
||||
{
|
||||
set => Value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -900,11 +900,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
|
||||
// we ran this on a background thread then those cache refreshers are going to not get 'live' data when they query the content cache which
|
||||
// they require.
|
||||
|
||||
// These cannot currently be run side by side in parallel, due to the monitors need to be exits my the same thread that enter them.
|
||||
// These can be run side by side in parallel.
|
||||
using (_contentStore.GetScopedWriteLock(_scopeProvider))
|
||||
{
|
||||
NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _);
|
||||
}
|
||||
|
||||
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
|
||||
{
|
||||
NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _);
|
||||
|
||||
@@ -6,5 +6,6 @@
|
||||
"username": "<insert username/email in cypress.env.json>",
|
||||
"password": "<insert password in cypress.env.json>"
|
||||
},
|
||||
"supportFile": "cypress/support/index.ts"
|
||||
"supportFile": "cypress/support/index.ts",
|
||||
"videoUploadOnPasses" : false
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ context('Languages', () => {
|
||||
});
|
||||
|
||||
it('Add language', () => {
|
||||
const name = "Neddersass’sch (Nedderlannen)"; // Must be an option in the select box
|
||||
const name = "Kyrgyz (Kyrgyzstan)"; // Must be an option in the select box
|
||||
|
||||
cy.umbracoEnsureLanguageNameNotExists(name);
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
/// <reference types="Cypress" />
|
||||
import {DocumentTypeBuilder, TemplateBuilder} from "umbraco-cypress-testhelpers";
|
||||
|
||||
context('Templates', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -6,28 +8,50 @@ context('Templates', () => {
|
||||
});
|
||||
|
||||
it('Create template', () => {
|
||||
const name = "Test template";
|
||||
const name = "Test template";
|
||||
|
||||
cy.umbracoEnsureTemplateNameNotExists(name);
|
||||
cy.umbracoEnsureTemplateNameNotExists(name);
|
||||
|
||||
cy.umbracoSection('settings');
|
||||
cy.get('li .umb-tree-root:contains("Settings")').should("be.visible");
|
||||
cy.umbracoSection('settings');
|
||||
cy.get('li .umb-tree-root:contains("Settings")').should("be.visible");
|
||||
|
||||
cy.umbracoTreeItem("settings", ["Templates"]).rightclick();
|
||||
cy.umbracoTreeItem("settings", ["Templates"]).rightclick();
|
||||
|
||||
cy.umbracoContextMenuAction("action-create").click();
|
||||
cy.umbracoContextMenuAction("action-create").click();
|
||||
|
||||
//Type name
|
||||
cy.umbracoEditorHeaderName(name);
|
||||
//Type name
|
||||
cy.umbracoEditorHeaderName(name);
|
||||
|
||||
//Save
|
||||
cy.get('.btn-success').click();
|
||||
//Save
|
||||
cy.get("form[name='contentForm']").submit();
|
||||
|
||||
//Assert
|
||||
cy.umbracoSuccessNotification().should('be.visible');
|
||||
//Assert
|
||||
cy.umbracoSuccessNotification().should('be.visible');
|
||||
|
||||
//Clean up
|
||||
cy.umbracoEnsureTemplateNameNotExists(name);
|
||||
//Clean up
|
||||
cy.umbracoEnsureTemplateNameNotExists(name);
|
||||
});
|
||||
|
||||
it('Delete template', () => {
|
||||
const name = "Test template";
|
||||
cy.umbracoEnsureTemplateNameNotExists(name);
|
||||
|
||||
const template = new TemplateBuilder()
|
||||
.withName(name)
|
||||
.build();
|
||||
|
||||
cy.saveTemplate(template);
|
||||
|
||||
cy.umbracoSection('settings');
|
||||
cy.get('li .umb-tree-root:contains("Settings")').should("be.visible");
|
||||
|
||||
cy.umbracoTreeItem("settings", ["Templates", name]).rightclick();
|
||||
cy.umbracoContextMenuAction("action-delete").click();
|
||||
|
||||
cy.umbracoButtonByLabelKey("general_ok").click();
|
||||
|
||||
cy.contains(name).should('not.exist');
|
||||
|
||||
cy.umbracoEnsureTemplateNameNotExists(name);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.2",
|
||||
"ncp": "^2.0.0",
|
||||
"cypress": "^4.5.0",
|
||||
"umbraco-cypress-testhelpers": "1.0.0-beta-38"
|
||||
"cypress": "^4.6.0",
|
||||
"umbraco-cypress-testhelpers": "1.0.0-beta-39"
|
||||
},
|
||||
"dependencies": {
|
||||
"typescript": "^3.9.2"
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using Umbraco.Core.Configuration;
|
||||
using System.Net.Mail;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
|
||||
namespace Umbraco.Tests.Common.Builders
|
||||
{
|
||||
@@ -16,6 +18,9 @@ namespace Umbraco.Tests.Common.Builders
|
||||
private string _host;
|
||||
private int? _port;
|
||||
private string _pickupDirectoryLocation;
|
||||
private SmtpDeliveryMethod? _deliveryMethod;
|
||||
private string _username;
|
||||
private string _password;
|
||||
|
||||
public SmtpSettingsBuilder(TParent parentBuilder) : base(parentBuilder)
|
||||
{
|
||||
@@ -33,24 +38,45 @@ namespace Umbraco.Tests.Common.Builders
|
||||
return this;
|
||||
}
|
||||
|
||||
public SmtpSettingsBuilder<TParent> WithUsername(string username)
|
||||
{
|
||||
_username = username;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SmtpSettingsBuilder<TParent> WithPost(int port)
|
||||
{
|
||||
_port = port;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SmtpSettingsBuilder<TParent> WithPassword(string password)
|
||||
{
|
||||
_password = password;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SmtpSettingsBuilder<TParent> WithPickupDirectoryLocation(string pickupDirectoryLocation)
|
||||
{
|
||||
_pickupDirectoryLocation = pickupDirectoryLocation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SmtpSettingsBuilder<TParent> WithDeliveryMethod(SmtpDeliveryMethod deliveryMethod)
|
||||
{
|
||||
_deliveryMethod = deliveryMethod;
|
||||
return this;
|
||||
}
|
||||
|
||||
public override ISmtpSettings Build()
|
||||
{
|
||||
var from = _from ?? null;
|
||||
var host = _host ?? null;
|
||||
var port = _port ?? 25;
|
||||
var pickupDirectoryLocation = _pickupDirectoryLocation ?? null;
|
||||
var deliveryMethod = _deliveryMethod ?? SmtpDeliveryMethod.Network;
|
||||
var username = _username ?? null;
|
||||
var password = _password ?? null;
|
||||
|
||||
return new TestSmtpSettings()
|
||||
{
|
||||
@@ -58,6 +84,9 @@ namespace Umbraco.Tests.Common.Builders
|
||||
Host = host,
|
||||
Port = port,
|
||||
PickupDirectoryLocation = pickupDirectoryLocation,
|
||||
DeliveryMethod = deliveryMethod,
|
||||
Username = username,
|
||||
Password = password,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -67,6 +96,9 @@ namespace Umbraco.Tests.Common.Builders
|
||||
public string Host { get; set; }
|
||||
public int Port { get; set; }
|
||||
public string PickupDirectoryLocation { get; set; }
|
||||
public SmtpDeliveryMethod DeliveryMethod { get; set; }
|
||||
public string Username { get; set; }
|
||||
public string Password { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Tests.Common
|
||||
{
|
||||
public class TestClone : IDeepCloneable, IEquatable<TestClone>
|
||||
{
|
||||
public TestClone(Guid id)
|
||||
{
|
||||
Id = id;
|
||||
IsClone = true;
|
||||
}
|
||||
|
||||
public TestClone()
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
public Guid Id { get; }
|
||||
public bool IsClone { get; }
|
||||
|
||||
public object DeepClone()
|
||||
{
|
||||
return new TestClone(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current object is equal to another object of the same type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="other">An object to compare with this object.</param>
|
||||
public bool Equals(TestClone other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id.Equals(other.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the specified object is equal to the current object; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="obj">The object to compare with the current object. </param>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((TestClone)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serves as the default hash function.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the current object.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(TestClone left, TestClone right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TestClone left, TestClone right)
|
||||
{
|
||||
return Equals(left, right) == false;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -1,12 +1,11 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class AttemptTests
|
||||
{
|
||||
|
||||
[Test]
|
||||
public void AttemptIf()
|
||||
{
|
||||
+1
-2
@@ -3,9 +3,8 @@ using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
public class ClaimsIdentityExtensionsTests
|
||||
{
|
||||
+4
-80
@@ -1,13 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Tests.Common;
|
||||
|
||||
namespace Umbraco.Tests.Collections
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections
|
||||
{
|
||||
[TestFixture]
|
||||
public class DeepCloneableListTests
|
||||
@@ -85,7 +81,7 @@ namespace Umbraco.Tests.Collections
|
||||
list.Add(new TestClone());
|
||||
list.Add(new TestClone());
|
||||
|
||||
var cloned = (DeepCloneableList<TestClone>)list.DeepClone();
|
||||
var cloned = (DeepCloneableList<TestClone>) list.DeepClone();
|
||||
|
||||
//Test that each item in the sequence is equal - based on the equality comparer of TestClone (i.e. it's ID)
|
||||
Assert.IsTrue(list.SequenceEqual(cloned));
|
||||
@@ -97,77 +93,5 @@ namespace Umbraco.Tests.Collections
|
||||
Assert.AreNotSame(item, clone);
|
||||
}
|
||||
}
|
||||
|
||||
public class TestClone : IDeepCloneable, IEquatable<TestClone>
|
||||
{
|
||||
public TestClone(Guid id)
|
||||
{
|
||||
Id = id;
|
||||
IsClone = true;
|
||||
}
|
||||
|
||||
public TestClone()
|
||||
{
|
||||
Id = Guid.NewGuid();
|
||||
}
|
||||
|
||||
public Guid Id { get; private set; }
|
||||
public bool IsClone { get; private set; }
|
||||
|
||||
public object DeepClone()
|
||||
{
|
||||
return new TestClone(Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the current object is equal to another object of the same type.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="other">An object to compare with this object.</param>
|
||||
public bool Equals(TestClone other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
return Id.Equals(other.Id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the specified object is equal to the current object.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// true if the specified object is equal to the current object; otherwise, false.
|
||||
/// </returns>
|
||||
/// <param name="obj">The object to compare with the current object. </param>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((TestClone)obj);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serves as the default hash function.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// A hash code for the current object.
|
||||
/// </returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return Id.GetHashCode();
|
||||
}
|
||||
|
||||
public static bool operator ==(TestClone left, TestClone right)
|
||||
{
|
||||
return Equals(left, right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TestClone left, TestClone right)
|
||||
{
|
||||
return Equals(left, right) == false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Collections;
|
||||
|
||||
namespace Umbraco.Tests.Collections
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core.Collections
|
||||
{
|
||||
[TestFixture]
|
||||
public class OrderedHashSetTests
|
||||
@@ -12,7 +11,7 @@ namespace Umbraco.Tests.Collections
|
||||
public void Keeps_Last()
|
||||
{
|
||||
var list = new OrderedHashSet<MyClass>(keepOldest: false);
|
||||
var items = new[] { new MyClass("test"), new MyClass("test"), new MyClass("test") };
|
||||
var items = new[] {new MyClass("test"), new MyClass("test"), new MyClass("test")};
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Add(item);
|
||||
@@ -27,7 +26,7 @@ namespace Umbraco.Tests.Collections
|
||||
public void Keeps_First()
|
||||
{
|
||||
var list = new OrderedHashSet<MyClass>(keepOldest: true);
|
||||
var items = new[] { new MyClass("test"), new MyClass("test"), new MyClass("test") };
|
||||
var items = new[] {new MyClass("test"), new MyClass("test"), new MyClass("test")};
|
||||
foreach (var item in items)
|
||||
{
|
||||
list.Add(item);
|
||||
@@ -60,7 +59,7 @@ namespace Umbraco.Tests.Collections
|
||||
if (ReferenceEquals(null, obj)) return false;
|
||||
if (ReferenceEquals(this, obj)) return true;
|
||||
if (obj.GetType() != this.GetType()) return false;
|
||||
return Equals((MyClass)obj);
|
||||
return Equals((MyClass) obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
+1
-1
@@ -3,7 +3,7 @@ using Lucene.Net.Index;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class DelegateExtensionsTests
|
||||
+1
-1
@@ -3,7 +3,7 @@ using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Web.Trees;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class EnumExtensionsTests
|
||||
+2
-3
@@ -1,10 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class EnumerableExtensionsTests
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
public class GuidUtilsTests
|
||||
{
|
||||
+1
-1
@@ -3,7 +3,7 @@ using System.Text;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
public class HexEncoderTests
|
||||
{
|
||||
+3
-24
@@ -2,7 +2,7 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.Clr
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class ReflectionTests
|
||||
@@ -25,37 +25,16 @@ namespace Umbraco.Tests.Clr
|
||||
Assert.Contains(typeof(object), types);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetInterfacesIsOk()
|
||||
{
|
||||
// tests that GetInterfaces gets _all_ interfaces
|
||||
// so the AllInterfaces extension method is useless
|
||||
|
||||
var type = typeof(Class2);
|
||||
var interfaces = type.GetInterfaces();
|
||||
Assert.AreEqual(2, interfaces.Length);
|
||||
Assert.Contains(typeof(IInterface1), interfaces);
|
||||
Assert.Contains(typeof(IInterface2), interfaces);
|
||||
}
|
||||
|
||||
#region Test Objects
|
||||
|
||||
interface IInterface1
|
||||
{ }
|
||||
|
||||
interface IInterface2 : IInterface1
|
||||
private class Class1
|
||||
{
|
||||
void Method();
|
||||
}
|
||||
|
||||
class Class1 : IInterface2
|
||||
private class Class2 : Class1
|
||||
{
|
||||
public void Method() { }
|
||||
}
|
||||
|
||||
class Class2 : Class1
|
||||
{ }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+4
-10
@@ -1,12 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace Umbraco.Tests.Clr
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class ReflectionUtilitiesTests
|
||||
@@ -104,8 +104,6 @@ namespace Umbraco.Tests.Clr
|
||||
[Test]
|
||||
public void EmitMethodEmitsStaticStatic()
|
||||
{
|
||||
// static types cannot be used as type arguments
|
||||
//var method = ReflectionUtilities.EmitMethod<StaticClass1, Action>("Method");
|
||||
var method = ReflectionUtilities.EmitMethod<Action>(typeof (StaticClass1), "Method");
|
||||
method();
|
||||
}
|
||||
@@ -205,10 +203,6 @@ namespace Umbraco.Tests.Clr
|
||||
(var getter3, var setter3) = ReflectionUtilities.EmitPropertyGetterAndSetter<Class1, int>("Value3");
|
||||
Assert.AreEqual(42, getter3(class1));
|
||||
setter3(class1, 42);
|
||||
|
||||
// this is not supported yet
|
||||
//var getter4 = ReflectionUtilities.EmitPropertyGetter<Class1, object>("Value1", returned: typeof(int));
|
||||
//Assert.AreEqual(42, getter1(class1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -448,7 +442,7 @@ namespace Umbraco.Tests.Clr
|
||||
var propInt4 = type4.GetProperty("IntValue");
|
||||
Assert.IsNotNull(propInt4);
|
||||
|
||||
// ... if explicitely getting a value type
|
||||
// ... if explicitly getting a value type
|
||||
var getterInt4T = ReflectionUtilities.EmitPropertyGetter<Class4, int>(propInt4);
|
||||
Assert.IsNotNull(getterInt4T);
|
||||
var valueInt4T = getterInt4T(object4);
|
||||
+2
-2
@@ -2,7 +2,7 @@
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class VersionExtensionTests
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Tests.CoreThings
|
||||
[TestCase(0, 0, 1, 1, "0.0.1.0")]
|
||||
[TestCase(0, 0, 0, 1, "0.0.0.0")]
|
||||
[TestCase(7, 3, 0, 0, "7.2.2147483647.2147483647")]
|
||||
public void Subract_Revision(int major, int minor, int build, int rev, string outcome)
|
||||
public void Subtract_Revision(int major, int minor, int build, int rev, string outcome)
|
||||
{
|
||||
var version = new Version(major, minor, build, rev);
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ using System.Xml.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Tests.CoreThings
|
||||
namespace Umbraco.Tests.UnitTests.Umbraco.Core
|
||||
{
|
||||
[TestFixture]
|
||||
public class XmlExtensionsTests
|
||||
@@ -25,6 +25,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Lucene.Net.Contrib" Version="3.0.3" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0" />
|
||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||
<PackageReference Include="NUnit3TestAdapter" Version="3.16.1" />
|
||||
|
||||
@@ -12,7 +12,7 @@ using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Tests.Collections;
|
||||
using Umbraco.Tests.Common;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web.Cache;
|
||||
|
||||
@@ -41,12 +41,12 @@ namespace Umbraco.Tests.Cache
|
||||
[Test]
|
||||
public void Clones_List()
|
||||
{
|
||||
var original = new DeepCloneableList<DeepCloneableListTests.TestClone>(ListCloneBehavior.Always);
|
||||
original.Add(new DeepCloneableListTests.TestClone());
|
||||
original.Add(new DeepCloneableListTests.TestClone());
|
||||
original.Add(new DeepCloneableListTests.TestClone());
|
||||
var original = new DeepCloneableList<TestClone>(ListCloneBehavior.Always);
|
||||
original.Add(new TestClone());
|
||||
original.Add(new TestClone());
|
||||
original.Add(new TestClone());
|
||||
|
||||
var val = _provider.GetCacheItem<DeepCloneableList<DeepCloneableListTests.TestClone>>("test", () => original);
|
||||
var val = _provider.GetCacheItem<DeepCloneableList<TestClone>>("test", () => original);
|
||||
|
||||
Assert.AreEqual(original.Count, val.Count);
|
||||
foreach (var item in val)
|
||||
|
||||
@@ -333,6 +333,24 @@ namespace Umbraco.Tests.Composing
|
||||
Assert.AreSame(s1, s2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void CanRegisterMultipleSameTypeParametersWithCreateInstance()
|
||||
{
|
||||
var register = GetRegister();
|
||||
|
||||
register.Register<Thing4>(c =>
|
||||
{
|
||||
const string param1 = "param1";
|
||||
const string param2 = "param2";
|
||||
|
||||
return c.CreateInstance<Thing4>(param1, param2);
|
||||
});
|
||||
|
||||
var factory = register.CreateFactory();
|
||||
var instance = factory.GetInstance<Thing4>();
|
||||
Assert.AreNotEqual(instance.Thing, instance.AnotherThing);
|
||||
}
|
||||
|
||||
public interface IThing { }
|
||||
|
||||
public abstract class ThingBase : IThing { }
|
||||
@@ -353,5 +371,17 @@ namespace Umbraco.Tests.Composing
|
||||
|
||||
public IEnumerable<ThingBase> Things { get; }
|
||||
}
|
||||
|
||||
public class Thing4 : ThingBase
|
||||
{
|
||||
public readonly string Thing;
|
||||
public readonly string AnotherThing;
|
||||
|
||||
public Thing4(string thing, string anotherThing)
|
||||
{
|
||||
Thing = thing;
|
||||
AnotherThing = anotherThing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,405 @@
|
||||
using Newtonsoft.Json;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Web.Compose;
|
||||
|
||||
namespace Umbraco.Tests.PropertyEditors
|
||||
{
|
||||
[TestFixture]
|
||||
public class NestedContentPropertyComponentTests
|
||||
{
|
||||
[Test]
|
||||
public void Invalid_Json()
|
||||
{
|
||||
var component = new NestedContentPropertyComponent();
|
||||
|
||||
Assert.DoesNotThrow(() => component.CreateNestedContentKeys("this is not json", true));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void No_Nesting()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
var json = @"[
|
||||
{""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1"",""ncContentTypeAlias"":""nested"",""text"":""woot""},
|
||||
{""key"":""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",""name"":""Item 2"",""ncContentTypeAlias"":""nested"",""text"":""zoot""}
|
||||
]";
|
||||
var expected = json
|
||||
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
|
||||
.Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString());
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, false, guidFactory);
|
||||
|
||||
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void One_Level_Nesting_Unescaped()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
var json = @"[{
|
||||
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"": [{
|
||||
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]
|
||||
}
|
||||
]";
|
||||
|
||||
var expected = json
|
||||
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
|
||||
.Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString())
|
||||
.Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString())
|
||||
.Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString());
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, false, guidFactory);
|
||||
|
||||
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void One_Level_Nesting_Escaped()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
|
||||
// and this is how to do that, the result will also include quotes around it.
|
||||
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
|
||||
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]").ToString());
|
||||
|
||||
var json = @"[{
|
||||
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"":" + subJsonEscaped + @"
|
||||
}
|
||||
]";
|
||||
|
||||
var expected = json
|
||||
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
|
||||
.Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString())
|
||||
.Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString())
|
||||
.Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString());
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, false, guidFactory);
|
||||
|
||||
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Nested_In_Complex_Editor_Escaped()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
|
||||
// and this is how to do that, the result will also include quotes around it.
|
||||
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
|
||||
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""fbde4288-8382-4e13-8933-ed9c160de050"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]").ToString());
|
||||
|
||||
// Complex editor such as the grid
|
||||
var complexEditorJsonEscaped = @"{
|
||||
""name"": ""1 column layout"",
|
||||
""sections"": [
|
||||
{
|
||||
""grid"": ""12"",
|
||||
""rows"": [
|
||||
{
|
||||
""name"": ""Article"",
|
||||
""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"",
|
||||
""areas"": [
|
||||
{
|
||||
""grid"": ""4"",
|
||||
""controls"": [
|
||||
{
|
||||
""value"": ""I am quote"",
|
||||
""editor"": {
|
||||
""alias"": ""quote"",
|
||||
""view"": ""textstring""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
},
|
||||
{
|
||||
""grid"": ""8"",
|
||||
""controls"": [
|
||||
{
|
||||
""value"": ""Header"",
|
||||
""editor"": {
|
||||
""alias"": ""headline"",
|
||||
""view"": ""textstring""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
},
|
||||
{
|
||||
""value"": " + subJsonEscaped + @",
|
||||
""editor"": {
|
||||
""alias"": ""madeUpNestedContent"",
|
||||
""view"": ""madeUpNestedContentInGrid""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}]
|
||||
}]
|
||||
}";
|
||||
|
||||
|
||||
var json = @"[{
|
||||
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"":" + complexEditorJsonEscaped + @"
|
||||
}
|
||||
]";
|
||||
|
||||
var expected = json
|
||||
.Replace("04a6dba8-813c-4144-8aca-86a3f24ebf08", guids[0].ToString())
|
||||
.Replace("d8e214d8-c5a5-4b45-9b51-4050dd47f5fa", guids[1].ToString())
|
||||
.Replace("dccf550c-3a05-469e-95e1-a8f560f788c2", guids[2].ToString())
|
||||
.Replace("fbde4288-8382-4e13-8933-ed9c160de050", guids[3].ToString());
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, false, guidFactory);
|
||||
|
||||
Assert.AreEqual(JsonConvert.DeserializeObject(expected).ToString(), JsonConvert.DeserializeObject(result).ToString());
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void No_Nesting_Generates_Keys_For_Missing_Items()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
var json = @"[
|
||||
{""key"":""04a6dba8-813c-4144-8aca-86a3f24ebf08"",""name"":""Item 1 my key wont change"",""ncContentTypeAlias"":""nested"",""text"":""woot""},
|
||||
{""name"":""Item 2 was copied and has no key prop"",""ncContentTypeAlias"":""nested"",""text"":""zoot""}
|
||||
]";
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, true, guidFactory);
|
||||
|
||||
// Ensure the new GUID is put in a key into the JSON
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString()));
|
||||
|
||||
// Ensure that the original key is NOT changed/modified & still exists
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains("04a6dba8-813c-4144-8aca-86a3f24ebf08"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void One_Level_Nesting_Escaped_Generates_Keys_For_Missing_Items()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
|
||||
// and this is how to do that, the result will also include quotes around it.
|
||||
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""name"": ""Nested Item 2 was copied and has no key"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]").ToString());
|
||||
|
||||
var json = @"[{
|
||||
""name"": ""Item 1 was copied and has no key"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""key"": ""d8e214d8-c5a5-4b45-9b51-4050dd47f5fa"",
|
||||
""name"": ""Item 2"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"":" + subJsonEscaped + @"
|
||||
}
|
||||
]";
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, true, guidFactory);
|
||||
|
||||
// Ensure the new GUID is put in a key into the JSON for each item
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString()));
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString()));
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[2].ToString()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Nested_In_Complex_Editor_Escaped_Generates_Keys_For_Missing_Items()
|
||||
{
|
||||
var guids = new[] { Guid.NewGuid(), Guid.NewGuid() };
|
||||
var guidCounter = 0;
|
||||
Func<Guid> guidFactory = () => guids[guidCounter++];
|
||||
|
||||
// we need to ensure the escaped json is consistent with how it will be re-escaped after parsing
|
||||
// and this is how to do that, the result will also include quotes around it.
|
||||
var subJsonEscaped = JsonConvert.ToString(JsonConvert.DeserializeObject(@"[{
|
||||
""key"": ""dccf550c-3a05-469e-95e1-a8f560f788c2"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""name"": ""Nested Item 2 was copied and has no key"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""zoot""
|
||||
}
|
||||
]").ToString());
|
||||
|
||||
// Complex editor such as the grid
|
||||
var complexEditorJsonEscaped = @"{
|
||||
""name"": ""1 column layout"",
|
||||
""sections"": [
|
||||
{
|
||||
""grid"": ""12"",
|
||||
""rows"": [
|
||||
{
|
||||
""name"": ""Article"",
|
||||
""id"": ""b4f6f651-0de3-ef46-e66a-464f4aaa9c57"",
|
||||
""areas"": [
|
||||
{
|
||||
""grid"": ""4"",
|
||||
""controls"": [
|
||||
{
|
||||
""value"": ""I am quote"",
|
||||
""editor"": {
|
||||
""alias"": ""quote"",
|
||||
""view"": ""textstring""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
},
|
||||
{
|
||||
""grid"": ""8"",
|
||||
""controls"": [
|
||||
{
|
||||
""value"": ""Header"",
|
||||
""editor"": {
|
||||
""alias"": ""headline"",
|
||||
""view"": ""textstring""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
},
|
||||
{
|
||||
""value"": " + subJsonEscaped + @",
|
||||
""editor"": {
|
||||
""alias"": ""madeUpNestedContent"",
|
||||
""view"": ""madeUpNestedContentInGrid""
|
||||
},
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}],
|
||||
""styles"": null,
|
||||
""config"": null
|
||||
}]
|
||||
}]
|
||||
}";
|
||||
|
||||
|
||||
var json = @"[{
|
||||
""key"": ""04a6dba8-813c-4144-8aca-86a3f24ebf08"",
|
||||
""name"": ""Item 1"",
|
||||
""ncContentTypeAlias"": ""text"",
|
||||
""text"": ""woot""
|
||||
}, {
|
||||
""name"": ""Item 2 was copied and has no key"",
|
||||
""ncContentTypeAlias"": ""list"",
|
||||
""text"": ""zoot"",
|
||||
""subItems"":" + complexEditorJsonEscaped + @"
|
||||
}
|
||||
]";
|
||||
|
||||
var component = new NestedContentPropertyComponent();
|
||||
var result = component.CreateNestedContentKeys(json, true, guidFactory);
|
||||
|
||||
// Ensure the new GUID is put in a key into the JSON for each item
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[0].ToString()));
|
||||
Assert.IsTrue(JsonConvert.DeserializeObject(result).ToString().Contains(guids[1].ToString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,8 +99,8 @@ namespace Umbraco.Tests.Services
|
||||
private void AssertJsonStartsWith(int id, string expected)
|
||||
{
|
||||
var json = GetJson(id).Replace('"', '\'');
|
||||
var pos = json.IndexOf("'cultureData':", StringComparison.InvariantCultureIgnoreCase);
|
||||
json = json.Substring(0, pos + "'cultureData':".Length);
|
||||
var pos = json.IndexOf("'cd':", StringComparison.InvariantCultureIgnoreCase);
|
||||
json = json.Substring(0, pos + "'cd':".Length);
|
||||
Assert.AreEqual(expected, json);
|
||||
}
|
||||
|
||||
@@ -606,7 +606,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch content type to Nothing
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
@@ -623,7 +623,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch content back to Culture
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
@@ -640,7 +640,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch property back to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
|
||||
@@ -656,7 +656,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -697,7 +697,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch content type to Culture
|
||||
contentType.Variations = ContentVariation.Culture;
|
||||
@@ -713,7 +713,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch property to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
|
||||
@@ -728,7 +728,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch content back to Nothing
|
||||
contentType.Variations = ContentVariation.Nothing;
|
||||
@@ -745,7 +745,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -783,7 +783,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch property type to Nothing
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Nothing;
|
||||
@@ -800,7 +800,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'','seg':'','val':'v1en'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'v':'v1en'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch property back to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value1").Variations = ContentVariation.Culture;
|
||||
@@ -816,7 +816,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'v':'v2'}]},'cd':");
|
||||
|
||||
// switch other property to Culture
|
||||
contentType.PropertyTypes.First(x => x.Alias == "value2").Variations = ContentVariation.Culture;
|
||||
@@ -834,7 +834,7 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value1':[{'culture':'en','seg':'','val':'v1en'},{'culture':'fr','seg':'','val':'v1fr'}],'value2':[{'culture':'en','seg':'','val':'v2'}]},'cultureData':");
|
||||
"{'pd':{'value1':[{'c':'en','v':'v1en'},{'c':'fr','v':'v1fr'}],'value2':[{'c':'en','v':'v2'}]},'cd':");
|
||||
}
|
||||
|
||||
[TestCase(ContentVariation.Culture, ContentVariation.Nothing)]
|
||||
@@ -1065,7 +1065,7 @@ namespace Umbraco.Tests.Services
|
||||
// both value11 and value21 are variant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composed.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
@@ -1073,7 +1073,7 @@ namespace Umbraco.Tests.Services
|
||||
// both value11 and value21 are invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composed.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
@@ -1081,7 +1081,7 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is variant again, but value21 is still invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composed.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed);
|
||||
@@ -1089,7 +1089,7 @@ namespace Umbraco.Tests.Services
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composing.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1097,7 +1097,7 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composing.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1105,7 +1105,7 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is still invariant
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1113,7 +1113,7 @@ namespace Umbraco.Tests.Services
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document.Id));
|
||||
AssertJsonStartsWith(document.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -1178,11 +1178,11 @@ namespace Umbraco.Tests.Services
|
||||
// both value11 and value21 are variant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composed1.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
@@ -1190,11 +1190,11 @@ namespace Umbraco.Tests.Services
|
||||
// both value11 and value21 are invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composed1.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
@@ -1202,11 +1202,11 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is variant again, but value21 is still invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'','seg':'','val':'v21en'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'v':'v21en'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composed1.PropertyTypes.First(x => x.Alias == "value21").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composed1);
|
||||
@@ -1214,11 +1214,11 @@ namespace Umbraco.Tests.Services
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composing.Variations = ContentVariation.Nothing;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1226,11 +1226,11 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composing.Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1238,11 +1238,11 @@ namespace Umbraco.Tests.Services
|
||||
// value11 is still invariant
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11en'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11en'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
|
||||
composing.PropertyTypes.First(x => x.Alias == "value11").Variations = ContentVariation.Culture;
|
||||
ServiceContext.ContentTypeService.Save(composing);
|
||||
@@ -1250,11 +1250,11 @@ namespace Umbraco.Tests.Services
|
||||
// we can make it variant again
|
||||
Console.WriteLine(GetJson(document1.Id));
|
||||
AssertJsonStartsWith(document1.Id,
|
||||
"{'properties':{'value11':[{'culture':'en','seg':'','val':'v11en'},{'culture':'fr','seg':'','val':'v11fr'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value21':[{'culture':'en','seg':'','val':'v21en'},{'culture':'fr','seg':'','val':'v21fr'}],'value22':[{'culture':'','seg':'','val':'v22'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'c':'en','v':'v11en'},{'c':'fr','v':'v11fr'}],'value12':[{'v':'v12'}],'value21':[{'c':'en','v':'v21en'},{'c':'fr','v':'v21fr'}],'value22':[{'v':'v22'}]},'cd':");
|
||||
|
||||
Console.WriteLine(GetJson(document2.Id));
|
||||
AssertJsonStartsWith(document2.Id,
|
||||
"{'properties':{'value11':[{'culture':'','seg':'','val':'v11'}],'value12':[{'culture':'','seg':'','val':'v12'}],'value31':[{'culture':'','seg':'','val':'v31'}],'value32':[{'culture':'','seg':'','val':'v32'}]},'cultureData':");
|
||||
"{'pd':{'value11':[{'v':'v11'}],'value12':[{'v':'v12'}],'value31':[{'v':'v31'}],'value32':[{'v':'v32'}]},'cd':");
|
||||
}
|
||||
|
||||
private void CreateFrenchAndEnglishLangs()
|
||||
|
||||
@@ -313,6 +313,7 @@ namespace Umbraco.Tests.Testing
|
||||
Composition.RegisterUnique<ISectionService, SectionService>();
|
||||
|
||||
Composition.RegisterUnique<HtmlLocalLinkParser>();
|
||||
Composition.RegisterUnique<IEmailSender, EmailSender>();
|
||||
Composition.RegisterUnique<HtmlUrlParser>();
|
||||
Composition.RegisterUnique<HtmlImageSourceParser>();
|
||||
Composition.RegisterUnique<RichTextEditorPastedImages>();
|
||||
|
||||
@@ -117,17 +117,11 @@
|
||||
<Compile Include="Cache\DistributedCacheBinderTests.cs" />
|
||||
<Compile Include="Cache\RefresherTests.cs" />
|
||||
<Compile Include="Cache\SnapDictionaryTests.cs" />
|
||||
<Compile Include="Clr\ReflectionUtilitiesTests.cs" />
|
||||
<Compile Include="Collections\OrderedHashSetTests.cs" />
|
||||
<Compile Include="Composing\CompositionTests.cs" />
|
||||
<Compile Include="Composing\ContainerConformingTests.cs" />
|
||||
<Compile Include="Configurations\GlobalSettingsTests.cs" />
|
||||
<Compile Include="CoreThings\CallContextTests.cs" />
|
||||
<Compile Include="Components\ComponentTests.cs" />
|
||||
<Compile Include="CoreThings\ClaimsIdentityExtensionsTests.cs" />
|
||||
<Compile Include="CoreThings\EnumExtensionsTests.cs" />
|
||||
<Compile Include="CoreThings\GuidUtilsTests.cs" />
|
||||
<Compile Include="CoreThings\HexEncoderTests.cs" />
|
||||
<Compile Include="CoreXml\RenamedRootNavigatorTests.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\ContentXmlDto.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\PreviewXmlDto.cs" />
|
||||
@@ -157,6 +151,7 @@
|
||||
<Compile Include="Persistence\Repositories\UserRepositoryTest.cs" />
|
||||
<Compile Include="UmbracoExamine\ExamineExtensions.cs" />
|
||||
<Compile Include="PropertyEditors\DataValueReferenceFactoryCollectionTests.cs" />
|
||||
<Compile Include="PropertyEditors\NestedContentPropertyComponentTests.cs" />
|
||||
<Compile Include="PublishedContent\NuCacheChildrenTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentLanguageVariantTests.cs" />
|
||||
<Compile Include="PublishedContent\PublishedContentSnapshotTestBase.cs" />
|
||||
@@ -247,17 +242,14 @@
|
||||
<Compile Include="Web\AngularIntegration\ContentModelSerializationTests.cs" />
|
||||
<Compile Include="Web\AngularIntegration\JsInitializationTests.cs" />
|
||||
<Compile Include="Web\AngularIntegration\ServerVariablesParserTests.cs" />
|
||||
<Compile Include="CoreThings\AttemptTests.cs" />
|
||||
<Compile Include="Migrations\Stubs\DropForeignKeyMigrationStub.cs" />
|
||||
<Compile Include="Models\Mapping\ContentTypeModelMappingTests.cs" />
|
||||
<Compile Include="Cache\DeepCloneAppCacheTests.cs" />
|
||||
<Compile Include="Cache\DefaultCachePolicyTests.cs" />
|
||||
<Compile Include="Cache\FullDataSetCachePolicyTests.cs" />
|
||||
<Compile Include="Cache\SingleItemsOnlyCachePolicyTests.cs" />
|
||||
<Compile Include="Collections\DeepCloneableListTests.cs" />
|
||||
<Compile Include="Web\Controllers\AuthenticationControllerTests.cs" />
|
||||
<Compile Include="Web\Controllers\BackOfficeControllerUnitTests.cs" />
|
||||
<Compile Include="CoreThings\DelegateExtensionsTests.cs" />
|
||||
<Compile Include="Web\Controllers\ContentControllerTests.cs" />
|
||||
<Compile Include="Web\Controllers\UserEditorAuthorizationHelperTests.cs" />
|
||||
<Compile Include="Web\Controllers\UsersControllerTests.cs" />
|
||||
@@ -307,7 +299,6 @@
|
||||
<Compile Include="Web\Controllers\FilterAllowedOutgoingContentAttributeTests.cs" />
|
||||
<Compile Include="Web\Controllers\MediaControllerUnitTests.cs" />
|
||||
<Compile Include="CoreXml\FrameworkXmlTests.cs" />
|
||||
<Compile Include="Clr\ReflectionTests.cs" />
|
||||
<Compile Include="Macros\MacroParserTests.cs" />
|
||||
<Compile Include="Models\ContentExtensionsTests.cs" />
|
||||
<Compile Include="Web\Mvc\MergeParentContextViewDataAttributeTests.cs" />
|
||||
@@ -465,7 +456,6 @@
|
||||
<Compile Include="Composing\TypeLoaderTests.cs" />
|
||||
<Compile Include="TestHelpers\Stubs\TestLastChanceFinder.cs" />
|
||||
<Compile Include="TestHelpers\TestHelper.cs" />
|
||||
<Compile Include="CoreThings\EnumerableExtensionsTests.cs" />
|
||||
<Compile Include="IO\AbstractFileSystemTests.cs" />
|
||||
<Compile Include="IO\FileSystemsTests.cs" />
|
||||
<Compile Include="IO\PhysicalFileSystemTests.cs" />
|
||||
@@ -474,10 +464,8 @@
|
||||
<Compile Include="TestHelpers\FakeHttpContextFactory.cs" />
|
||||
<Compile Include="Composing\TypeFinderTests.cs" />
|
||||
<Compile Include="Routing\UmbracoModuleTests.cs" />
|
||||
<Compile Include="CoreThings\VersionExtensionTests.cs" />
|
||||
<Compile Include="Web\UrlHelperExtensionTests.cs" />
|
||||
<Compile Include="Web\WebExtensionMethodTests.cs" />
|
||||
<Compile Include="CoreThings\XmlExtensionsTests.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\DictionaryPublishedContent.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\DomainCache.cs" />
|
||||
<Compile Include="LegacyXmlPublishedCache\PreviewContent.cs" />
|
||||
|
||||
@@ -91,7 +91,8 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
Factory.GetInstance<IRuntimeState>(),
|
||||
Factory.GetInstance<UmbracoMapper>(),
|
||||
Factory.GetInstance<ISecuritySettings>(),
|
||||
Factory.GetInstance<IPublishedUrlProvider>()
|
||||
Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
Factory.GetInstance<IEmailSender>()
|
||||
);
|
||||
return usersController;
|
||||
}
|
||||
|
||||
@@ -103,7 +103,8 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
Factory.GetInstance<IHostingEnvironment>(),
|
||||
Factory.GetInstance<IImageUrlGenerator>(),
|
||||
Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
Factory.GetInstance<ISecuritySettings>()
|
||||
Factory.GetInstance<ISecuritySettings>(),
|
||||
Factory.GetInstance<IEmailSender>()
|
||||
|
||||
);
|
||||
return usersController;
|
||||
@@ -176,7 +177,8 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
Factory.GetInstance<IHostingEnvironment>(),
|
||||
Factory.GetInstance<IImageUrlGenerator>(),
|
||||
Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
Factory.GetInstance<ISecuritySettings>()
|
||||
Factory.GetInstance<ISecuritySettings>(),
|
||||
Factory.GetInstance<IEmailSender>()
|
||||
);
|
||||
return usersController;
|
||||
}
|
||||
@@ -219,7 +221,8 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
Factory.GetInstance<IHostingEnvironment>(),
|
||||
Factory.GetInstance<IImageUrlGenerator>(),
|
||||
Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
Factory.GetInstance<ISecuritySettings>()
|
||||
Factory.GetInstance<ISecuritySettings>(),
|
||||
Factory.GetInstance<IEmailSender>()
|
||||
);
|
||||
return usersController;
|
||||
}
|
||||
@@ -297,7 +300,8 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
Factory.GetInstance<IHostingEnvironment>(),
|
||||
Factory.GetInstance<IImageUrlGenerator>(),
|
||||
Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
Factory.GetInstance<ISecuritySettings>()
|
||||
Factory.GetInstance<ISecuritySettings>(),
|
||||
Factory.GetInstance<IEmailSender>()
|
||||
);
|
||||
return usersController;
|
||||
}
|
||||
@@ -487,7 +491,8 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
Factory.GetInstance<IHostingEnvironment>(),
|
||||
Factory.GetInstance<IImageUrlGenerator>(),
|
||||
Factory.GetInstance<IPublishedUrlProvider>(),
|
||||
Factory.GetInstance<ISecuritySettings>());
|
||||
Factory.GetInstance<ISecuritySettings>(),
|
||||
Factory.GetInstance<IEmailSender>());
|
||||
|
||||
var mockOwinContext = new Mock<IOwinContext>();
|
||||
var mockUserManagerMarker = new Mock<IBackOfficeUserManagerMarker>();
|
||||
|
||||
@@ -176,9 +176,10 @@ namespace Umbraco.Web.Common.Extensions
|
||||
var globalSettings = configs.Global();
|
||||
var connStrings = configs.ConnectionStrings();
|
||||
var appSettingMainDomLock = globalSettings.MainDomLock;
|
||||
|
||||
var isLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
|
||||
var mainDomLock = appSettingMainDomLock == "SqlMainDomLock" || isLinux == true
|
||||
? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connStrings, dbProviderFactoryCreator)
|
||||
? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connStrings, dbProviderFactoryCreator, hostingEnvironment)
|
||||
: new MainDomSemaphoreLock(logger, hostingEnvironment);
|
||||
|
||||
var mainDom = new MainDom(logger, mainDomLock);
|
||||
|
||||
@@ -3,10 +3,16 @@
|
||||
module.exports = {
|
||||
compile: {
|
||||
build: {
|
||||
sourcemaps: false
|
||||
sourcemaps: false,
|
||||
embedtemplates: true
|
||||
},
|
||||
dev: {
|
||||
sourcemaps: true
|
||||
sourcemaps: true,
|
||||
embedtemplates: true
|
||||
},
|
||||
test: {
|
||||
sourcemaps: false,
|
||||
embedtemplates: true
|
||||
}
|
||||
},
|
||||
sources: {
|
||||
@@ -17,7 +23,7 @@ module.exports = {
|
||||
installer: { files: "./src/less/installer.less", watch: "./src/less/**/*.less", out: "installer.css" },
|
||||
nonodes: { files: "./src/less/pages/nonodes.less", watch: "./src/less/**/*.less", out: "nonodes.style.min.css"},
|
||||
preview: { files: "./src/less/canvas-designer.less", watch: "./src/less/**/*.less", out: "canvasdesigner.css" },
|
||||
umbraco: { files: "./src/less/belle.less", watch: "./src/less/**/*.less", out: "umbraco.css" },
|
||||
umbraco: { files: "./src/less/belle.less", watch: "./src/**/*.less", out: "umbraco.css" },
|
||||
rteContent: { files: "./src/less/rte-content.less", watch: "./src/less/**/*.less", out: "rte-content.css" }
|
||||
},
|
||||
|
||||
|
||||
@@ -10,4 +10,14 @@ function setDevelopmentMode(cb) {
|
||||
return cb();
|
||||
};
|
||||
|
||||
module.exports = { setDevelopmentMode: setDevelopmentMode };
|
||||
function setTestMode(cb) {
|
||||
|
||||
config.compile.current = config.compile.test;
|
||||
|
||||
return cb();
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
setDevelopmentMode: setDevelopmentMode,
|
||||
setTestMode: setTestMode
|
||||
};
|
||||
|
||||
@@ -6,11 +6,24 @@ var karmaServer = require('karma').Server;
|
||||
* Build tests
|
||||
**************************/
|
||||
|
||||
// Karma test
|
||||
// Karma test
|
||||
function testUnit() {
|
||||
|
||||
return new karmaServer({
|
||||
configFile: __dirname + "/../../test/config/karma.conf.js"
|
||||
})
|
||||
.start();
|
||||
};
|
||||
|
||||
// Run karma test server
|
||||
function runUnitTestServer() {
|
||||
|
||||
return new karmaServer({
|
||||
configFile: __dirname + "/../../test/config/karma.conf.js",
|
||||
autoWatch: true,
|
||||
port: 9999,
|
||||
singleRun: false,
|
||||
browsers: ['ChromeDebugging'],
|
||||
keepalive: true
|
||||
})
|
||||
.start();
|
||||
@@ -24,4 +37,4 @@ function testE2e() {
|
||||
.start();
|
||||
};
|
||||
|
||||
module.exports = { testUnit: testUnit, testE2e: testE2e };
|
||||
module.exports = { testUnit: testUnit, testE2e: testE2e, runUnitTestServer: runUnitTestServer };
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const config = require('../config');
|
||||
const {watch, parallel, dest, src} = require('gulp');
|
||||
const {watch, series, parallel, dest, src} = require('gulp');
|
||||
|
||||
var _ = require('lodash');
|
||||
var MergeStream = require('merge-stream');
|
||||
@@ -9,9 +9,7 @@ var MergeStream = require('merge-stream');
|
||||
var processJs = require('../util/processJs');
|
||||
var processLess = require('../util/processLess');
|
||||
|
||||
//const { less } = require('./less');
|
||||
//const { views } = require('./views');
|
||||
|
||||
var {js} = require('./js');
|
||||
|
||||
function watchTask(cb) {
|
||||
|
||||
@@ -30,24 +28,27 @@ function watchTask(cb) {
|
||||
watch(group.watch, { ignoreInitial: true, interval: watchInterval }, function Less_Group_Compile() { return processLess(group.files, group.out); });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//Setup a watcher for all groups of view files
|
||||
var viewWatcher;
|
||||
_.forEach(config.sources.views, function (group) {
|
||||
if(group.watch !== false) {
|
||||
viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval });
|
||||
viewWatcher.on('change', function(path, stats) {
|
||||
|
||||
var task = src(group.files);
|
||||
viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval },
|
||||
parallel(
|
||||
function MoveViewsAndRegenerateJS() {
|
||||
var task = src(group.files);
|
||||
|
||||
_.forEach(config.roots, function(root){
|
||||
console.log("copying " + group.files + " to " + root + config.targets.views + group.folder);
|
||||
task = task.pipe( dest(root + config.targets.views + group.folder) );
|
||||
})
|
||||
});
|
||||
_.forEach(config.roots, function(root){
|
||||
console.log("copying " + group.files + " to " + root + config.targets.views + group.folder);
|
||||
task = task.pipe( dest(root + config.targets.views + group.folder) );
|
||||
});
|
||||
},
|
||||
js
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return cb();
|
||||
};
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ module.exports = function (files, out) {
|
||||
.pipe(sort());
|
||||
|
||||
//in production, embed the templates
|
||||
task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } }))
|
||||
if(config.compile.current.embedtemplates === true) {
|
||||
task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } }));
|
||||
}
|
||||
|
||||
task = task.pipe(concat(out)).pipe(wrap('(function(){\n%= body %\n})();'))
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
const { src, dest, series, parallel, lastRun } = require('gulp');
|
||||
|
||||
const config = require('./gulp/config');
|
||||
const { setDevelopmentMode } = require('./gulp/modes');
|
||||
const { setDevelopmentMode, setTestMode } = require('./gulp/modes');
|
||||
const { dependencies } = require('./gulp/tasks/dependencies');
|
||||
const { js } = require('./gulp/tasks/js');
|
||||
const { less } = require('./gulp/tasks/less');
|
||||
const { testE2e, testUnit } = require('./gulp/tasks/test');
|
||||
const { testE2e, testUnit, runUnitTestServer } = require('./gulp/tasks/test');
|
||||
const { views } = require('./gulp/tasks/views');
|
||||
const { watchTask } = require('./gulp/tasks/watchTask');
|
||||
|
||||
@@ -31,6 +31,6 @@ exports.build = series(parallel(dependencies, js, less, views), testUnit);
|
||||
exports.dev = series(setDevelopmentMode, parallel(dependencies, js, less, views), watchTask);
|
||||
exports.watch = series(watchTask);
|
||||
//
|
||||
exports.runTests = series(js, testUnit);
|
||||
exports.testUnit = series(testUnit);
|
||||
exports.testE2e = series(testE2e);
|
||||
exports.runTests = series(setTestMode, parallel(js, testUnit));
|
||||
exports.runUnit = series(setTestMode, parallel(js, runUnitTestServer), watchTask);
|
||||
exports.testE2e = series(setTestMode, parallel(testE2e));
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
tinymce.addI18n('en_US',{
|
||||
"Redo": "Redo",
|
||||
"Undo": "Undo",
|
||||
"Cut": "Cut",
|
||||
"Copy": "Copy",
|
||||
"Paste": "Paste",
|
||||
"Select all": "Select all",
|
||||
"New document": "New document",
|
||||
"Ok": "Ok",
|
||||
"Cancel": "Cancel",
|
||||
"Visual aids": "Visual aids",
|
||||
"Bold": "Bold",
|
||||
"Italic": "Italic",
|
||||
"Underline": "Underline",
|
||||
"Strikethrough": "Strikethrough",
|
||||
"Superscript": "Superscript",
|
||||
"Subscript": "Subscript",
|
||||
"Clear formatting": "Clear formatting",
|
||||
"Align left": "Align left",
|
||||
"Align center": "Align center",
|
||||
"Align right": "Align right",
|
||||
"Justify": "Justify",
|
||||
"Bullet list": "Bullet list",
|
||||
"Numbered list": "Numbered list",
|
||||
"Decrease indent": "Decrease indent",
|
||||
"Increase indent": "Increase indent",
|
||||
"Close": "Close",
|
||||
"Formats": "Formats",
|
||||
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.",
|
||||
"Headers": "Headers",
|
||||
"Header 1": "Header 1",
|
||||
"Header 2": "Header 2",
|
||||
"Header 3": "Header 3",
|
||||
"Header 4": "Header 4",
|
||||
"Header 5": "Header 5",
|
||||
"Header 6": "Header 6",
|
||||
"Headings": "Headings",
|
||||
"Heading 1": "Heading 1",
|
||||
"Heading 2": "Heading 2",
|
||||
"Heading 3": "Heading 3",
|
||||
"Heading 4": "Heading 4",
|
||||
"Heading 5": "Heading 5",
|
||||
"Heading 6": "Heading 6",
|
||||
"Preformatted": "Preformatted",
|
||||
"Div": "Div",
|
||||
"Pre": "Pre",
|
||||
"Code": "Code",
|
||||
"Paragraph": "Paragraph",
|
||||
"Blockquote": "Blockquote",
|
||||
"Inline": "Inline",
|
||||
"Blocks": "Blocks",
|
||||
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.",
|
||||
"Font Family": "Font Family",
|
||||
"Font Sizes": "Font Sizes",
|
||||
"Class": "Class",
|
||||
"Browse for an image": "Browse for an image",
|
||||
"OR": "OR",
|
||||
"Drop an image here": "Drop an image here",
|
||||
"Upload": "Upload",
|
||||
"Block": "Blocks",
|
||||
"Align": "Align",
|
||||
"Default": "Default",
|
||||
"Circle": "Circle",
|
||||
"Disc": "Disc",
|
||||
"Square": "Square",
|
||||
"Lower Alpha": "Lower Alpha",
|
||||
"Lower Greek": "Lower Greek",
|
||||
"Lower Roman": "Lower Roman",
|
||||
"Upper Alpha": "Upper Alpha",
|
||||
"Upper Roman": "Upper Roman",
|
||||
"Anchor": "Anchor",
|
||||
"Name": "Name",
|
||||
"Id": "ID",
|
||||
"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons, or underscores.",
|
||||
"You have unsaved changes are you sure you want to navigate away?": "You have unsaved changes are you sure you want to navigate away?",
|
||||
"Restore last draft": "Restore last draft",
|
||||
"Special character": "Special character",
|
||||
"Source code": "Source code",
|
||||
"Insert\/Edit code sample": "Insert\/Edit code sample",
|
||||
"Language": "Language",
|
||||
"Code sample": "Code sample",
|
||||
"Color": "color",
|
||||
"R": "R",
|
||||
"G": "G",
|
||||
"B": "B",
|
||||
"Left to right": "Left to right",
|
||||
"Right to left": "Right to left",
|
||||
"Emoticons": "Emoticons",
|
||||
"Document properties": "Document properties",
|
||||
"Title": "Title",
|
||||
"Keywords": "Keywords",
|
||||
"Description": "Description",
|
||||
"Robots": "Robots",
|
||||
"Author": "Author",
|
||||
"Encoding": "Encoding",
|
||||
"Fullscreen": "Fullscreen",
|
||||
"Action": "Action",
|
||||
"Shortcut": "Shortcut",
|
||||
"Help": "Help",
|
||||
"Address": "Address",
|
||||
"Focus to menubar": "Focus to menubar",
|
||||
"Focus to toolbar": "Focus to toolbar",
|
||||
"Focus to element path": "Focus to element path",
|
||||
"Focus to contextual toolbar": "Focus to contextual toolbar",
|
||||
"Insert link (if link plugin activated)": "Insert link (if link plugin activated)",
|
||||
"Save (if save plugin activated)": "Save (if save plugin activated)",
|
||||
"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)",
|
||||
"Plugins installed ({0}):": "Plugins installed ({0}):",
|
||||
"Premium plugins:": "Premium plugins:",
|
||||
"Learn more...": "Learn more...",
|
||||
"You are using {0}": "You are using {0}",
|
||||
"Plugins": "Plugins",
|
||||
"Handy Shortcuts": "Handy Shortcuts",
|
||||
"Horizontal line": "Horizontal line",
|
||||
"Insert\/edit image": "Insert\/edit image",
|
||||
"Image description": "Image description",
|
||||
"Source": "Source",
|
||||
"Dimensions": "Dimensions",
|
||||
"Constrain proportions": "Constrain proportions",
|
||||
"General": "General",
|
||||
"Advanced": "Advanced",
|
||||
"Style": "Style",
|
||||
"Vertical space": "Vertical space",
|
||||
"Horizontal space": "Horizontal space",
|
||||
"Border": "Border",
|
||||
"Insert image": "Insert image",
|
||||
"Image": "Image",
|
||||
"Image list": "Image list",
|
||||
"Rotate counterclockwise": "Rotate counterclockwise",
|
||||
"Rotate clockwise": "Rotate clockwise",
|
||||
"Flip vertically": "Flip vertically",
|
||||
"Flip horizontally": "Flip horizontally",
|
||||
"Edit image": "Edit image",
|
||||
"Image options": "Image options",
|
||||
"Zoom in": "Zoom in",
|
||||
"Zoom out": "Zoom out",
|
||||
"Crop": "Crop",
|
||||
"Resize": "Resize",
|
||||
"Orientation": "Orientation",
|
||||
"Brightness": "Brightness",
|
||||
"Sharpen": "Sharpen",
|
||||
"Contrast": "Contrast",
|
||||
"Color levels": "color levels",
|
||||
"Gamma": "Gamma",
|
||||
"Invert": "Invert",
|
||||
"Apply": "Apply",
|
||||
"Back": "Back",
|
||||
"Insert date\/time": "Insert date\/time",
|
||||
"Date\/time": "Date\/time",
|
||||
"Insert link": "Insert link",
|
||||
"Insert\/edit link": "Insert\/edit link",
|
||||
"Text to display": "Text to display",
|
||||
"Url": "Url",
|
||||
"Target": "Target",
|
||||
"None": "None",
|
||||
"New window": "New window",
|
||||
"Remove link": "Remove link",
|
||||
"Anchors": "Anchors",
|
||||
"Link": "Link",
|
||||
"Paste or type a link": "Paste or type a link",
|
||||
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",
|
||||
"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?",
|
||||
"Link list": "Link list",
|
||||
"Insert video": "Insert video",
|
||||
"Insert\/edit video": "Insert\/edit video",
|
||||
"Insert\/edit media": "Insert\/edit media",
|
||||
"Alternative source": "Alternative source",
|
||||
"Poster": "Poster",
|
||||
"Paste your embed code below:": "Paste your embed code below:",
|
||||
"Embed": "Embed",
|
||||
"Media": "Media",
|
||||
"Nonbreaking space": "Nonbreaking space",
|
||||
"Page break": "Page break",
|
||||
"Paste as text": "Paste as text",
|
||||
"Preview": "Preview",
|
||||
"Print": "Print",
|
||||
"Save": "Save",
|
||||
"Find": "Find",
|
||||
"Replace with": "Replace with",
|
||||
"Replace": "Replace",
|
||||
"Replace all": "Replace all",
|
||||
"Prev": "Prev",
|
||||
"Next": "Next",
|
||||
"Find and replace": "Find and replace",
|
||||
"Could not find the specified string.": "Could not find the specified string.",
|
||||
"Match case": "Match case",
|
||||
"Whole words": "Whole words",
|
||||
"Spellcheck": "Spellcheck",
|
||||
"Ignore": "Ignore",
|
||||
"Ignore all": "Ignore all",
|
||||
"Finish": "Finish",
|
||||
"Add to Dictionary": "Add to Dictionary",
|
||||
"Insert table": "Insert table",
|
||||
"Table properties": "Table properties",
|
||||
"Delete table": "Delete table",
|
||||
"Cell": "Cell",
|
||||
"Row": "Row",
|
||||
"Column": "Column",
|
||||
"Cell properties": "Cell properties",
|
||||
"Merge cells": "Merge cells",
|
||||
"Split cell": "Split cell",
|
||||
"Insert row before": "Insert row before",
|
||||
"Insert row after": "Insert row after",
|
||||
"Delete row": "Delete row",
|
||||
"Row properties": "Row properties",
|
||||
"Cut row": "Cut row",
|
||||
"Copy row": "Copy row",
|
||||
"Paste row before": "Paste row before",
|
||||
"Paste row after": "Paste row after",
|
||||
"Insert column before": "Insert column before",
|
||||
"Insert column after": "Insert column after",
|
||||
"Delete column": "Delete column",
|
||||
"Cols": "Cols",
|
||||
"Rows": "Rows",
|
||||
"Width": "Width",
|
||||
"Height": "Height",
|
||||
"Cell spacing": "Cell spacing",
|
||||
"Cell padding": "Cell padding",
|
||||
"Caption": "Caption",
|
||||
"Left": "Left",
|
||||
"Center": "Center",
|
||||
"Right": "Right",
|
||||
"Cell type": "Cell type",
|
||||
"Scope": "Scope",
|
||||
"Alignment": "Alignment",
|
||||
"H Align": "H Align",
|
||||
"V Align": "V Align",
|
||||
"Top": "Top",
|
||||
"Middle": "Middle",
|
||||
"Bottom": "Bottom",
|
||||
"Header cell": "Header cell",
|
||||
"Row group": "Row group",
|
||||
"Column group": "Column group",
|
||||
"Row type": "Row type",
|
||||
"Header": "Header",
|
||||
"Body": "Body",
|
||||
"Footer": "Footer",
|
||||
"Border color": "Border color",
|
||||
"Insert template": "Insert template",
|
||||
"Templates": "Templates",
|
||||
"Template": "Template",
|
||||
"Text color": "Text color",
|
||||
"Background color": "Background color",
|
||||
"Custom...": "Custom...",
|
||||
"Custom color": "Custom color",
|
||||
"No color": "No color",
|
||||
"Table of Contents": "Table of Contents",
|
||||
"Show blocks": "Show blocks",
|
||||
"Show invisible characters": "Show invisible characters",
|
||||
"Words: {0}": "Words: {0}",
|
||||
"{0} words": "{0} words",
|
||||
"File": "File",
|
||||
"Edit": "Edit",
|
||||
"Insert": "Insert",
|
||||
"View": "View",
|
||||
"Format": "Format",
|
||||
"Table": "Table",
|
||||
"Tools": "Tools",
|
||||
"Powered by {0}": "Powered by {0}",
|
||||
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help"
|
||||
});
|
||||
@@ -42,7 +42,7 @@
|
||||
"npm": "6.13.6",
|
||||
"signalr": "2.4.0",
|
||||
"spectrum-colorpicker": "1.8.0",
|
||||
"tinymce": "4.9.9",
|
||||
"tinymce": "4.9.10",
|
||||
"typeahead.js": "0.11.1",
|
||||
"underscore": "1.9.1"
|
||||
},
|
||||
|
||||
+63
-59
@@ -63,10 +63,14 @@
|
||||
vm.labels = {};
|
||||
localizationService.localizeMany([
|
||||
vm.usernameIsEmail ? "general_email" : "general_username",
|
||||
vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint"]
|
||||
vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint",
|
||||
vm.usernameIsEmail ? "placeholders_emptyEmail" : "placeholders_emptyUsername",
|
||||
"placeholders_emptyPassword"]
|
||||
).then(function (data) {
|
||||
vm.labels.usernameLabel = data[0];
|
||||
vm.labels.usernamePlaceholder = data[1];
|
||||
vm.labels.usernameError = data[2];
|
||||
vm.labels.passwordError = data[3];
|
||||
});
|
||||
|
||||
vm.twoFactor = {};
|
||||
@@ -193,70 +197,70 @@
|
||||
}
|
||||
|
||||
function loginSubmit() {
|
||||
|
||||
// make sure that we are returning to the login view.
|
||||
vm.view = "login";
|
||||
|
||||
// TODO: Do validation properly like in the invite password update
|
||||
|
||||
if (formHelper.submitForm({ scope: $scope })) {
|
||||
//if the login and password are not empty we need to automatically
|
||||
// validate them - this is because if there are validation errors on the server
|
||||
// then the user has to change both username & password to resubmit which isn't ideal,
|
||||
// so if they're not empty, we'll just make sure to set them to valid.
|
||||
if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) {
|
||||
vm.loginForm.username.$setValidity('auth', true);
|
||||
vm.loginForm.password.$setValidity('auth', true);
|
||||
}
|
||||
|
||||
if (vm.loginForm.$invalid) {
|
||||
SetTitle();
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure that we are returning to the login view.
|
||||
vm.view = "login";
|
||||
|
||||
//if the login and password are not empty we need to automatically
|
||||
// validate them - this is because if there are validation errors on the server
|
||||
// then the user has to change both username & password to resubmit which isn't ideal,
|
||||
// so if they're not empty, we'll just make sure to set them to valid.
|
||||
if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) {
|
||||
vm.loginForm.username.$setValidity('auth', true);
|
||||
vm.loginForm.password.$setValidity('auth', true);
|
||||
}
|
||||
vm.loginStates.submitButton = "busy";
|
||||
|
||||
if (vm.loginForm.$invalid) {
|
||||
return;
|
||||
}
|
||||
userService.authenticate(vm.login, vm.password)
|
||||
.then(function(data) {
|
||||
vm.loginStates.submitButton = "success";
|
||||
userService._retryRequestQueue(true);
|
||||
if (vm.onLogin) {
|
||||
vm.onLogin();
|
||||
}
|
||||
},
|
||||
function(reason) {
|
||||
|
||||
vm.loginStates.submitButton = "busy";
|
||||
//is Two Factor required?
|
||||
if (reason.status === 402) {
|
||||
vm.errorMsg = "Additional authentication required";
|
||||
show2FALoginDialog(reason.data.twoFactorView);
|
||||
} else {
|
||||
vm.loginStates.submitButton = "error";
|
||||
vm.errorMsg = reason.errorMsg;
|
||||
|
||||
userService.authenticate(vm.login, vm.password)
|
||||
.then(function (data) {
|
||||
vm.loginStates.submitButton = "success";
|
||||
userService._retryRequestQueue(true);
|
||||
if(vm.onLogin) {
|
||||
vm.onLogin();
|
||||
//set the form inputs to invalid
|
||||
vm.loginForm.username.$setValidity("auth", false);
|
||||
vm.loginForm.password.$setValidity("auth", false);
|
||||
}
|
||||
|
||||
userService._retryRequestQueue();
|
||||
|
||||
});
|
||||
|
||||
//setup a watch for both of the model values changing, if they change
|
||||
// while the form is invalid, then revalidate them so that the form can
|
||||
// be submitted again.
|
||||
vm.loginForm.username.$viewChangeListeners.push(function() {
|
||||
if (vm.loginForm.$invalid) {
|
||||
vm.loginForm.username.$setValidity('auth', true);
|
||||
vm.loginForm.password.$setValidity('auth', true);
|
||||
}
|
||||
},
|
||||
function (reason) {
|
||||
|
||||
//is Two Factor required?
|
||||
if (reason.status === 402) {
|
||||
vm.errorMsg = "Additional authentication required";
|
||||
show2FALoginDialog(reason.data.twoFactorView);
|
||||
}
|
||||
else {
|
||||
vm.loginStates.submitButton = "error";
|
||||
vm.errorMsg = reason.errorMsg;
|
||||
|
||||
//set the form inputs to invalid
|
||||
vm.loginForm.username.$setValidity("auth", false);
|
||||
vm.loginForm.password.$setValidity("auth", false);
|
||||
}
|
||||
|
||||
userService._retryRequestQueue();
|
||||
|
||||
});
|
||||
|
||||
//setup a watch for both of the model values changing, if they change
|
||||
// while the form is invalid, then revalidate them so that the form can
|
||||
// be submitted again.
|
||||
vm.loginForm.username.$viewChangeListeners.push(function () {
|
||||
if (vm.loginForm.$invalid) {
|
||||
vm.loginForm.username.$setValidity('auth', true);
|
||||
vm.loginForm.password.$setValidity('auth', true);
|
||||
}
|
||||
});
|
||||
vm.loginForm.password.$viewChangeListeners.push(function () {
|
||||
if (vm.loginForm.$invalid) {
|
||||
vm.loginForm.username.$setValidity('auth', true);
|
||||
vm.loginForm.password.$setValidity('auth', true);
|
||||
}
|
||||
});
|
||||
vm.loginForm.password.$viewChangeListeners.push(function() {
|
||||
if (vm.loginForm.$invalid) {
|
||||
vm.loginForm.username.$setValidity('auth', true);
|
||||
vm.loginForm.password.$setValidity('auth', true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function requestPasswordResetSubmit(email) {
|
||||
|
||||
+28
-8
@@ -5,14 +5,14 @@
|
||||
@scope
|
||||
|
||||
@description
|
||||
<b>Added in Umbraco 7.8</b>. The tour component is a global component and is already added to the umbraco markup.
|
||||
In the Umbraco UI the tours live in the "Help drawer" which opens when you click the Help-icon in the bottom left corner of Umbraco.
|
||||
You can easily add you own tours to the Help-drawer or show and start tours from
|
||||
<b>Added in Umbraco 7.8</b>. The tour component is a global component and is already added to the umbraco markup.
|
||||
In the Umbraco UI the tours live in the "Help drawer" which opens when you click the Help-icon in the bottom left corner of Umbraco.
|
||||
You can easily add you own tours to the Help-drawer or show and start tours from
|
||||
anywhere in the Umbraco backoffice. To see a real world example of a custom tour implementation, install <a href="https://our.umbraco.com/projects/starter-kits/the-starter-kit/">The Starter Kit</a> in Umbraco 7.8
|
||||
|
||||
<h1><b>Extending the help drawer with custom tours</b></h1>
|
||||
The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file.
|
||||
Place the file in <i>App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json</i> and it will automatically be
|
||||
The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file.
|
||||
Place the file in <i>App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json</i> and it will automatically be
|
||||
picked up by Umbraco and shown in the Help-drawer.
|
||||
|
||||
<h3><b>The tour object</b></h3>
|
||||
@@ -26,7 +26,7 @@ The tour object consist of two parts - The overall tour configuration and a list
|
||||
"groupOrder": 200 // Control the order of tour groups
|
||||
"allowDisable": // Adds a "Don't" show this tour again"-button to the intro step
|
||||
"culture" : // From v7.11+. Specifies the culture of the tour (eg. en-US), if set the tour will only be shown to users with this culture set on their profile. If omitted or left empty the tour will be visible to all users
|
||||
"requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load.
|
||||
"requiredSections":["content", "media", "mySection"] // Sections that the tour will access while running, if the user does not have access to the required tour sections, the tour will not load.
|
||||
"steps": [] // tour steps - see next example
|
||||
}
|
||||
</pre>
|
||||
@@ -43,11 +43,12 @@ The tour object consist of two parts - The overall tour configuration and a list
|
||||
"backdropOpacity": 0.4 // the backdrop opacity
|
||||
"view": "" // add a custom view
|
||||
"customProperties" : {} // add any custom properties needed for the custom view
|
||||
"skipStepIfVisible": ".dashboard div [data-element='my-tour-button']" // if we can find this DOM element on the page then we will skip this step
|
||||
}
|
||||
</pre>
|
||||
|
||||
<h1><b>Adding tours to other parts of the Umbraco backoffice</b></h1>
|
||||
It is also possible to add a list of custom tours to other parts of the Umbraco backoffice,
|
||||
It is also possible to add a list of custom tours to other parts of the Umbraco backoffice,
|
||||
as an example on a Dashboard in a Custom section. You can then use the {@link umbraco.services.tourService tourService} to start and stop tours but you don't have to register them as part of the tour service.
|
||||
|
||||
<h1><b>Using the tour service</b></h1>
|
||||
@@ -86,7 +87,8 @@ as an example on a Dashboard in a Custom section. You can then use the {@link um
|
||||
"element": "[data-element='my-tour-button']",
|
||||
"title": "Click the button",
|
||||
"content": "Click the button",
|
||||
"event": "click"
|
||||
"event": "click",
|
||||
"skipStepIfVisible": "[data-element='my-other-tour-button']"
|
||||
}
|
||||
]
|
||||
};
|
||||
@@ -257,9 +259,27 @@ In the following example you see how to run some custom logic before a step goes
|
||||
|
||||
// make sure we don't go too far
|
||||
if (scope.model.currentStepIndex !== scope.model.steps.length) {
|
||||
|
||||
var upcomingStep = scope.model.steps[scope.model.currentStepIndex];
|
||||
|
||||
// If the currentStep JSON object has 'skipStepIfVisible'
|
||||
// It's a DOM selector - if we find it then we ship over this step
|
||||
if (upcomingStep.skipStepIfVisible) {
|
||||
let tryFindDomEl = document.querySelector(upcomingStep.skipStepIfVisible);
|
||||
if (tryFindDomEl) {
|
||||
// check if element is visible:
|
||||
if( tryFindDomEl.offsetWidth || tryFindDomEl.offsetHeight || tryFindDomEl.getClientRects().length ) {
|
||||
// if it was visible then we skip the step.
|
||||
nextStep();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startStep();
|
||||
// tour completed - final step
|
||||
} else {
|
||||
// tour completed - final step
|
||||
scope.loadingStep = true;
|
||||
|
||||
waitForPendingRerequests().then(function () {
|
||||
|
||||
+77
-63
@@ -206,7 +206,7 @@ Use this directive to construct a header inside the main editor window.
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function EditorHeaderDirective(editorService, localizationService, editorState) {
|
||||
function EditorHeaderDirective(editorService, localizationService, editorState, $rootScope) {
|
||||
|
||||
function link(scope, $injector) {
|
||||
|
||||
@@ -224,27 +224,9 @@ Use this directive to construct a header inside the main editor window.
|
||||
if (editorState.current) {
|
||||
//to do make work for user create/edit
|
||||
// to do make it work for user group create/ edit
|
||||
// to do make it work for language edit/create
|
||||
// to do make it work for log viewer
|
||||
scope.isNew = editorState.current.id === 0 ||
|
||||
editorState.current.id === "0" ||
|
||||
editorState.current.id === -1 ||
|
||||
editorState.current.id === 0 ||
|
||||
editorState.current.id === "-1";
|
||||
|
||||
var localizeVars = [
|
||||
scope.isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit",
|
||||
"visuallyHiddenTexts_name",
|
||||
scope.isNew ? "general_new" : "general_edit"
|
||||
];
|
||||
|
||||
if (scope.editorfor) {
|
||||
localizeVars.push(scope.editorfor);
|
||||
}
|
||||
localizationService.localizeMany(localizeVars).then(function(data) {
|
||||
setAccessibilityForEditor(data);
|
||||
scope.loading = false;
|
||||
});
|
||||
// to make it work for language edit/create
|
||||
setAccessibilityForEditorState();
|
||||
scope.loading = false;
|
||||
} else {
|
||||
scope.loading = false;
|
||||
}
|
||||
@@ -283,59 +265,91 @@ Use this directive to construct a header inside the main editor window.
|
||||
editorService.iconPicker(iconPicker);
|
||||
};
|
||||
|
||||
function setAccessibilityForEditor(data) {
|
||||
|
||||
if (editorState.current) {
|
||||
if (scope.nameLocked) {
|
||||
scope.accessibility.a11yName = scope.name;
|
||||
SetPageTitle(scope.name);
|
||||
} else {
|
||||
|
||||
scope.accessibility.a11yMessage = data[0];
|
||||
scope.accessibility.a11yName = data[1];
|
||||
var title = data[2] + ":";
|
||||
if (!scope.isNew) {
|
||||
scope.accessibility.a11yMessage += " " + scope.name;
|
||||
title += " " + scope.name;
|
||||
} else {
|
||||
var name = "";
|
||||
if (editorState.current.contentTypeName) {
|
||||
name = editorState.current.contentTypeName;
|
||||
} else if (scope.editorfor) {
|
||||
name = data[3];
|
||||
}
|
||||
if (name !== "") {
|
||||
scope.accessibility.a11yMessage += " " + name;
|
||||
scope.accessibility.a11yName = name + " " + scope.accessibility.a11yName;
|
||||
title += " " + name;
|
||||
}
|
||||
}
|
||||
if (title !== data[2] + ":") {
|
||||
SetPageTitle(title);
|
||||
}
|
||||
|
||||
}
|
||||
scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage);
|
||||
scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName);
|
||||
function setAccessibilityForEditorState() {
|
||||
var isNew = editorState.current.id === 0 ||
|
||||
editorState.current.id === "0" ||
|
||||
editorState.current.id === -1 ||
|
||||
editorState.current.id === 0 ||
|
||||
editorState.current.id === "-1";
|
||||
|
||||
var contentTypeName = "";
|
||||
if (editorState.current.contentTypeName) {
|
||||
contentTypeName = editorState.current.contentTypeName;
|
||||
}
|
||||
|
||||
|
||||
var setTitle = false;
|
||||
if (scope.setpagetitle !== undefined) {
|
||||
setTitle = scope.setpagetitle;
|
||||
}
|
||||
setAccessibilityHeaderDirective(isNew, scope.editorfor, scope.nameLocked, scope.name, contentTypeName, setTitle);
|
||||
}
|
||||
|
||||
function setAccessibilityHeaderDirective(isNew, editorFor, nameLocked, entityName, contentTypeName, setTitle) {
|
||||
|
||||
var localizeVars = [
|
||||
isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit",
|
||||
"visuallyHiddenTexts_name",
|
||||
isNew ? "general_new" : "general_edit"
|
||||
];
|
||||
|
||||
if (editorFor) {
|
||||
localizeVars.push(editorFor);
|
||||
}
|
||||
localizationService.localizeMany(localizeVars).then(function(data) {
|
||||
if (nameLocked) {
|
||||
scope.accessibility.a11yName = entityName;
|
||||
if (setTitle) {
|
||||
SetPageTitle(entityName);
|
||||
}
|
||||
} else {
|
||||
|
||||
scope.accessibility.a11yMessage = data[0];
|
||||
scope.accessibility.a11yName = data[1];
|
||||
var title = data[2] + ":";
|
||||
if (!isNew) {
|
||||
scope.accessibility.a11yMessage += " " + entityName;
|
||||
title += " " + entityName;
|
||||
} else {
|
||||
var name = "";
|
||||
if (contentTypeName) {
|
||||
name = editorState.current.contentTypeName;
|
||||
} else if (editorFor) {
|
||||
name = data[3];
|
||||
}
|
||||
if (name !== "") {
|
||||
scope.accessibility.a11yMessage += " " + name;
|
||||
scope.accessibility.a11yName = name + " " + scope.accessibility.a11yName;
|
||||
title += " " + name;
|
||||
}
|
||||
}
|
||||
if (setTitle && title !== data[2] + ":") {
|
||||
SetPageTitle(title);
|
||||
}
|
||||
|
||||
}
|
||||
scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage);
|
||||
scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
function isEmptyOrSpaces(str) {
|
||||
return str === null || str===undefined || str.trim ==='';
|
||||
}
|
||||
|
||||
function SetPageTitle(title) {
|
||||
var setTitle = false;
|
||||
if (scope.setpagetitle !== undefined) {
|
||||
setTitle = scope.setpagetitle;
|
||||
}
|
||||
if (setTitle) {
|
||||
scope.$emit("$changeTitle", title);
|
||||
}
|
||||
}
|
||||
|
||||
$rootScope.$on('$setAccessibleHeader', function (event, isNew, editorFor, nameLocked, name, contentTypeName, setTitle) {
|
||||
setAccessibilityHeaderDirective(isNew, editorFor, nameLocked, name, contentTypeName, setTitle);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
var directive = {
|
||||
transclude: true,
|
||||
restrict: 'E',
|
||||
|
||||
+3
-1
@@ -32,6 +32,7 @@
|
||||
@param {boolean} required Set the checkbox to be required.
|
||||
@param {callback} onChange Callback when the value of the checkbox change by interaction.
|
||||
@param {string} cssClass Set a css class modifier
|
||||
@param {boolean} disableDirtyCheck Disable checking if the model is dirty
|
||||
|
||||
**/
|
||||
|
||||
@@ -84,7 +85,8 @@
|
||||
required: "<",
|
||||
onChange: "&?",
|
||||
cssClass: "@?",
|
||||
iconClass: "@?"
|
||||
iconClass: "@?",
|
||||
disableDirtyCheck: "=?"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+9
-4
@@ -16,10 +16,15 @@ angular.module("umbraco.directives")
|
||||
replace: true,
|
||||
templateUrl: 'views/components/property/umb-property.html',
|
||||
link: function (scope) {
|
||||
userService.getCurrentUser().then(function (u) {
|
||||
var isAdmin = u.userGroups.indexOf('admin') !== -1;
|
||||
scope.propertyAlias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.property.alias : null;
|
||||
});
|
||||
|
||||
scope.controlLabelTitle = null;
|
||||
if(Umbraco.Sys.ServerVariables.isDebuggingEnabled) {
|
||||
userService.getCurrentUser().then(function (u) {
|
||||
if(u.allowedSections.indexOf("settings") !== -1 ? true : false) {
|
||||
scope.controlLabelTitle = scope.property.alias;
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
//Define a controller for this directive to expose APIs to other directives
|
||||
controller: function ($scope) {
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
<div ng-controller="My.Controller as vm">
|
||||
|
||||
<div class="my-gallery">
|
||||
<a href="" ng-repeat="image in images" ng-click="vm.openLightbox($index, images)">
|
||||
<button type="button" ng-repeat="image in images" ng-click="vm.openLightbox($index, images)">
|
||||
<img ng-src="image.source" />
|
||||
</a>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<umb-lightbox
|
||||
|
||||
+9
-4
@@ -102,10 +102,15 @@
|
||||
if (!scope.editLabelKey) {
|
||||
scope.editLabelKey = "general_edit";
|
||||
}
|
||||
userService.getCurrentUser().then(function (u) {
|
||||
var isAdmin = u.userGroups.indexOf('admin') !== -1;
|
||||
scope.alias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.alias : null;
|
||||
});
|
||||
|
||||
scope.nodeNameTitle = null;
|
||||
if(Umbraco.Sys.ServerVariables.isDebuggingEnabled) {
|
||||
userService.getCurrentUser().then(function (u) {
|
||||
if (u.allowedSections.indexOf("settings") !== -1 ? true : false) {
|
||||
scope.nodeNameTitle = scope.alias;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
var directive = {
|
||||
|
||||
+15
-2
@@ -26,6 +26,7 @@
|
||||
fileManager.setFiles({
|
||||
propertyAlias: vm.propertyAlias,
|
||||
culture: vm.culture,
|
||||
segment: vm.segment,
|
||||
files: []
|
||||
});
|
||||
//clear the current files
|
||||
@@ -92,6 +93,11 @@
|
||||
vm.culture = null;
|
||||
}
|
||||
|
||||
//normalize segment to null if it's not there
|
||||
if (!vm.segment) {
|
||||
vm.segment = null;
|
||||
}
|
||||
|
||||
// TODO: need to figure out what we can do for things like Nested Content
|
||||
|
||||
var existingClientFiles = checkPendingClientFiles();
|
||||
@@ -134,11 +140,16 @@
|
||||
vm.culture = null;
|
||||
}
|
||||
|
||||
//normalize segment to null if it's not there
|
||||
if (!vm.segment) {
|
||||
vm.segment = null;
|
||||
}
|
||||
|
||||
//check the file manager to see if there's already local files pending for this editor
|
||||
var existingClientFiles = _.map(
|
||||
_.filter(fileManager.getFiles(),
|
||||
function (f) {
|
||||
return f.alias === vm.propertyAlias && f.culture === vm.culture;
|
||||
return f.alias === vm.propertyAlias && f.culture === vm.culture && f.segment === vm.segment;
|
||||
}),
|
||||
function (f) {
|
||||
return f.file;
|
||||
@@ -264,7 +275,8 @@
|
||||
fileManager.setFiles({
|
||||
propertyAlias: vm.propertyAlias,
|
||||
files: args.files,
|
||||
culture: vm.culture
|
||||
culture: vm.culture,
|
||||
segment: vm.segment
|
||||
});
|
||||
|
||||
updateModelFromSelectedFiles(args.files).then(function(newVal) {
|
||||
@@ -287,6 +299,7 @@
|
||||
templateUrl: 'views/components/upload/umb-property-file-upload.html',
|
||||
bindings: {
|
||||
culture: "@?",
|
||||
segment: "@?",
|
||||
propertyAlias: "@",
|
||||
value: "<",
|
||||
hideSelection: "<",
|
||||
|
||||
@@ -12,6 +12,9 @@
|
||||
*/
|
||||
function clipboardService(notificationsService, eventsService, localStorageService, iconHelper) {
|
||||
|
||||
|
||||
var clearPropertyResolvers = [];
|
||||
|
||||
|
||||
var STORAGE_KEY = "umbClipboardService";
|
||||
|
||||
@@ -53,13 +56,32 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
return false;
|
||||
}
|
||||
|
||||
var prepareEntryForStorage = function(entryData) {
|
||||
|
||||
var shallowCloneData = Object.assign({}, entryData);// Notice only a shallow copy, since we dont need to deep copy. (that will happen when storing the data)
|
||||
delete shallowCloneData.key;
|
||||
delete shallowCloneData.$$hashKey;
|
||||
|
||||
return shallowCloneData;
|
||||
function clearPropertyForStorage(prop) {
|
||||
|
||||
for (var i=0; i<clearPropertyResolvers.length; i++) {
|
||||
clearPropertyResolvers[i](prop, clearPropertyForStorage);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var prepareEntryForStorage = function(entryData, firstLevelClearupMethod) {
|
||||
|
||||
var cloneData = Utilities.copy(entryData);
|
||||
if (firstLevelClearupMethod != undefined) {
|
||||
firstLevelClearupMethod(cloneData);
|
||||
}
|
||||
|
||||
// remove keys from sub-entries
|
||||
for (var t = 0; t < cloneData.variants[0].tabs.length; t++) {
|
||||
var tab = cloneData.variants[0].tabs[t];
|
||||
for (var p = 0; p < tab.properties.length; p++) {
|
||||
var prop = tab.properties[p];
|
||||
clearPropertyForStorage(prop);
|
||||
}
|
||||
}
|
||||
|
||||
return cloneData;
|
||||
}
|
||||
|
||||
var isEntryCompatible = function(entry, type, allowedAliases) {
|
||||
@@ -75,6 +97,38 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
|
||||
var service = {};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.clipboardService#registrerClearPropertyResolver
|
||||
* @methodOf umbraco.services.clipboardService
|
||||
*
|
||||
* @param {string} function A method executed for every property and inner properties copied.
|
||||
*
|
||||
* @description
|
||||
* Executed for all properties including inner properties when performing a copy action.
|
||||
*
|
||||
* @deprecated Incorrect spelling please use 'registerClearPropertyResolver'
|
||||
*/
|
||||
service.registrerClearPropertyResolver = function(resolver) {
|
||||
this.registerClearPropertyResolver(resolver);
|
||||
};
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.clipboardService#registerClearPropertyResolver
|
||||
* @methodOf umbraco.services.clipboardService
|
||||
*
|
||||
* @param {string} function A method executed for every property and inner properties copied.
|
||||
*
|
||||
* @description
|
||||
* Executed for all properties including inner properties when performing a copy action.
|
||||
*/
|
||||
service.registerClearPropertyResolver = function(resolver) {
|
||||
clearPropertyResolvers.push(resolver);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.clipboardService#copy
|
||||
@@ -84,15 +138,19 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
* @param {string} alias A string defining the alias of the data to store, example: 'product'
|
||||
* @param {object} entry A object containing the properties to be saved, this could be the object of a ElementType, ContentNode, ...
|
||||
* @param {string} displayLabel (optional) A string swetting the label to display when showing paste entries.
|
||||
* @param {string} displayIcon (optional) A string setting the icon to display when showing paste entries.
|
||||
* @param {string} uniqueKey (optional) A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data.
|
||||
*
|
||||
* @description
|
||||
* Saves a single JS-object with a type and alias to the clipboard.
|
||||
*/
|
||||
service.copy = function(type, alias, data, displayLabel) {
|
||||
service.copy = function(type, alias, data, displayLabel, displayIcon, uniqueKey, firstLevelClearupMethod) {
|
||||
|
||||
var storage = retriveStorage();
|
||||
|
||||
var uniqueKey = data.key || data.$$hashKey || console.error("missing unique key for this content");
|
||||
displayLabel = displayLabel || data.name;
|
||||
displayIcon = displayIcon || iconHelper.convertFromLegacyIcon(data.icon);
|
||||
uniqueKey = uniqueKey || data.key || console.error("missing unique key for this content");
|
||||
|
||||
// remove previous copies of this entry:
|
||||
storage.entries = storage.entries.filter(
|
||||
@@ -101,7 +159,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
}
|
||||
);
|
||||
|
||||
var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data), label:displayLabel || data.name, icon:iconHelper.convertFromLegacyIcon(data.icon)};
|
||||
var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data, firstLevelClearupMethod), label:displayLabel, icon:displayIcon};
|
||||
storage.entries.push(entry);
|
||||
|
||||
if (saveStorage(storage) === true) {
|
||||
@@ -124,16 +182,17 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
* @param {string} displayLabel A string setting the label to display when showing paste entries.
|
||||
* @param {string} displayIcon A string setting the icon to display when showing paste entries.
|
||||
* @param {string} uniqueKey A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data.
|
||||
* @param {string} firstLevelClearupMethod A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data.
|
||||
*
|
||||
* @description
|
||||
* Saves a single JS-object with a type and alias to the clipboard.
|
||||
*/
|
||||
service.copyArray = function(type, aliases, datas, displayLabel, displayIcon, uniqueKey) {
|
||||
service.copyArray = function(type, aliases, datas, displayLabel, displayIcon, uniqueKey, firstLevelClearupMethod) {
|
||||
|
||||
var storage = retriveStorage();
|
||||
|
||||
// Clean up each entry
|
||||
var copiedDatas = datas.map(data => prepareEntryForStorage(data));
|
||||
var copiedDatas = datas.map(data => prepareEntryForStorage(data, firstLevelClearupMethod));
|
||||
|
||||
// remove previous copies of this entry:
|
||||
storage.entries = storage.entries.filter(
|
||||
|
||||
@@ -39,18 +39,22 @@ function fileManager($rootScope) {
|
||||
args.culture = null;
|
||||
}
|
||||
|
||||
if (!args.segment) {
|
||||
args.segment = null;
|
||||
}
|
||||
|
||||
var metaData = [];
|
||||
if (Utilities.isArray(args.metaData)) {
|
||||
metaData = args.metaData;
|
||||
}
|
||||
|
||||
//this will clear the files for the current property/culture and then add the new ones for the current property
|
||||
//this will clear the files for the current property/culture/segment and then add the new ones for the current property
|
||||
fileCollection = _.reject(fileCollection, function (item) {
|
||||
return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture);
|
||||
return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture) && (!args.segment || args.segment === item.segment);
|
||||
});
|
||||
for (var i = 0; i < args.files.length; i++) {
|
||||
//save the file object to the files collection
|
||||
fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData });
|
||||
fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, segment: args.segment, metaData: metaData });
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -252,12 +252,13 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe
|
||||
for (var f in args.files) {
|
||||
//each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key
|
||||
// so we know which property it belongs to on the server side
|
||||
var fileKey = "file_" + args.files[f].alias + "_" + (args.files[f].culture ? args.files[f].culture : "");
|
||||
var file = args.files[f];
|
||||
var fileKey = "file_" + file.alias + "_" + (file.culture ? file.culture : "") + "_" + (file.segment ? file.segment : "");
|
||||
|
||||
if (Utilities.isArray(args.files[f].metaData) && args.files[f].metaData.length > 0) {
|
||||
fileKey += ("_" + args.files[f].metaData.join("_"));
|
||||
if (Utilities.isArray(file.metaData) && file.metaData.length > 0) {
|
||||
fileKey += ("_" + file.metaData.join("_"));
|
||||
}
|
||||
formData.append(fileKey, args.files[f].file);
|
||||
formData.append(fileKey, file.file);
|
||||
}
|
||||
}).then(function (response) {
|
||||
//success callback
|
||||
|
||||
@@ -227,6 +227,7 @@
|
||||
@import "dashboards/umbraco-forms.less";
|
||||
@import "dashboards/examine-management.less";
|
||||
@import "dashboards/healthcheck.less";
|
||||
@import "dashboards/content-templates.less";
|
||||
@import "dashboards/nucache.less";
|
||||
|
||||
@import "typeahead.less";
|
||||
|
||||
@@ -18,6 +18,8 @@ label.umb-form-check--checkbox{
|
||||
}
|
||||
.umb-form-check__info {
|
||||
margin-left:20px;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,6 +48,9 @@ label.umb-form-check--checkbox{
|
||||
&:checked ~ .umb-form-check__state .umb-form-check__check {
|
||||
border-color: @ui-option-type;
|
||||
}
|
||||
&[type='checkbox']:checked ~ .umb-form-check__state .umb-form-check__check {
|
||||
background-color: @ui-option-type;
|
||||
}
|
||||
&:checked:hover ~ .umb-form-check__state .umb-form-check__check {
|
||||
&::before {
|
||||
background: @ui-option-type-hover;
|
||||
|
||||
@@ -32,14 +32,18 @@
|
||||
.umb-lightbox__close {
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
right: 60px;
|
||||
right: 20px;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.umb-lightbox__close i {
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
height: inherit;
|
||||
width: inherit;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.umb-lightbox__images {
|
||||
@@ -75,12 +79,20 @@
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
|
||||
.umb-lightbox__control-icon {
|
||||
margin-right: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-lightbox__control.-prev {
|
||||
left: 20px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
|
||||
.umb-lightbox__control-icon {
|
||||
margin-left: -4px;
|
||||
}
|
||||
}
|
||||
|
||||
.umb-lightbox__control-icon {
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
.content-templates-dashboard{
|
||||
p{
|
||||
line-height: 1.6em;
|
||||
margin-bottom: 30px;
|
||||
|
||||
&:last-child{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
ul{
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
li{
|
||||
margin-bottom: 5px;
|
||||
|
||||
&:last-child{
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,7 @@
|
||||
position: relative;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
margin-left: auto;
|
||||
|
||||
a {
|
||||
opacity: .5;
|
||||
@@ -134,8 +135,8 @@
|
||||
.password-text {
|
||||
background-repeat: no-repeat;
|
||||
background-size: 18px;
|
||||
background-position: left center;
|
||||
padding-left: 26px;
|
||||
background-position: 0px 1px;
|
||||
padding-left: 24px;
|
||||
|
||||
&.show {
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M16 6C9 6 3 10 0 16c3 6 9 10 16 10s13-4 16-10c-3-6-9-10-16-10zm8 5.3c1.8 1.2 3.4 2.8 4.6 4.7-1.2 2-2.8 3.5-4.7 4.7-3 1.5-6 2.3-8 2.3s-6-.8-8-2.3C6 19.5 4 18 3 16c1.5-2 3-3.5 5-4.7l.6-.2C8 12 8 13 8 14c0 4.5 3.5 8 8 8s8-3.5 8-8c0-1-.3-2-.6-2.6l.4.3zM16 13c0 1.7-1.3 3-3 3s-3-1.3-3-3 1.3-3 3-3 3 1.3 3 3z'/%3E%3C/svg%3E");
|
||||
|
||||
@@ -863,11 +863,11 @@
|
||||
.bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;}
|
||||
.umb-datepicker .input-append .add-on{cursor: pointer;}
|
||||
.umb-datepicker .input-append .on-top {
|
||||
border: 0 none;
|
||||
position: absolute;
|
||||
margin-left: -31px;
|
||||
margin-top: 1px;
|
||||
display: inline-block;
|
||||
height: 22px;
|
||||
padding: 5px 6px 3px 6px;
|
||||
font-size: @baseFontSize;
|
||||
font-weight: normal;
|
||||
|
||||
@@ -56,12 +56,14 @@ function MainController($scope, $location, appState, treeService, notificationsS
|
||||
appState.setSearchState("show", false);
|
||||
};
|
||||
|
||||
$scope.showLoginScreen = function(isTimedOut) {
|
||||
$scope.showLoginScreen = function (isTimedOut) {
|
||||
$scope.login.pageTitle = $scope.$root.locationTitle;
|
||||
$scope.login.isTimedOut = isTimedOut;
|
||||
$scope.login.show = true;
|
||||
};
|
||||
|
||||
$scope.hideLoginScreen = function() {
|
||||
$scope.hideLoginScreen = function () {
|
||||
$scope.$root.locationTitle = $scope.login.pageTitle;
|
||||
$scope.login.show = false;
|
||||
};
|
||||
|
||||
|
||||
+5
-6
@@ -18,15 +18,14 @@
|
||||
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
|
||||
|
||||
<div ng-if="!vm.loading">
|
||||
|
||||
<a href="" tabindex="0" umb-auto-focus></a>
|
||||
<ul class="umb-card-grid -three-in-row">
|
||||
<li ng-repeat="dataTypeConfig in vm.configs | orderBy:'name'"
|
||||
data-element="datatypeconfig-{{dataTypeConfig.name}}"
|
||||
ng-click="vm.pickDataType(dataTypeConfig)">
|
||||
data-element="datatypeconfig-{{dataTypeConfig.name}}">
|
||||
<div ng-if="dataTypeConfig.loading" class="umb-card-grid-item__loading">
|
||||
<div class="umb-button__progress"></div>
|
||||
</div>
|
||||
<a class="umb-card-grid-item" href="" title="{{ dataTypeConfig.name }}">
|
||||
<a class="umb-card-grid-item" href="" ng-click="vm.pickDataType(dataTypeConfig)" title="{{ dataTypeConfig.name }}">
|
||||
<span>
|
||||
<i class="{{ dataTypeConfig.icon }}" ng-class="{'icon-autofill': dataTypeConfig.icon == null}"></i>
|
||||
{{ dataTypeConfig.name }}
|
||||
@@ -35,8 +34,8 @@
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="umb-card-grid -three-in-row">
|
||||
<li ng-click="vm.newDataType()">
|
||||
<a class="umb-card-grid-item-slot" href="" title="Create a new configuration of {{ model.dataType.name }}">
|
||||
<li>
|
||||
<a class="umb-card-grid-item-slot" href="" ng-click="vm.newDataType()" title="Create a new configuration of {{ model.editor.name }}">
|
||||
<span>
|
||||
<i class="icon icon-add"></i>
|
||||
Create new
|
||||
|
||||
+6
-9
@@ -37,9 +37,8 @@
|
||||
<h5>{{key | umbCmsTitleCase}}</h5>
|
||||
<ul class="umb-card-grid -six-in-row">
|
||||
<li ng-repeat="dataType in value | orderBy:'name'"
|
||||
data-element="datatype-{{dataType.name}}"
|
||||
ng-click="vm.viewOptionsForEditor(dataType)">
|
||||
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
|
||||
data-element="datatype-{{dataType.name}}">
|
||||
<a class="umb-card-grid-item" href="" ng-click="vm.viewOptionsForEditor(dataType)" title="{{ dataType.name }}">
|
||||
<span>
|
||||
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
|
||||
{{ dataType.name }}
|
||||
@@ -58,12 +57,11 @@
|
||||
<h5>{{result.group | umbCmsTitleCase}}</h5>
|
||||
<ul class="umb-card-grid -six-in-row">
|
||||
<li ng-repeat="dataType in result.entries | orderBy:'name'"
|
||||
ng-mouseover="vm.showDetailsOverlay(dataType)"
|
||||
ng-click="vm.pickDataType(dataType)">
|
||||
ng-mouseover="vm.showDetailsOverlay(dataType)">
|
||||
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
|
||||
<div class="umb-button__progress"></div>
|
||||
</div>
|
||||
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
|
||||
<a class="umb-card-grid-item" href="" ng-click="vm.pickDataType(dataType)" title="{{ dataType.name }}">
|
||||
<span>
|
||||
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
|
||||
{{ dataType.name }}
|
||||
@@ -78,9 +76,8 @@
|
||||
<div ng-if="result.entries.length > 0">
|
||||
<h5>{{result.group | umbCmsTitleCase}}</h5>
|
||||
<ul class="umb-card-grid -six-in-row">
|
||||
<li ng-repeat="dataType in result.entries | orderBy:'name'"
|
||||
ng-click="vm.pickEditor(dataType)">
|
||||
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
|
||||
<li ng-repeat="dataType in result.entries | orderBy:'name'">
|
||||
<a class="umb-card-grid-item" href="" ng-click="vm.pickEditor(dataType)" title="{{ dataType.name }}">
|
||||
<span>
|
||||
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
|
||||
{{ dataType.name }}
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function ConfirmController($scope, userService) {
|
||||
|
||||
var vm = this;
|
||||
vm.userEmailAddress = "";
|
||||
|
||||
userService.getCurrentUser().then(function(user){
|
||||
vm.userEmailAddress = user.email;
|
||||
});
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Tours.UmbEmailMarketing.ConfirmController", ConfirmController);
|
||||
})();
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<div ng-controller="Umbraco.Tours.UmbEmailMarketing.ConfirmController as vm">
|
||||
|
||||
<umb-tour-step-header title="model.currentStep.title"></umb-tour-step-header>
|
||||
|
||||
<umb-tour-step on-close="model.completeTour()">
|
||||
<p>We have sent a welcome email to your email address <strong>{{ vm.userEmailAddress }}</strong></p>
|
||||
</umb-tour-step>
|
||||
|
||||
<umb-tour-step-footer>
|
||||
<div class="flex justify-end">
|
||||
<umb-button type="button" button-style="link" action="model.completeTour()" label-key="general_close" shortcut="esc"></umb-button>
|
||||
</div>
|
||||
</umb-tour-step-footer>
|
||||
|
||||
</div>
|
||||
+1
-4
@@ -13,10 +13,7 @@
|
||||
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();
|
||||
$scope.model.nextStep();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,16 +149,16 @@
|
||||
<form method="POST" name="vm.loginForm" ng-submit="vm.loginSubmit()">
|
||||
|
||||
<div ng-messages="vm.loginForm.$error" class="control-group" aria-live="assertive">
|
||||
<p ng-message="auth" class="text-error" role="alert">{{vm.errorMsg}}</p>
|
||||
<p ng-message="auth" class="text-error" role="alert" tabindex="0">{{vm.errorMsg}}</p>
|
||||
</div>
|
||||
<div class="control-group" ng-class="{error: vm.loginForm.username.$invalid}">
|
||||
<label for="umb-username">{{vm.labels.usernameLabel}}</label>
|
||||
<input type="text" ng-model="vm.login" name="username" id="umb-username" class="-full-width-input" placeholder="{{vm.labels.usernamePlaceholder}}" focus-when="{{vm.view === 'login'}}" />
|
||||
<input type="text" ng-model="vm.login" name="username" id="umb-username" class="-full-width-input" placeholder="{{vm.labels.usernamePlaceholder}}" focus-when="{{vm.view === 'login'}}" aria-required="true"/>
|
||||
</div>
|
||||
|
||||
<div class="control-group" ng-class="{error: vm.loginForm.password.$invalid}">
|
||||
<label for="umb-passwordTwo"><localize key="general_password">Password</localize></label>
|
||||
<input type="password" ng-model="vm.password" name="password" id="umb-passwordTwo" class="-full-width-input" localize="placeholder" placeholder="@placeholders_password" />
|
||||
<input type="password" ng-model="vm.password" name="password" id="umb-passwordTwo" class="-full-width-input" localize="placeholder" placeholder="@placeholders_password" aria-required="true" />
|
||||
<div class="password-toggle">
|
||||
<a href="#" prevent-default ng-click="vm.togglePassword()">
|
||||
<span class="password-text show"><localize key="login_showPassword">Show password</localize></span>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
{{vm.buttonLabel}}
|
||||
<span ng-if="vm.showCaret" class="umb-button__caret caret" aria-hidden="true"></span>
|
||||
</span>
|
||||
</button
|
||||
</button>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,14 @@
|
||||
<umb-dropdown-item class="umb-action" ng-class="{'sep':action.separatorm, '-opens-dialog': action.opensDialog}" ng-repeat="action in actions">
|
||||
<button type="button" ng-click="executeMenuItem(action)">
|
||||
<i class="icon icon-{{action.cssclass}}" aria-hidden="true"></i>
|
||||
<span class="menu-label">{{action.name}}</span>
|
||||
<!-- Render the text that will be visually displayed -->
|
||||
<span class="menu-label" aria-hidden="true">{{action.name}}</span>
|
||||
<!-- Render the textDescription from the language files if it's attached to the action object-->
|
||||
<span class="sr-only" ng-if="action.textDescription">
|
||||
<localize key="visuallyHiddenTexts_{{action.alias}}Description" tokens="[currentNode.name]"></localize>
|
||||
</span>
|
||||
<!-- Otherwise render a combination of the nodename and the currentNode name-->
|
||||
<span class="sr-only" ng-if="!action.textDescription">{{action.name}} {{currentNode.name}}</span>
|
||||
</button>
|
||||
</umb-dropdown-item>
|
||||
</umb-dropdown>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<label class="checkbox umb-form-check umb-form-check--checkbox {{vm.cssClass}}" ng-class="{ 'umb-form-check--disabled': vm.disabled }">
|
||||
|
||||
<div class="umb-form-check__symbol">
|
||||
<input type="checkbox"
|
||||
<input ng-if="vm.disableDirtyCheck"
|
||||
type="checkbox"
|
||||
id="{{vm.inputId}}"
|
||||
name="{{vm.name}}"
|
||||
value="{{vm.value}}"
|
||||
@@ -10,7 +10,20 @@
|
||||
ng-model="vm.model"
|
||||
ng-disabled="vm.disabled"
|
||||
ng-required="vm.required"
|
||||
ng-change="vm.change()"/>
|
||||
ng-change="vm.change()"
|
||||
no-dirty-check />
|
||||
|
||||
<input ng-if="!vm.disableDirtyCheck"
|
||||
type="checkbox"
|
||||
id="{{vm.inputId}}"
|
||||
name="{{vm.name}}"
|
||||
value="{{vm.value}}"
|
||||
class="umb-form-check__input"
|
||||
val-server-field="{{vm.serverValidationField}}"
|
||||
ng-model="vm.model"
|
||||
ng-disabled="vm.disabled"
|
||||
ng-required="vm.required"
|
||||
ng-change="vm.change()"/>
|
||||
|
||||
<span class="umb-form-check__state" aria-hidden="true">
|
||||
<span class="umb-form-check__check">
|
||||
|
||||
@@ -7,18 +7,19 @@
|
||||
<div ng-if="notification.view">
|
||||
<div ng-include="notification.view"></div>
|
||||
</div>
|
||||
<div ng-if="notification.headline" ng-switch on="{{notification}}">
|
||||
<a ng-href="{{notification.url}}" ng-switch-when="{{notification.url && notification.url.trim() != ''}}" target="_blank">
|
||||
<strong>{{notification.headline}}</strong>
|
||||
|
||||
<div ng-if="notification.headline">
|
||||
<a ng-if="notification.url" ng-href="{{notification.url}}" href="" target="_blank">
|
||||
<strong ng-bind="notification.headline"></strong>
|
||||
<span ng-bind-html="notification.message"></span>
|
||||
</a>
|
||||
<div ng-switch-default>
|
||||
<strong>{{notification.headline}}</strong>
|
||||
<div ng-if="!notification.url">
|
||||
<strong ng-bind="notification.headline"></strong>
|
||||
<span ng-bind-html="notification.message"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class='close -align-right' ng-click="removeNotification($index)" aria-hidden="true">
|
||||
<button type="button" class="close -align-right" ng-click="removeNotification($index)" aria-hidden="true">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<localize key="contentTypeEditor_inheritedFrom"></localize> {{inheritsFrom}}
|
||||
</small>
|
||||
|
||||
<label class="control-label" for="{{property.alias}}" ng-attr-title="{{propertyAlias}}">
|
||||
<label class="control-label" for="{{property.alias}}" ng-attr-title="{{controlLabelTitle}}">
|
||||
|
||||
{{property.label}}
|
||||
|
||||
|
||||
@@ -7,10 +7,16 @@
|
||||
on-outside-click="clickCancel()">
|
||||
|
||||
<button class="umb_confirm-action__overlay-action -confirm btn-reset" ng-click="clickConfirm()" localize="title" title="@buttons_confirmActionConfirm" type="button">
|
||||
<i class="icon-check"></i>
|
||||
<i class="icon-check" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="buttons_confirmActionConfirm">Confirm</localize>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button class="umb_confirm-action__overlay-action -cancel btn-reset" ng-click="clickCancel()" localize="title" title="@buttons_confirmActionCancel" type="button">
|
||||
<i class="icon-delete"></i>
|
||||
<i class="icon-delete" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="buttons_confirmActionCancel">Cancel</localize>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<div class="umb-lightbox">
|
||||
|
||||
<div class="umb-lightbox__backdrop" ng-click="close()" hotkey="esc"></div>
|
||||
<div class="umb-lightbox__close" title="Close" ng-click="close()">
|
||||
<i class="icon-delete umb-lightbox__control"></i>
|
||||
</div>
|
||||
<button type="button" class="btn-reset umb-lightbox__close" localize="title" title="@general_close" ng-click="close()">
|
||||
<i class="icon-delete umb-lightbox__control" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="general_close">Close</localize>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="umb-lightbox__images">
|
||||
<div class="umb-lightbox__image shadow-depth-2" ng-repeat="item in items" ng-show="$index === activeItemIndex">
|
||||
@@ -11,12 +14,18 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-lightbox__control -prev" title="Previous" ng-if="activeItemIndex > 0" ng-click="prev()" hotkey="left">
|
||||
<i class="icon-previous umb-lightbox__control-icon"></i>
|
||||
</div>
|
||||
<button type="button" class="btn-reset umb-lightbox__control -prev" localize="title" title="@general_previous" ng-if="activeItemIndex > 0" ng-click="prev()" hotkey="left">
|
||||
<i class="icon-previous umb-lightbox__control-icon" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="general_previous">Previous</localize>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="umb-lightbox__control -next" title="Next" ng-if="activeItemIndex + 1 < items.length" ng-click="next()" hotkey="right">
|
||||
<i class="icon-next umb-lightbox__control-icon"></i>
|
||||
</div>
|
||||
<button type="button" class="btn-reset umb-lightbox__control -next" localize="title" title="general_next" ng-if="activeItemIndex + 1 < items.length" ng-click="next()" hotkey="right">
|
||||
<i class="icon-next umb-lightbox__control-icon" aria-hidden="true"></i>
|
||||
<span class="sr-only">
|
||||
<localize key="general_next">Next</localize>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -44,6 +44,6 @@
|
||||
aria-label="Edit"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<i ng-if="icon" class="umb-node-preview__icon {{ icon }}" aria-hidden="true"></i>
|
||||
<div class="umb-node-preview__content">
|
||||
|
||||
<div class="umb-node-preview__name" ng-attr-title="{{alias}}">{{ name }}</div>
|
||||
<div class="umb-node-preview__name" ng-attr-title="{{nodeNameTitle}}">{{ name }}</div>
|
||||
<div class="umb-node-preview__description" ng-if="description">{{ description }}</div>
|
||||
|
||||
<div class="umb-user-group-preview__permissions" ng-if="permissions">
|
||||
|
||||
@@ -12,21 +12,32 @@
|
||||
<umb-editor-container>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-content>
|
||||
<h3>What are Content Templates?</h3>
|
||||
<p style="line-height: 1.6em; margin-bottom: 30px;">Content Templates are pre-defined content that can be selected when creating a new content node.</p>
|
||||
<umb-box-content class="content-templates-dashboard">
|
||||
<h3>
|
||||
<localize key="contentTemplatesDashboard_whatHeadline">What are Content Templates?</localize>
|
||||
</h3>
|
||||
<p>
|
||||
<localise key="contentTemplatesDashboard_whatDescription">Content Templates are pre-defined content that can be selected when creating a new content node.</localize>
|
||||
</p>
|
||||
|
||||
<h4 class="bold">How do I create a Content Template?</h4>
|
||||
<p style="line-height: 1.6em;">There are two ways to create a Content Template:</p>
|
||||
<ul style="margin-bottom: 15px;">
|
||||
<li style="margin-bottom: 5px;">Right-click a content node and select "Create Content Template" to create a new Content Template.</li>
|
||||
<li>Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.</li>
|
||||
</ul>
|
||||
<p style="line-height: 1.6em; margin-bottom: 30px;">Once given a name, editors can start using the Content Template as a foundation for their new page.</p>
|
||||
<h4 class="bold">
|
||||
<localize key="contentTemplatesDashboard_createHeadline">How do I create a Content Template?</localize>
|
||||
</h4>
|
||||
<localize key="contentTemplatesDashboard_createDescription">
|
||||
<p>There are two ways to create a Content Template:</p>
|
||||
<ul>
|
||||
<li>Right-click a content node and select "Create Content Template" to create a new Content Template.</li>
|
||||
<li>Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.</li>
|
||||
</ul>
|
||||
<p>Once given a name, editors can start using the Content Template as a foundation for their new page.</p>
|
||||
</localize>
|
||||
|
||||
<h4 class="bold">How do I manage Content Templates</h4>
|
||||
<p style="line-height: 1.6em;">You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Just expand the Document Type which the
|
||||
Content Template is based on and click it to edit or delete it.</p>
|
||||
<h4 class="bold">
|
||||
<localize key="contentTemplatesDashboard_manageHeadline">How do I manage Content Templates?</localize>
|
||||
</h4>
|
||||
<p>
|
||||
<localize key="contentTemplatesDashboard_manageDescription">You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Just expand the Document Type which the Content Template is based on and click it to edit or delete it.</localize>
|
||||
</p>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
|
||||
@@ -20,10 +20,11 @@
|
||||
ng-required="model.validation.mandatory"
|
||||
val-server="value"
|
||||
class="datepickerinput">
|
||||
<span class="on-top" ng-click="clearDate()" ng-show="hasDatetimePickerValue === true || datePickerForm.datepicker.$error.pickerError === true">
|
||||
<i class="icon-delete"></i>
|
||||
</span>
|
||||
<span class="add-on">
|
||||
<button type="button" class="on-top" ng-click="clearDate()" ng-show="hasDatetimePickerValue === true || datePickerForm.datepicker.$error.pickerError === true">
|
||||
<i class="icon-delete" aria-hidden="true"></i>
|
||||
<span class="sr-only"><localize key="content_removeDate">Clear date</localize></span>
|
||||
</button>
|
||||
<span class="add-on" aria-hidden="true">
|
||||
<i class="icon-calendar"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
+8
-1
@@ -15,7 +15,14 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo
|
||||
|
||||
//ensure this is a bool, old data could store zeros/ones or string versions
|
||||
$scope.model.config.multiple = Object.toBoolean($scope.model.config.multiple);
|
||||
|
||||
|
||||
//ensure when form is saved that we don't store [] or [null] as string values in the database when no items are selected
|
||||
$scope.$on("formSubmitting", function () {
|
||||
if ($scope.model.value.length === 0 || $scope.model.value[0] === null) {
|
||||
$scope.model.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
function convertArrayToDictionaryArray(model){
|
||||
//now we need to format the items in the dictionary because we always want to have an array
|
||||
var newItems = [];
|
||||
|
||||
+1
@@ -34,6 +34,7 @@
|
||||
fileManager.setFiles({
|
||||
propertyAlias: $scope.model.alias,
|
||||
culture: $scope.model.culture,
|
||||
segment: $scope.model.segment,
|
||||
files: []
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<div ng-controller="Umbraco.PropertyEditors.FileUploadController">
|
||||
<umb-property-file-upload culture="{{model.culture}}"
|
||||
segment="{{model.segment}}"
|
||||
property-alias="{{model.alias}}"
|
||||
value="model.value"
|
||||
required="model.validation.mandatory"
|
||||
|
||||
+2
@@ -55,6 +55,7 @@ angular.module('umbraco')
|
||||
fileManager.setFiles({
|
||||
propertyAlias: $scope.model.alias,
|
||||
culture: $scope.model.culture,
|
||||
segment: $scope.model.segment,
|
||||
files: []
|
||||
});
|
||||
}
|
||||
@@ -189,6 +190,7 @@ angular.module('umbraco')
|
||||
fileManager.setFiles({
|
||||
propertyAlias: $scope.model.alias,
|
||||
culture: $scope.model.culture,
|
||||
segment: $scope.model.segment,
|
||||
files: []
|
||||
});
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user