Merge branch 'v8/contrib' into pr/7814
This commit is contained in:
+12
-10
@@ -22,7 +22,7 @@ This project and everyone participating in it, is governed by the [our Code of C
|
||||
|
||||
[Reviews](#reviews)
|
||||
* [Styleguides](#styleguides)
|
||||
* [The PR team](#the-pr-team)
|
||||
* [The Core Contributors](#the-core-contributors-team)
|
||||
* [Questions?](#questions)
|
||||
|
||||
[Working with the code](#working-with-the-code)
|
||||
@@ -72,7 +72,7 @@ Great question! The short version goes like this:
|
||||
### Pull requests
|
||||
The most successful pull requests usually look a like this:
|
||||
|
||||
* Fill in the required template, linking your pull request to an issue on the [issue tracker,](https://github.com/umbraco/Umbraco-CMS/issues) if applicable.
|
||||
* Fill in the required template (shown when starting a PR on GitHub), and link your pull request to an issue on the [issue tracker,](https://github.com/umbraco/Umbraco-CMS/issues) if applicable.
|
||||
* Include screenshots and animated GIFs in your pull request whenever possible.
|
||||
* Unit tests, while optional, are awesome. Thank you!
|
||||
* New code is commented with documentation from which [the reference documentation](https://our.umbraco.com/documentation/Reference/) is generated.
|
||||
@@ -98,20 +98,21 @@ To be honest, we don't like rules very much. We trust you have the best of inten
|
||||
|
||||
That said, the Umbraco development team likes to follow the hints that ReSharper gives us (no problem if you don't have this installed) and we've added a `.editorconfig` file so that Visual Studio knows what to do with whitespace, line endings, etc.
|
||||
|
||||
### The PR team
|
||||
### The Core Contributors team
|
||||
|
||||
The pull request team consists of one member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members who have comitted to volunteering their free time:
|
||||
The Core Contributors team consists of one member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members who have comitted to volunteering their free time:
|
||||
|
||||
- [Anders Bjerner](https://github.com/abjerner)
|
||||
- [Dave Woestenborghs](https://github.com/dawoe)
|
||||
- [Emma Burstow](https://github.com/emmaburstow)
|
||||
- [Poornima Nayar](https://github.com/poornimanayar)
|
||||
- [Kenn Jacobsen](https://twitter.com/KennJacobsen_DK)
|
||||
|
||||
|
||||
These wonderful people aim to provide you with a first reply to your PR, review and test out your changes and on occasions, they might ask more questions. If they are happy with your work, they'll let Umbraco HQ know by approving the PR. Hq will have final sign-off and will check the work again before it is merged.
|
||||
|
||||
### Questions?
|
||||
|
||||
You can get in touch with [the PR team](#the-pr-team) in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
|
||||
You can get in touch with [the core contributors team](#the-core-contributors-team) in multiple ways; we love open conversations and we are a friendly bunch. No question you have is stupid. Any question you have usually helps out multiple people with the same question. Ask away:
|
||||
|
||||
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward.
|
||||
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum. The team monitors that one closely, so one of us will be on hand and ready to point you in the right direction.
|
||||
@@ -122,9 +123,10 @@ You can get in touch with [the PR team](#the-pr-team) in multiple ways; we love
|
||||
|
||||
In order to build the Umbraco source code locally, first make sure you have the following installed.
|
||||
|
||||
* Visual Studio 2017 v15.9.7+
|
||||
* Node v10+
|
||||
* npm v6.4.1+
|
||||
* [Visual Studio 2017 v15.9.7+](https://visualstudio.microsoft.com/vs/)
|
||||
* [Node.js v10+](https://nodejs.org/en/download/)
|
||||
* npm v6.4.1+ (installed with Node.js)
|
||||
* [Git command line](https://git-scm.com/download/)
|
||||
|
||||
The easiest way to get started is to open `src\umbraco.sln` in Visual Studio 2017 (version 15.9.7 or higher, [the community edition is free](https://www.visualstudio.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) for you to use to contribute to Open Source projects). In Visual Studio, find the Task Runner Explorer (in the View menu under Other Windows) and run the build task under the gulpfile.
|
||||
|
||||
@@ -193,4 +195,4 @@ In this command we're syncing with the `v8/contrib` branch, but you can of cours
|
||||
|
||||
### And finally
|
||||
|
||||
We welcome all kinds of contributions to this repository. If you don't feel you'd like to make code changes here, you can visit our [documentation repository](https://github.com/umbraco/UmbracoDocs) and use your experience to contribute to making the docs we have, even better. We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the PR team and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
|
||||
We welcome all kinds of contributions to this repository. If you don't feel you'd like to make code changes here, you can visit our [documentation repository](https://github.com/umbraco/UmbracoDocs) and use your experience to contribute to making the docs we have, even better. We also encourage community members to feel free to comment on others' pull requests and issues - the expertise we have is not limited to the Core Contributors and HQ. So, if you see something on the issue tracker or pull requests you feel you can add to, please don't be shy.
|
||||
|
||||
@@ -100,6 +100,9 @@ src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/loader.dev.js
|
||||
src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.js
|
||||
src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/app.js
|
||||
src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/canvasdesigner.*.js
|
||||
src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/navigation.controller.js
|
||||
src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/main.controller.js
|
||||
src/Umbraco.Web.UI/[Uu]mbraco/[Jj]s/utilities.js
|
||||
|
||||
src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/
|
||||
src/Umbraco.Web.UI/[Uu]mbraco/[Vv]iews/**/*.js
|
||||
|
||||
@@ -7,6 +7,5 @@
|
||||
-->
|
||||
<packageSources>
|
||||
<add key="UmbracoCoreMyGet" value="https://www.myget.org/F/umbracocore/api/v3/index.json" />
|
||||
<add key="ExamineAppVeyor" value="https://ci.appveyor.com/nuget/examine-f73l6qv0oqfh/" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
not want this to happen as the alpha of the next major is, really, the next major already.
|
||||
-->
|
||||
<dependency id="UmbracoCms.Core" version="[$version$]" />
|
||||
<dependency id="ClientDependency" version="[1.9.8,1.999999)" />
|
||||
<dependency id="ClientDependency" version="[1.9.9,1.999999)" />
|
||||
<dependency id="ClientDependency-Mvc5" version="[1.9.3,1.999999)" />
|
||||
<dependency id="CSharpTest.Net.Collections" version="[14.906.1403.1082,14.999999)" />
|
||||
<dependency id="Examine" version="[1.0.2,1.999999)" />
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=5.2.7.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" xdt:Locator="Match(factoryType)" xdt:Transform="SetAttributes(factoryType)" />
|
||||
<pages pageBaseType="System.Web.Mvc.WebViewPage">
|
||||
<namespaces>
|
||||
<add namespace="Umbraco.Web.PublishedContentModels" xdt:Transform="InsertIfMissing" />
|
||||
<add namespace="Umbraco.Web.PublishedModels" xdt:Transform="InsertIfMissing" />
|
||||
</namespaces>
|
||||
</pages>
|
||||
</system.web.webPages.razor>
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
# get NuGet
|
||||
$cache = 4
|
||||
$nuget = "$scriptTemp\nuget.exe"
|
||||
# ensure the correct NuGet-source is used. This one is used by Umbraco
|
||||
$nugetsourceUmbraco = "https://www.myget.org/F/umbracocore/api/v3/index.json"
|
||||
if (-not $local)
|
||||
{
|
||||
$source = "https://dist.nuget.org/win-x86-commandline/latest/nuget.exe"
|
||||
@@ -61,7 +63,7 @@
|
||||
# get the build system
|
||||
if (-not $local)
|
||||
{
|
||||
$params = "-OutputDirectory", $scriptTemp, "-Verbosity", "quiet", "-PreRelease"
|
||||
$params = "-OutputDirectory", $scriptTemp, "-Verbosity", "quiet", "-PreRelease", "-Source", $nugetsourceUmbraco
|
||||
&$nuget install Umbraco.Build @params
|
||||
if (-not $?) { throw "Failed to download Umbraco.Build." }
|
||||
}
|
||||
|
||||
+4
-1
@@ -375,11 +375,14 @@
|
||||
|
||||
})
|
||||
|
||||
$nugetsourceUmbraco = "https://api.nuget.org/v3/index.json"
|
||||
|
||||
$ubuild.DefineMethod("RestoreNuGet",
|
||||
{
|
||||
Write-Host "Restore NuGet"
|
||||
Write-Host "Logging to $($this.BuildTemp)\nuget.restore.log"
|
||||
&$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log"
|
||||
$params = "-Source", $nugetsourceUmbraco
|
||||
&$this.BuildEnv.NuGet restore "$($this.SolutionRoot)\src\Umbraco.sln" > "$($this.BuildTemp)\nuget.restore.log" @params
|
||||
if (-not $?) { throw "Failed to restore NuGet packages." }
|
||||
})
|
||||
|
||||
|
||||
@@ -37,23 +37,13 @@ namespace Umbraco.Core.Cache
|
||||
/// <inheritdoc />
|
||||
public object Get(string key, Func<object> factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
{
|
||||
CacheDependency dependency = null;
|
||||
if (dependentFiles != null && dependentFiles.Any())
|
||||
{
|
||||
dependency = new CacheDependency(dependentFiles);
|
||||
}
|
||||
return Get(key, factory, timeout, isSliding, priority, removedCallback, dependency);
|
||||
return GetInternal(key, factory, timeout, isSliding, priority, removedCallback, dependentFiles);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Insert(string key, Func<object> factory, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
{
|
||||
CacheDependency dependency = null;
|
||||
if (dependentFiles != null && dependentFiles.Any())
|
||||
{
|
||||
dependency = new CacheDependency(dependentFiles);
|
||||
}
|
||||
Insert(key, factory, timeout, isSliding, priority, removedCallback, dependency);
|
||||
InsertInternal(key, factory, timeout, isSliding, priority, removedCallback, dependentFiles);
|
||||
}
|
||||
|
||||
#region Dictionary
|
||||
@@ -103,7 +93,7 @@ namespace Umbraco.Core.Cache
|
||||
|
||||
#endregion
|
||||
|
||||
private object Get(string key, Func<object> factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null)
|
||||
private object GetInternal(string key, Func<object> factory, TimeSpan? timeout, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
{
|
||||
key = GetCacheKey(key);
|
||||
|
||||
@@ -163,6 +153,10 @@ namespace Umbraco.Core.Cache
|
||||
var sliding = isSliding == false ? System.Web.Caching.Cache.NoSlidingExpiration : (timeout ?? System.Web.Caching.Cache.NoSlidingExpiration);
|
||||
|
||||
lck.UpgradeToWriteLock();
|
||||
|
||||
// create a cache dependency if one is needed.
|
||||
var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null;
|
||||
|
||||
//NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update!
|
||||
_cache.Insert(key, result, dependency, absolute, sliding, priority, removedCallback);
|
||||
}
|
||||
@@ -180,7 +174,7 @@ namespace Umbraco.Core.Cache
|
||||
return value;
|
||||
}
|
||||
|
||||
private void Insert(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, CacheDependency dependency = null)
|
||||
private void InsertInternal(string cacheKey, Func<object> getCacheItem, TimeSpan? timeout = null, bool isSliding = false, CacheItemPriority priority = CacheItemPriority.Normal, CacheItemRemovedCallback removedCallback = null, string[] dependentFiles = null)
|
||||
{
|
||||
// NOTE - here also we must insert a Lazy<object> but we can evaluate it right now
|
||||
// and make sure we don't store a null value.
|
||||
@@ -197,6 +191,10 @@ namespace Umbraco.Core.Cache
|
||||
try
|
||||
{
|
||||
_locker.EnterWriteLock();
|
||||
|
||||
// create a cache dependency if one is needed.
|
||||
var dependency = dependentFiles != null && dependentFiles.Length > 0 ? new CacheDependency(dependentFiles) : null;
|
||||
|
||||
//NOTE: 'Insert' on System.Web.Caching.Cache actually does an add or update!
|
||||
_cache.Insert(cacheKey, result, dependency, absolute, sliding, priority, removedCallback);
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ namespace Umbraco.Core
|
||||
public const string UnknownUserName = "SYTEM";
|
||||
|
||||
public const string AdminGroupAlias = "admin";
|
||||
public const string EditorGroupAlias = "editor";
|
||||
public const string SensitiveDataGroupAlias = "sensitiveData";
|
||||
public const string TranslatorGroupAlias = "translator";
|
||||
public const string WriterGroupAlias = "writer";
|
||||
|
||||
public const string BackOfficeAuthenticationType = "UmbracoBackOffice";
|
||||
public const string BackOfficeExternalAuthenticationType = "UmbracoExternalCookie";
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
public static partial class Constants
|
||||
{
|
||||
public static class SqlTemplates
|
||||
{
|
||||
public static class VersionableRepository
|
||||
{
|
||||
public const string GetVersionIds = "Umbraco.Core.VersionableRepository.GetVersionIds";
|
||||
public const string GetVersion = "Umbraco.Core.VersionableRepository.GetVersion";
|
||||
public const string GetVersions = "Umbraco.Core.VersionableRepository.GetVersions";
|
||||
public const string EnsureUniqueNodeName = "Umbraco.Core.VersionableRepository.EnsureUniqueNodeName";
|
||||
public const string GetSortOrder = "Umbraco.Core.VersionableRepository.GetSortOrder";
|
||||
public const string GetParentNode = "Umbraco.Core.VersionableRepository.GetParentNode";
|
||||
public const string GetReservedId = "Umbraco.Core.VersionableRepository.GetReservedId";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +60,19 @@ namespace Umbraco.Core
|
||||
|
||||
#endregion
|
||||
|
||||
internal static bool IsMoving(this IContentBase entity)
|
||||
{
|
||||
// Check if this entity is being moved as a descendant as part of a bulk moving operations.
|
||||
// When this occurs, only Path + Level + UpdateDate are being changed. In this case we can bypass a lot of the below
|
||||
// operations which will make this whole operation go much faster. When moving we don't need to create
|
||||
// new versions, etc... because we cannot roll this operation back anyways.
|
||||
var isMoving = entity.IsPropertyDirty(nameof(entity.Path))
|
||||
&& entity.IsPropertyDirty(nameof(entity.Level))
|
||||
&& entity.IsPropertyDirty(nameof(entity.UpdateDate));
|
||||
|
||||
return isMoving;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes characters that are not valid XML characters from all entity properties
|
||||
/// of type string. See: http://stackoverflow.com/a/961504/5018
|
||||
|
||||
@@ -171,8 +171,8 @@ namespace Umbraco.Core.Migrations.Install
|
||||
private void CreateUserGroupData()
|
||||
{
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 1, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.AdminGroupAlias, Name = "Administrators", DefaultPermissions = "CADMOSKTPIURZ:5F7ï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-medal" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = "writer", Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = "editor", Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 2, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.WriterGroupAlias, Name = "Writers", DefaultPermissions = "CAH:F", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-edit" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 3, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.EditorGroupAlias, Name = "Editors", DefaultPermissions = "CADMOSKTPUZ:5Fï", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-tools" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 4, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.TranslatorGroupAlias, Name = "Translators", DefaultPermissions = "AF", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-globe" });
|
||||
_database.Insert(Constants.DatabaseSchema.Tables.UserGroup, "id", false, new UserGroupDto { Id = 5, StartMediaId = -1, StartContentId = -1, Alias = Constants.Security.SensitiveDataGroupAlias, Name = "Sensitive data", DefaultPermissions = "", CreateDate = DateTime.Now, UpdateDate = DateTime.Now, Icon = "icon-lock" });
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ namespace Umbraco.Core.Migrations.Upgrade
|
||||
.As("{0576E786-5C30-4000-B969-302B61E90CA3}");
|
||||
|
||||
To<FixLanguageIsoCodeLength>("{48AD6CCD-C7A4-4305-A8AB-38728AD23FC5}");
|
||||
To<AddPackagesSectionAccess>("{DF470D86-E5CA-42AC-9780-9D28070E25F9}");
|
||||
|
||||
// finish migrating from v7 - recreate all keys and indexes
|
||||
To<CreateKeysAndIndexes>("{3F9764F5-73D0-4D45-8804-1240A66E43A2}");
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0
|
||||
{
|
||||
public class AddPackagesSectionAccess : MigrationBase
|
||||
{
|
||||
public AddPackagesSectionAccess(IMigrationContext context)
|
||||
: base(context)
|
||||
{ }
|
||||
|
||||
public override void Migrate()
|
||||
{
|
||||
// Any user group which had access to the Developer section should have access to Packages
|
||||
Database.Execute($@"
|
||||
insert into {Constants.DatabaseSchema.Tables.UserGroup2App}
|
||||
select userGroupId, '{Constants.Applications.Packages}'
|
||||
from {Constants.DatabaseSchema.Tables.UserGroup2App}
|
||||
where app='developer'");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public class ContentDataIntegrityReport
|
||||
{
|
||||
public ContentDataIntegrityReport(IReadOnlyDictionary<int, ContentDataIntegrityReportEntry> detectedIssues)
|
||||
{
|
||||
DetectedIssues = detectedIssues;
|
||||
}
|
||||
|
||||
public bool Ok => DetectedIssues.Count == 0 || DetectedIssues.Count == DetectedIssues.Values.Count(x => x.Fixed);
|
||||
|
||||
public IReadOnlyDictionary<int, ContentDataIntegrityReportEntry> DetectedIssues { get; }
|
||||
|
||||
public IReadOnlyDictionary<int, ContentDataIntegrityReportEntry> FixedIssues
|
||||
=> DetectedIssues.Where(x => x.Value.Fixed).ToDictionary(x => x.Key, x => x.Value);
|
||||
|
||||
public enum IssueType
|
||||
{
|
||||
/// <summary>
|
||||
/// The item's level and path are inconsistent with it's parent's path and level
|
||||
/// </summary>
|
||||
InvalidPathAndLevelByParentId,
|
||||
|
||||
/// <summary>
|
||||
/// The item's path doesn't contain all required parts
|
||||
/// </summary>
|
||||
InvalidPathEmpty,
|
||||
|
||||
/// <summary>
|
||||
/// The item's path parts are inconsistent with it's level value
|
||||
/// </summary>
|
||||
InvalidPathLevelMismatch,
|
||||
|
||||
/// <summary>
|
||||
/// The item's path does not end with it's own ID
|
||||
/// </summary>
|
||||
InvalidPathById,
|
||||
|
||||
/// <summary>
|
||||
/// The item's path does not have it's parent Id as the 2nd last entry
|
||||
/// </summary>
|
||||
InvalidPathByParentId,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public class ContentDataIntegrityReportEntry
|
||||
{
|
||||
public ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType issueType)
|
||||
{
|
||||
IssueType = issueType;
|
||||
}
|
||||
|
||||
public ContentDataIntegrityReport.IssueType IssueType { get; }
|
||||
public bool Fixed { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
public class ContentDataIntegrityReportOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Set to true to try to automatically resolve data integrity issues
|
||||
/// </summary>
|
||||
public bool FixIssues { get; set; }
|
||||
|
||||
// TODO: We could define all sorts of options for the data integrity check like what to check for, what to fix, etc...
|
||||
// things like Tag data consistency, etc...
|
||||
}
|
||||
}
|
||||
@@ -81,9 +81,10 @@ namespace Umbraco.Core.Models
|
||||
if (propertyInfo.PropertyType.IsGenericType
|
||||
&& (propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IEnumerable<>)
|
||||
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>)
|
||||
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)))
|
||||
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IList<>)
|
||||
|| propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IReadOnlyCollection<>)))
|
||||
{
|
||||
//if it is a IEnumerable<>, IList<T> or ICollection<> we'll use a List<>
|
||||
//if it is a IEnumerable<>, IReadOnlyCollection<T>, IList<T> or ICollection<> we'll use a List<> since it implements them all
|
||||
var genericType = typeof(List<>).MakeGenericType(propertyInfo.PropertyType.GetGenericArguments());
|
||||
return new ClonePropertyInfo(propertyInfo) { GenericListType = genericType };
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ namespace Umbraco.Core.Models
|
||||
/// <summary>
|
||||
/// Represents a property value.
|
||||
/// </summary>
|
||||
public class PropertyValue : IEquatable<PropertyValue>
|
||||
public class PropertyValue : IDeepCloneable, IEquatable<PropertyValue>
|
||||
{
|
||||
// TODO: Either we allow change tracking at this class level, or we add some special change tracking collections to the Property
|
||||
// class to deal with change tracking which variants have changed
|
||||
@@ -96,6 +96,8 @@ namespace Umbraco.Core.Models
|
||||
public PropertyValue Clone()
|
||||
=> new PropertyValue { _culture = _culture, _segment = _segment, PublishedValue = PublishedValue, EditedValue = EditedValue };
|
||||
|
||||
public object DeepClone() => Clone();
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return Equals(obj as PropertyValue);
|
||||
@@ -105,14 +107,18 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
return other != null &&
|
||||
_culture == other._culture &&
|
||||
_segment == other._segment;
|
||||
_segment == other._segment &&
|
||||
EqualityComparer<object>.Default.Equals(EditedValue, other.EditedValue) &&
|
||||
EqualityComparer<object>.Default.Equals(PublishedValue, other.PublishedValue);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = -1254204277;
|
||||
var hashCode = 1885328050;
|
||||
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(_culture);
|
||||
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(_segment);
|
||||
hashCode = hashCode * -1521134295 + EqualityComparer<object>.Default.GetHashCode(EditedValue);
|
||||
hashCode = hashCode * -1521134295 + EqualityComparer<object>.Default.GetHashCode(PublishedValue);
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
@@ -358,20 +364,6 @@ namespace Umbraco.Core.Models
|
||||
|
||||
var clonedEntity = (Property)clone;
|
||||
|
||||
//manually clone _values, _pvalue, _vvalues
|
||||
clonedEntity._values = _values?.Select(x => x.Clone()).ToList(); // all values get copied
|
||||
clonedEntity._pvalue = _pvalue?.Clone();
|
||||
// the tricky part here is that _values contains ALL values including the values in the _vvalues
|
||||
// dictionary and they are by reference which is why we have equality overloads on PropertyValue
|
||||
if (clonedEntity._vvalues != null)
|
||||
{
|
||||
clonedEntity._vvalues = new Dictionary<CompositeNStringNStringKey, PropertyValue>();
|
||||
foreach (var item in _vvalues)
|
||||
{
|
||||
clonedEntity._vvalues[item.Key] = clonedEntity._values.First(x => x.Equals(item.Value));
|
||||
}
|
||||
}
|
||||
|
||||
//need to manually assign since this is a readonly property
|
||||
clonedEntity.PropertyType = (PropertyType) PropertyType.DeepClone();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
namespace Umbraco.Core.Models.PublishedContent
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a live published model creation service.
|
||||
/// </summary>
|
||||
public interface ILivePublishedModelFactory2 : ILivePublishedModelFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Tells the factory that it should build a new generation of models
|
||||
/// </summary>
|
||||
void Reset();
|
||||
|
||||
/// <summary>
|
||||
/// If the live model factory
|
||||
/// </summary>
|
||||
bool Enabled { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Provides a live published model creation service.
|
||||
/// </summary>
|
||||
|
||||
@@ -105,13 +105,13 @@ namespace Umbraco.Core.Models.PublishedContent
|
||||
{ "Email", Constants.DataTypes.Textbox },
|
||||
{ "Username", Constants.DataTypes.Textbox },
|
||||
{ "PasswordQuestion", Constants.DataTypes.Textbox },
|
||||
{ "Comments", Constants.DataTypes.Textbox },
|
||||
{ "Comments", Constants.DataTypes.Textarea },
|
||||
{ "IsApproved", Constants.DataTypes.Boolean },
|
||||
{ "IsLockedOut", Constants.DataTypes.Boolean },
|
||||
{ "LastLockoutDate", Constants.DataTypes.DateTime },
|
||||
{ "CreateDate", Constants.DataTypes.DateTime },
|
||||
{ "LastLoginDate", Constants.DataTypes.DateTime },
|
||||
{ "LastPasswordChangeDate", Constants.DataTypes.DateTime },
|
||||
{ "LastLockoutDate", Constants.DataTypes.LabelDateTime },
|
||||
{ "CreateDate", Constants.DataTypes.LabelDateTime },
|
||||
{ "LastLoginDate", Constants.DataTypes.LabelDateTime },
|
||||
{ "LastPasswordChangeDate", Constants.DataTypes.LabelDateTime }
|
||||
};
|
||||
|
||||
#region Content type
|
||||
|
||||
@@ -23,5 +23,12 @@
|
||||
/// Gets the segment.
|
||||
/// </summary>
|
||||
public string Segment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the segment for the content item
|
||||
/// </summary>
|
||||
/// <param name="contentId"></param>
|
||||
/// <returns></returns>
|
||||
public virtual string GetSegment(int contentId) => Segment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,32 @@
|
||||
public static class VariationContextAccessorExtensions
|
||||
{
|
||||
public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, ref string culture, ref string segment)
|
||||
=> variationContextAccessor.ContextualizeVariation(variations, null, ref culture, ref segment);
|
||||
|
||||
public static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int contentId, ref string culture, ref string segment)
|
||||
=> variationContextAccessor.ContextualizeVariation(variations, (int?)contentId, ref culture, ref segment);
|
||||
|
||||
private static void ContextualizeVariation(this IVariationContextAccessor variationContextAccessor, ContentVariation variations, int? contentId, ref string culture, ref string segment)
|
||||
{
|
||||
if (culture != null && segment != null) return;
|
||||
|
||||
// use context values
|
||||
var publishedVariationContext = variationContextAccessor?.VariationContext;
|
||||
if (culture == null) culture = variations.VariesByCulture() ? publishedVariationContext?.Culture : "";
|
||||
if (segment == null) segment = variations.VariesBySegment() ? publishedVariationContext?.Segment : "";
|
||||
|
||||
if (segment == null)
|
||||
{
|
||||
if (variations.VariesBySegment())
|
||||
{
|
||||
segment = contentId == null
|
||||
? publishedVariationContext?.Segment
|
||||
: publishedVariationContext?.GetSegment(contentId.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
segment = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Exceptions;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
|
||||
namespace Umbraco.Core.Models
|
||||
@@ -13,27 +14,41 @@ namespace Umbraco.Core.Models
|
||||
{
|
||||
private string _name;
|
||||
private string _alias;
|
||||
private bool _isBidrectional;
|
||||
private bool _isBidirectional;
|
||||
private Guid? _parentObjectType;
|
||||
private Guid? _childObjectType;
|
||||
|
||||
//TODO: Should we put back the broken ctors with obsolete attributes?
|
||||
|
||||
[Obsolete("This constructor is no longer used and will be removed in future versions, use one of the other constructors instead")]
|
||||
public RelationType(string alias, string name)
|
||||
: this(name, alias, false, null, null)
|
||||
: this(name: name, alias: alias, false, null, null)
|
||||
{
|
||||
}
|
||||
|
||||
public RelationType(string name, string alias, bool isBidrectional, Guid? parentObjectType, Guid? childObjectType)
|
||||
{
|
||||
if (name == null) throw new ArgumentNullException(nameof(name));
|
||||
if (string.IsNullOrWhiteSpace(name)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(name));
|
||||
if (alias == null) throw new ArgumentNullException(nameof(alias));
|
||||
if (string.IsNullOrWhiteSpace(alias)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(alias));
|
||||
|
||||
_name = name;
|
||||
_alias = alias;
|
||||
_isBidrectional = isBidrectional;
|
||||
_isBidirectional = isBidrectional;
|
||||
_parentObjectType = parentObjectType;
|
||||
_childObjectType = childObjectType;
|
||||
}
|
||||
|
||||
[Obsolete("This constructor is no longer used and will be removed in future versions, use one of the other constructors instead")]
|
||||
public RelationType(Guid childObjectType, Guid parentObjectType, string alias)
|
||||
: this(name: alias, alias: alias, isBidrectional: false, parentObjectType: parentObjectType, childObjectType: childObjectType)
|
||||
{
|
||||
}
|
||||
|
||||
[Obsolete("This constructor is no longer used and will be removed in future versions, use one of the other constructors instead")]
|
||||
public RelationType(Guid childObjectType, Guid parentObjectType, string alias, string name)
|
||||
: this(name: name, alias: alias, isBidrectional: false, parentObjectType: parentObjectType, childObjectType: childObjectType)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the Name of the RelationType
|
||||
@@ -61,8 +76,8 @@ namespace Umbraco.Core.Models
|
||||
[DataMember]
|
||||
public bool IsBidirectional
|
||||
{
|
||||
get => _isBidrectional;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isBidrectional, nameof(IsBidirectional));
|
||||
get => _isBidirectional;
|
||||
set => SetPropertyValueAndDetectChanges(value, ref _isBidirectional, nameof(IsBidirectional));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace Umbraco.Core.Models
|
||||
{
|
||||
internal static class RelationTypeExtensions
|
||||
{
|
||||
internal 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;
|
||||
}
|
||||
}
|
||||
@@ -134,15 +134,16 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
// publishing = deal with edit and published values
|
||||
foreach (var propertyValue in property.Values)
|
||||
{
|
||||
var isInvariantValue = propertyValue.Culture == null;
|
||||
var isCultureValue = propertyValue.Culture != null && propertyValue.Segment == null;
|
||||
var isInvariantValue = propertyValue.Culture == null && propertyValue.Segment == null;
|
||||
var isCultureValue = propertyValue.Culture != null;
|
||||
var isSegmentValue = propertyValue.Segment != null;
|
||||
|
||||
// deal with published value
|
||||
if (propertyValue.PublishedValue != null && publishedVersionId > 0)
|
||||
if ((propertyValue.PublishedValue != null || isSegmentValue) && publishedVersionId > 0)
|
||||
propertyDataDtos.Add(BuildDto(publishedVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.PublishedValue));
|
||||
|
||||
// deal with edit value
|
||||
if (propertyValue.EditedValue != null)
|
||||
if (propertyValue.EditedValue != null || isSegmentValue)
|
||||
propertyDataDtos.Add(BuildDto(currentVersionId, property, languageRepository.GetIdByIsoCode(propertyValue.Culture), propertyValue.Segment, propertyValue.EditedValue));
|
||||
|
||||
// property.Values will contain ALL of it's values, both variant and invariant which will be populated if the
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Umbraco.Core.Persistence.Factories
|
||||
|
||||
public static IRelationType BuildEntity(RelationTypeDto dto)
|
||||
{
|
||||
var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ChildObjectType, dto.ParentObjectType);
|
||||
var entity = new RelationType(dto.Name, dto.Alias, dto.Dual, dto.ParentObjectType, dto.ChildObjectType);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -18,6 +18,48 @@ namespace Umbraco.Core.Persistence
|
||||
/// </summary>
|
||||
public static partial class NPocoDatabaseExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Iterates over the result of a paged data set with a db reader
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="database"></param>
|
||||
/// <param name="pageSize">
|
||||
/// The number of rows to load per page
|
||||
/// </param>
|
||||
/// <param name="sql"></param>
|
||||
/// <returns></returns>
|
||||
/// <remarks>
|
||||
/// NPoco's normal Page returns a List{T} but sometimes we don't want all that in memory and instead want to
|
||||
/// iterate over each row with a reader using Query vs Fetch.
|
||||
/// </remarks>
|
||||
internal static IEnumerable<T> QueryPaged<T>(this IDatabase database, long pageSize, Sql sql)
|
||||
{
|
||||
var sqlString = sql.SQL;
|
||||
var sqlArgs = sql.Arguments;
|
||||
|
||||
int? itemCount = null;
|
||||
long pageIndex = 0;
|
||||
do
|
||||
{
|
||||
// Get the paged queries
|
||||
database.BuildPageQueries<T>(pageIndex * pageSize, pageSize, sqlString, ref sqlArgs, out var sqlCount, out var sqlPage);
|
||||
|
||||
// get the item count once
|
||||
if (itemCount == null)
|
||||
{
|
||||
itemCount = database.ExecuteScalar<int>(sqlCount, sqlArgs);
|
||||
}
|
||||
pageIndex++;
|
||||
|
||||
// iterate over rows without allocating all items to memory (Query vs Fetch)
|
||||
foreach (var row in database.Query<T>(sqlPage, sqlArgs))
|
||||
{
|
||||
yield return row;
|
||||
}
|
||||
|
||||
} while ((pageIndex * pageSize) < itemCount);
|
||||
}
|
||||
|
||||
// NOTE
|
||||
//
|
||||
// proper way to do it with TSQL and SQLCE
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Entities;
|
||||
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
|
||||
using Umbraco.Core.Persistence.Querying;
|
||||
@@ -77,5 +78,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <remarks>Here, <paramref name="filter"/> can be null but <paramref name="ordering"/> cannot.</remarks>
|
||||
IEnumerable<TEntity> GetPage(IQuery<TEntity> query, long pageIndex, int pageSize, out long totalRecords,
|
||||
IQuery<TEntity> filter, Ordering ordering);
|
||||
|
||||
ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// gets all version ids, current first
|
||||
public virtual IEnumerable<int> GetVersionIds(int nodeId, int maxRows)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersionIds", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetVersionIds, tsql =>
|
||||
tsql.Select<ContentVersionDto>(x => x.Id)
|
||||
.From<ContentVersionDto>()
|
||||
.Where<ContentVersionDto>(x => x.NodeId == SqlTemplate.Arg<int>("nodeId"))
|
||||
@@ -99,7 +99,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// TODO: test object node type?
|
||||
|
||||
// get the version we want to delete
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersion", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetVersion, tsql =>
|
||||
tsql.Select<ContentVersionDto>().From<ContentVersionDto>().Where<ContentVersionDto>(x => x.Id == SqlTemplate.Arg<int>("versionId"))
|
||||
);
|
||||
var versionDto = Database.Fetch<ContentVersionDto>(template.Sql(new { versionId })).FirstOrDefault();
|
||||
@@ -121,7 +121,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// TODO: test object node type?
|
||||
|
||||
// get the versions we want to delete, excluding the current one
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetVersions", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetVersions, tsql =>
|
||||
tsql.Select<ContentVersionDto>().From<ContentVersionDto>().Where<ContentVersionDto>(x => x.NodeId == SqlTemplate.Arg<int>("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg<DateTime>("versionDate"))
|
||||
);
|
||||
var versionDtos = Database.Fetch<ContentVersionDto>(template.Sql(new { nodeId, versionDate }));
|
||||
@@ -403,7 +403,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
}
|
||||
|
||||
// content type alias is invariant
|
||||
if(ordering.OrderBy.InvariantEquals("contentTypeAlias"))
|
||||
if (ordering.OrderBy.InvariantEquals("contentTypeAlias"))
|
||||
{
|
||||
var joins = Sql()
|
||||
.InnerJoin<ContentTypeDto>("ctype").On<ContentDto, ContentTypeDto>((content, contentType) => content.ContentTypeId == contentType.NodeId, aliasRight: "ctype");
|
||||
@@ -477,6 +477,123 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
IQuery<TEntity> filter,
|
||||
Ordering ordering);
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
var report = new Dictionary<int, ContentDataIntegrityReportEntry>();
|
||||
|
||||
var sql = SqlContext.Sql()
|
||||
.Select<NodeDto>()
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeObjectType == NodeObjectTypeId)
|
||||
.OrderBy<NodeDto>(x => x.Level, x => x.ParentId, x => x.SortOrder);
|
||||
|
||||
var nodesToRebuild = new Dictionary<int, List<NodeDto>>();
|
||||
var validNodes = new Dictionary<int, NodeDto>();
|
||||
var rootIds = new[] {Constants.System.Root, Constants.System.RecycleBinContent, Constants.System.RecycleBinMedia};
|
||||
var currentParentIds = new HashSet<int>(rootIds);
|
||||
var prevParentIds = currentParentIds;
|
||||
var lastLevel = -1;
|
||||
|
||||
// use a forward cursor (query)
|
||||
foreach (var node in Database.Query<NodeDto>(sql))
|
||||
{
|
||||
if (node.Level != lastLevel)
|
||||
{
|
||||
// changing levels
|
||||
prevParentIds = currentParentIds;
|
||||
currentParentIds = null;
|
||||
lastLevel = node.Level;
|
||||
}
|
||||
|
||||
if (currentParentIds == null)
|
||||
{
|
||||
// we're reset
|
||||
currentParentIds = new HashSet<int>();
|
||||
}
|
||||
|
||||
currentParentIds.Add(node.NodeId);
|
||||
|
||||
// paths parts without the roots
|
||||
var pathParts = node.Path.Split(',').Where(x => !rootIds.Contains(int.Parse(x))).ToArray();
|
||||
|
||||
if (!prevParentIds.Contains(node.ParentId))
|
||||
{
|
||||
// invalid, this will be because the level is wrong (which prob means path is wrong too)
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathAndLevelByParentId));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else if (pathParts.Length == 0)
|
||||
{
|
||||
// invalid path
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathEmpty));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else if (pathParts.Length != node.Level)
|
||||
{
|
||||
// invalid, either path or level is wrong
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathLevelMismatch));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else if (pathParts[pathParts.Length - 1] != node.NodeId.ToString())
|
||||
{
|
||||
// invalid path
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathById));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else if (!rootIds.Contains(node.ParentId) && pathParts[pathParts.Length - 2] != node.ParentId.ToString())
|
||||
{
|
||||
// invalid path
|
||||
report.Add(node.NodeId, new ContentDataIntegrityReportEntry(ContentDataIntegrityReport.IssueType.InvalidPathByParentId));
|
||||
AppendNodeToFix(nodesToRebuild, node);
|
||||
}
|
||||
else
|
||||
{
|
||||
// it's valid!
|
||||
|
||||
// don't track unless we are configured to fix
|
||||
if (options.FixIssues)
|
||||
validNodes.Add(node.NodeId, node);
|
||||
}
|
||||
}
|
||||
|
||||
var updated = new List<NodeDto>();
|
||||
|
||||
if (options.FixIssues)
|
||||
{
|
||||
// iterate all valid nodes to see if these are parents for invalid nodes
|
||||
foreach (var (nodeId, node) in validNodes)
|
||||
{
|
||||
if (!nodesToRebuild.TryGetValue(nodeId, out var invalidNodes)) continue;
|
||||
|
||||
// now we can try to rebuild the invalid paths.
|
||||
|
||||
foreach (var invalidNode in invalidNodes)
|
||||
{
|
||||
invalidNode.Level = (short)(node.Level + 1);
|
||||
invalidNode.Path = node.Path + "," + invalidNode.NodeId;
|
||||
updated.Add(invalidNode);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var node in updated)
|
||||
{
|
||||
Database.Update(node);
|
||||
if (report.TryGetValue(node.NodeId, out var entry))
|
||||
entry.Fixed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return new ContentDataIntegrityReport(report);
|
||||
}
|
||||
|
||||
private static void AppendNodeToFix(IDictionary<int, List<NodeDto>> nodesToRebuild, NodeDto node)
|
||||
{
|
||||
if (nodesToRebuild.TryGetValue(node.ParentId, out var childIds))
|
||||
childIds.Add(node);
|
||||
else
|
||||
nodesToRebuild[node.ParentId] = new List<NodeDto> { node };
|
||||
}
|
||||
|
||||
// here, filter can be null and ordering cannot
|
||||
protected IEnumerable<TEntity> GetPage<TDto>(IQuery<TEntity> query,
|
||||
long pageIndex, int pageSize, out long totalRecords,
|
||||
@@ -770,7 +887,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected virtual string EnsureUniqueNodeName(int parentId, string nodeName, int id = 0)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.EnsureUniqueNodeName", tsql => tsql
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.EnsureUniqueNodeName, tsql => tsql
|
||||
.Select<NodeDto>(x => Alias(x.NodeId, "id"), x => Alias(x.Text, "name"))
|
||||
.From<NodeDto>()
|
||||
.Where<NodeDto>(x => x.NodeObjectType == SqlTemplate.Arg<Guid>("nodeObjectType") && x.ParentId == SqlTemplate.Arg<int>("parentId")));
|
||||
@@ -783,7 +900,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected virtual int GetNewChildSortOrder(int parentId, int first)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetSortOrder", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetSortOrder, tsql =>
|
||||
tsql.Select($"COALESCE(MAX(sortOrder),{first - 1})").From<NodeDto>().Where<NodeDto>(x => x.ParentId == SqlTemplate.Arg<int>("parentId") && x.NodeObjectType == NodeObjectTypeId)
|
||||
);
|
||||
|
||||
@@ -792,7 +909,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected virtual NodeDto GetParentNodeDto(int parentId)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetParentNode", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetParentNode, tsql =>
|
||||
tsql.Select<NodeDto>().From<NodeDto>().Where<NodeDto>(x => x.NodeId == SqlTemplate.Arg<int>("parentId"))
|
||||
);
|
||||
|
||||
@@ -801,7 +918,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected virtual int GetReservedId(Guid uniqueId)
|
||||
{
|
||||
var template = SqlContext.Templates.Get("Umbraco.Core.VersionableRepository.GetReservedId", tsql =>
|
||||
var template = SqlContext.Templates.Get(Constants.SqlTemplates.VersionableRepository.GetReservedId, tsql =>
|
||||
tsql.Select<NodeDto>(x => x.NodeId).From<NodeDto>().Where<NodeDto>(x => x.UniqueId == SqlTemplate.Arg<Guid>("uniqueId") && x.NodeObjectType == Constants.ObjectTypes.IdReservation)
|
||||
);
|
||||
var id = Database.ExecuteScalar<int?>(template.Sql(new { uniqueId = uniqueId }));
|
||||
|
||||
@@ -321,7 +321,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
.InnerJoin<DocumentVersionDto>()
|
||||
.On<ContentVersionDto, DocumentVersionDto>((c, d) => c.Id == d.Id)
|
||||
.Where<ContentVersionDto>(x => x.NodeId == SqlTemplate.Arg<int>("nodeId") && !x.Current && x.VersionDate < SqlTemplate.Arg<DateTime>("versionDate"))
|
||||
.Where<DocumentVersionDto>( x => !x.Published)
|
||||
.Where<DocumentVersionDto>(x => !x.Published)
|
||||
);
|
||||
var versionDtos = Database.Fetch<ContentVersionDto>(template.Sql(new { nodeId, versionDate }));
|
||||
foreach (var versionDto in versionDtos)
|
||||
@@ -519,8 +519,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override void PersistUpdatedItem(IContent entity)
|
||||
{
|
||||
var entityBase = entity as EntityBase;
|
||||
var isEntityDirty = entityBase != null && entityBase.IsDirty();
|
||||
var isEntityDirty = entity.IsDirty();
|
||||
|
||||
// check if we need to make any database changes at all
|
||||
if ((entity.PublishedState == PublishedState.Published || entity.PublishedState == PublishedState.Unpublished)
|
||||
@@ -535,29 +534,41 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
// update
|
||||
entity.UpdatingEntity();
|
||||
|
||||
// Check if this entity is being moved as a descendant as part of a bulk moving operations.
|
||||
// In this case we can bypass a lot of the below operations which will make this whole operation go much faster.
|
||||
// When moving we don't need to create new versions, etc... because we cannot roll this operation back anyways.
|
||||
var isMoving = entity.IsMoving();
|
||||
// TODO: I'm sure we can also detect a "Copy" (of a descendant) operation and probably perform similar checks below.
|
||||
// There is probably more stuff that would be required for copying but I'm sure not all of this logic would be, we could more than likely boost
|
||||
// copy performance by 95% just like we did for Move
|
||||
|
||||
|
||||
var publishing = entity.PublishedState == PublishedState.Publishing;
|
||||
|
||||
// check if we need to create a new version
|
||||
if (publishing && entity.PublishedVersionId > 0)
|
||||
if (!isMoving)
|
||||
{
|
||||
// published version is not published anymore
|
||||
Database.Execute(Sql().Update<DocumentVersionDto>(u => u.Set(x => x.Published, false)).Where<DocumentVersionDto>(x => x.Id == entity.PublishedVersionId));
|
||||
}
|
||||
// check if we need to create a new version
|
||||
if (publishing && entity.PublishedVersionId > 0)
|
||||
{
|
||||
// published version is not published anymore
|
||||
Database.Execute(Sql().Update<DocumentVersionDto>(u => u.Set(x => x.Published, false)).Where<DocumentVersionDto>(x => x.Id == entity.PublishedVersionId));
|
||||
}
|
||||
|
||||
// sanitize names
|
||||
SanitizeNames(entity, publishing);
|
||||
// sanitize names
|
||||
SanitizeNames(entity, publishing);
|
||||
|
||||
// ensure that strings don't contain characters that are invalid in xml
|
||||
// TODO: do we really want to keep doing this here?
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
// ensure that strings don't contain characters that are invalid in xml
|
||||
// TODO: do we really want to keep doing this here?
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
// if parent has changed, get path, level and sort order
|
||||
if (entity.IsPropertyDirty("ParentId"))
|
||||
{
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
entity.Level = parent.Level + 1;
|
||||
entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0);
|
||||
// if parent has changed, get path, level and sort order
|
||||
if (entity.IsPropertyDirty("ParentId"))
|
||||
{
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
entity.Level = parent.Level + 1;
|
||||
entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// create the dto
|
||||
@@ -568,146 +579,152 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
nodeDto.ValidatePathWithException();
|
||||
Database.Update(nodeDto);
|
||||
|
||||
// update the content dto
|
||||
Database.Update(dto.ContentDto);
|
||||
|
||||
// update the content & document version dtos
|
||||
var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto;
|
||||
var documentVersionDto = dto.DocumentVersionDto;
|
||||
if (publishing)
|
||||
if (!isMoving)
|
||||
{
|
||||
documentVersionDto.Published = true; // now published
|
||||
contentVersionDto.Current = false; // no more current
|
||||
}
|
||||
Database.Update(contentVersionDto);
|
||||
Database.Update(documentVersionDto);
|
||||
// update the content dto
|
||||
Database.Update(dto.ContentDto);
|
||||
|
||||
// and, if publishing, insert new content & document version dtos
|
||||
if (publishing)
|
||||
{
|
||||
entity.PublishedVersionId = entity.VersionId;
|
||||
|
||||
contentVersionDto.Id = 0; // want a new id
|
||||
contentVersionDto.Current = true; // current version
|
||||
contentVersionDto.Text = entity.Name;
|
||||
Database.Insert(contentVersionDto);
|
||||
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
|
||||
|
||||
documentVersionDto.Published = false; // non-published version
|
||||
Database.Insert(documentVersionDto);
|
||||
}
|
||||
|
||||
// replace the property data (rather than updating)
|
||||
// only need to delete for the version that existed, the new version (if any) has no property data yet
|
||||
var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId;
|
||||
var deletePropertyDataSql = Sql().Delete<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == versionToDelete);
|
||||
Database.Execute(deletePropertyDataSql);
|
||||
|
||||
// insert property data
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0,
|
||||
entity.Properties, LanguageRepository, out var edited, out var editedCultures);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
|
||||
// if !publishing, we may have a new name != current publish name,
|
||||
// also impacts 'edited'
|
||||
if (!publishing && entity.PublishName != entity.Name)
|
||||
edited = true;
|
||||
|
||||
if (entity.ContentType.VariesByCulture())
|
||||
{
|
||||
// bump dates to align cultures to version
|
||||
// update the content & document version dtos
|
||||
var contentVersionDto = dto.DocumentVersionDto.ContentVersionDto;
|
||||
var documentVersionDto = dto.DocumentVersionDto;
|
||||
if (publishing)
|
||||
entity.AdjustDates(contentVersionDto.VersionDate);
|
||||
{
|
||||
documentVersionDto.Published = true; // now published
|
||||
contentVersionDto.Current = false; // no more current
|
||||
}
|
||||
Database.Update(contentVersionDto);
|
||||
Database.Update(documentVersionDto);
|
||||
|
||||
// names also impact 'edited'
|
||||
// ReSharper disable once UseDeconstruction
|
||||
foreach (var cultureInfo in entity.CultureInfos)
|
||||
if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture))
|
||||
{
|
||||
edited = true;
|
||||
(editedCultures ?? (editedCultures = new HashSet<string>(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture);
|
||||
// and, if publishing, insert new content & document version dtos
|
||||
if (publishing)
|
||||
{
|
||||
entity.PublishedVersionId = entity.VersionId;
|
||||
|
||||
// TODO: change tracking
|
||||
// at the moment, we don't do any dirty tracking on property values, so we don't know whether the
|
||||
// culture has just been edited or not, so we don't update its update date - that date only changes
|
||||
// when the name is set, and it all works because the controller does it - but, if someone uses a
|
||||
// service to change a property value and save (without setting name), the update date does not change.
|
||||
}
|
||||
contentVersionDto.Id = 0; // want a new id
|
||||
contentVersionDto.Current = true; // current version
|
||||
contentVersionDto.Text = entity.Name;
|
||||
Database.Insert(contentVersionDto);
|
||||
entity.VersionId = documentVersionDto.Id = contentVersionDto.Id; // get the new id
|
||||
|
||||
// replace the content version variations (rather than updating)
|
||||
documentVersionDto.Published = false; // non-published version
|
||||
Database.Insert(documentVersionDto);
|
||||
}
|
||||
|
||||
// replace the property data (rather than updating)
|
||||
// only need to delete for the version that existed, the new version (if any) has no property data yet
|
||||
var deleteContentVariations = Sql().Delete<ContentVersionCultureVariationDto>().Where<ContentVersionCultureVariationDto>(x => x.VersionId == versionToDelete);
|
||||
Database.Execute(deleteContentVariations);
|
||||
var versionToDelete = publishing ? entity.PublishedVersionId : entity.VersionId;
|
||||
var deletePropertyDataSql = Sql().Delete<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == versionToDelete);
|
||||
Database.Execute(deletePropertyDataSql);
|
||||
|
||||
// replace the document version variations (rather than updating)
|
||||
var deleteDocumentVariations = Sql().Delete<DocumentCultureVariationDto>().Where<DocumentCultureVariationDto>(x => x.NodeId == entity.Id);
|
||||
Database.Execute(deleteDocumentVariations);
|
||||
// insert property data
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, publishing ? entity.PublishedVersionId : 0,
|
||||
entity.Properties, LanguageRepository, out var edited, out var editedCultures);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
|
||||
// TODO: NPoco InsertBulk issue?
|
||||
// we should use the native NPoco InsertBulk here but it causes problems (not sure exactly all scenarios)
|
||||
// but by using SQL Server and updating a variants name will cause: Unable to cast object of type
|
||||
// 'Umbraco.Core.Persistence.FaultHandling.RetryDbConnection' to type 'System.Data.SqlClient.SqlConnection'.
|
||||
// (same in PersistNewItem above)
|
||||
// if !publishing, we may have a new name != current publish name,
|
||||
// also impacts 'edited'
|
||||
if (!publishing && entity.PublishName != entity.Name)
|
||||
edited = true;
|
||||
|
||||
// insert content variations
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
if (entity.ContentType.VariesByCulture())
|
||||
{
|
||||
// bump dates to align cultures to version
|
||||
if (publishing)
|
||||
entity.AdjustDates(contentVersionDto.VersionDate);
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
// names also impact 'edited'
|
||||
// ReSharper disable once UseDeconstruction
|
||||
foreach (var cultureInfo in entity.CultureInfos)
|
||||
if (cultureInfo.Name != entity.GetPublishName(cultureInfo.Culture))
|
||||
{
|
||||
edited = true;
|
||||
(editedCultures ?? (editedCultures = new HashSet<string>(StringComparer.OrdinalIgnoreCase))).Add(cultureInfo.Culture);
|
||||
|
||||
// TODO: change tracking
|
||||
// at the moment, we don't do any dirty tracking on property values, so we don't know whether the
|
||||
// culture has just been edited or not, so we don't update its update date - that date only changes
|
||||
// when the name is set, and it all works because the controller does it - but, if someone uses a
|
||||
// service to change a property value and save (without setting name), the update date does not change.
|
||||
}
|
||||
|
||||
// replace the content version variations (rather than updating)
|
||||
// only need to delete for the version that existed, the new version (if any) has no property data yet
|
||||
var deleteContentVariations = Sql().Delete<ContentVersionCultureVariationDto>().Where<ContentVersionCultureVariationDto>(x => x.VersionId == versionToDelete);
|
||||
Database.Execute(deleteContentVariations);
|
||||
|
||||
// replace the document version variations (rather than updating)
|
||||
var deleteDocumentVariations = Sql().Delete<DocumentCultureVariationDto>().Where<DocumentCultureVariationDto>(x => x.NodeId == entity.Id);
|
||||
Database.Execute(deleteDocumentVariations);
|
||||
|
||||
// TODO: NPoco InsertBulk issue?
|
||||
// we should use the native NPoco InsertBulk here but it causes problems (not sure exactly all scenarios)
|
||||
// but by using SQL Server and updating a variants name will cause: Unable to cast object of type
|
||||
// 'Umbraco.Core.Persistence.FaultHandling.RetryDbConnection' to type 'System.Data.SqlClient.SqlConnection'.
|
||||
// (same in PersistNewItem above)
|
||||
|
||||
// insert content variations
|
||||
Database.BulkInsertRecords(GetContentVariationDtos(entity, publishing));
|
||||
|
||||
// insert document variations
|
||||
Database.BulkInsertRecords(GetDocumentVariationDtos(entity, editedCultures));
|
||||
}
|
||||
|
||||
// refresh content
|
||||
entity.SetCultureEdited(editedCultures);
|
||||
|
||||
// update the document dto
|
||||
// at that point, when un/publishing, the entity still has its old Published value
|
||||
// so we need to explicitly update the dto to persist the correct value
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
dto.Published = true;
|
||||
else if (entity.PublishedState == PublishedState.Unpublishing)
|
||||
dto.Published = false;
|
||||
entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
|
||||
Database.Update(dto);
|
||||
|
||||
//update the schedule
|
||||
if (entity.IsPropertyDirty("ContentSchedule"))
|
||||
PersistContentSchedule(entity, true);
|
||||
|
||||
// if entity is publishing, update tags, else leave tags there
|
||||
// means that implicitly unpublished, or trashed, entities *still* have tags in db
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
}
|
||||
|
||||
// refresh content
|
||||
entity.SetCultureEdited(editedCultures);
|
||||
|
||||
// update the document dto
|
||||
// at that point, when un/publishing, the entity still has its old Published value
|
||||
// so we need to explicitly update the dto to persist the correct value
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
dto.Published = true;
|
||||
else if (entity.PublishedState == PublishedState.Unpublishing)
|
||||
dto.Published = false;
|
||||
entity.Edited = dto.Edited = !dto.Published || edited; // if not published, always edited
|
||||
Database.Update(dto);
|
||||
|
||||
//update the schedule
|
||||
if (entity.IsPropertyDirty("ContentSchedule"))
|
||||
PersistContentSchedule(entity, true);
|
||||
|
||||
// if entity is publishing, update tags, else leave tags there
|
||||
// means that implicitly unpublished, or trashed, entities *still* have tags in db
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
|
||||
// trigger here, before we reset Published etc
|
||||
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
|
||||
|
||||
// flip the entity's published property
|
||||
// this also flips its published state
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
if (!isMoving)
|
||||
{
|
||||
entity.Published = true;
|
||||
entity.PublishTemplateId = entity.TemplateId;
|
||||
entity.PublisherId = entity.WriterId;
|
||||
entity.PublishName = entity.Name;
|
||||
entity.PublishDate = entity.UpdateDate;
|
||||
// flip the entity's published property
|
||||
// this also flips its published state
|
||||
if (entity.PublishedState == PublishedState.Publishing)
|
||||
{
|
||||
entity.Published = true;
|
||||
entity.PublishTemplateId = entity.TemplateId;
|
||||
entity.PublisherId = entity.WriterId;
|
||||
entity.PublishName = entity.Name;
|
||||
entity.PublishDate = entity.UpdateDate;
|
||||
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
}
|
||||
else if (entity.PublishedState == PublishedState.Unpublishing)
|
||||
{
|
||||
entity.Published = false;
|
||||
entity.PublishTemplateId = null;
|
||||
entity.PublisherId = null;
|
||||
entity.PublishName = null;
|
||||
entity.PublishDate = null;
|
||||
|
||||
ClearEntityTags(entity, _tagRepository);
|
||||
}
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
// TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what?
|
||||
}
|
||||
else if (entity.PublishedState == PublishedState.Unpublishing)
|
||||
{
|
||||
entity.Published = false;
|
||||
entity.PublishTemplateId = null;
|
||||
entity.PublisherId = null;
|
||||
entity.PublishName = null;
|
||||
entity.PublishDate = null;
|
||||
|
||||
ClearEntityTags(entity, _tagRepository);
|
||||
}
|
||||
|
||||
PersistRelations(entity);
|
||||
|
||||
// TODO: note re. tags: explicitly unpublished entities have cleared tags, but masked or trashed entities *still* have tags in the db - so what?
|
||||
|
||||
entity.ResetDirtyProperties();
|
||||
|
||||
@@ -1183,7 +1200,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
if (temp.Template2Id.HasValue && templates.ContainsKey(temp.Template2Id.Value))
|
||||
temp.Content.PublishTemplateId = temp.Template2Id;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// set properties
|
||||
if (loadProperties)
|
||||
@@ -1216,7 +1233,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
SetVariations(temp.Content, contentVariations, documentVariations);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
foreach (var c in content)
|
||||
@@ -1430,7 +1447,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
yield return dto;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private class ContentVariation
|
||||
|
||||
@@ -219,7 +219,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override void PersistNewItem(IMedia entity)
|
||||
{
|
||||
var media = (Models.Media) entity;
|
||||
entity.AddingEntity();
|
||||
|
||||
// ensure unique name on the same level
|
||||
@@ -274,15 +273,15 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
contentVersionDto.NodeId = nodeDto.NodeId;
|
||||
contentVersionDto.Current = true;
|
||||
Database.Insert(contentVersionDto);
|
||||
media.VersionId = contentVersionDto.Id;
|
||||
entity.VersionId = contentVersionDto.Id;
|
||||
|
||||
// persist the media version dto
|
||||
var mediaVersionDto = dto.MediaVersionDto;
|
||||
mediaVersionDto.Id = media.VersionId;
|
||||
mediaVersionDto.Id = entity.VersionId;
|
||||
Database.Insert(mediaVersionDto);
|
||||
|
||||
// persist the property data
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(media.ContentType.Variations, media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _);
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, 0, entity.Properties, LanguageRepository, out _, out _);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
|
||||
@@ -298,26 +297,32 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
protected override void PersistUpdatedItem(IMedia entity)
|
||||
{
|
||||
var media = (Models.Media) entity;
|
||||
|
||||
// update
|
||||
media.UpdatingEntity();
|
||||
entity.UpdatingEntity();
|
||||
|
||||
// ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
|
||||
// Check if this entity is being moved as a descendant as part of a bulk moving operations.
|
||||
// In this case we can bypass a lot of the below operations which will make this whole operation go much faster.
|
||||
// When moving we don't need to create new versions, etc... because we cannot roll this operation back anyways.
|
||||
var isMoving = entity.IsMoving();
|
||||
|
||||
// ensure that strings don't contain characters that are invalid in xml
|
||||
// TODO: do we really want to keep doing this here?
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
// if parent has changed, get path, level and sort order
|
||||
if (entity.IsPropertyDirty("ParentId"))
|
||||
if (!isMoving)
|
||||
{
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
// ensure unique name on the same level
|
||||
entity.Name = EnsureUniqueNodeName(entity.ParentId, entity.Name, entity.Id);
|
||||
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
entity.Level = parent.Level + 1;
|
||||
entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0);
|
||||
// ensure that strings don't contain characters that are invalid in xml
|
||||
// TODO: do we really want to keep doing this here?
|
||||
entity.SanitizeEntityPropertiesForXmlStorage();
|
||||
|
||||
// if parent has changed, get path, level and sort order
|
||||
if (entity.IsPropertyDirty(nameof(entity.ParentId)))
|
||||
{
|
||||
var parent = GetParentNodeDto(entity.ParentId);
|
||||
|
||||
entity.Path = string.Concat(parent.Path, ",", entity.Id);
|
||||
entity.Level = parent.Level + 1;
|
||||
entity.SortOrder = GetNewChildSortOrder(entity.ParentId, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// create the dto
|
||||
@@ -328,26 +333,29 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
nodeDto.ValidatePathWithException();
|
||||
Database.Update(nodeDto);
|
||||
|
||||
// update the content dto
|
||||
Database.Update(dto.ContentDto);
|
||||
if (!isMoving)
|
||||
{
|
||||
// update the content dto
|
||||
Database.Update(dto.ContentDto);
|
||||
|
||||
// update the content & media version dtos
|
||||
var contentVersionDto = dto.MediaVersionDto.ContentVersionDto;
|
||||
var mediaVersionDto = dto.MediaVersionDto;
|
||||
contentVersionDto.Current = true;
|
||||
Database.Update(contentVersionDto);
|
||||
Database.Update(mediaVersionDto);
|
||||
// update the content & media version dtos
|
||||
var contentVersionDto = dto.MediaVersionDto.ContentVersionDto;
|
||||
var mediaVersionDto = dto.MediaVersionDto;
|
||||
contentVersionDto.Current = true;
|
||||
Database.Update(contentVersionDto);
|
||||
Database.Update(mediaVersionDto);
|
||||
|
||||
// replace the property data
|
||||
var deletePropertyDataSql = SqlContext.Sql().Delete<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == media.VersionId);
|
||||
Database.Execute(deletePropertyDataSql);
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(media.ContentType.Variations, media.VersionId, 0, entity.Properties, LanguageRepository, out _, out _);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
// replace the property data
|
||||
var deletePropertyDataSql = SqlContext.Sql().Delete<PropertyDataDto>().Where<PropertyDataDto>(x => x.VersionId == entity.VersionId);
|
||||
Database.Execute(deletePropertyDataSql);
|
||||
var propertyDataDtos = PropertyFactory.BuildDtos(entity.ContentType.Variations, entity.VersionId, 0, entity.Properties, LanguageRepository, out _, out _);
|
||||
foreach (var propertyDataDto in propertyDataDtos)
|
||||
Database.Insert(propertyDataDto);
|
||||
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
SetEntityTags(entity, _tagRepository);
|
||||
|
||||
PersistRelations(entity);
|
||||
PersistRelations(entity);
|
||||
}
|
||||
|
||||
OnUowRefreshedEntity(new ScopedEntityEventArgs(AmbientScope, entity));
|
||||
|
||||
|
||||
@@ -25,8 +25,17 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
path = path.EnsureEndsWith(".css");
|
||||
|
||||
if (FileSystem.FileExists(path) == false)
|
||||
// if the css directory is changed, references to the old path can still exist (ie in RTE config)
|
||||
// these old references will throw an error, which breaks the RTE
|
||||
// try-catch here makes the request fail silently, and allows RTE to load correctly
|
||||
try
|
||||
{
|
||||
if (FileSystem.FileExists(path) == false)
|
||||
return null;
|
||||
} catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// content will be lazy-loaded when required
|
||||
var created = FileSystem.GetCreated(path).UtcDateTime;
|
||||
|
||||
@@ -19,7 +19,6 @@ namespace Umbraco.Core.PropertyEditors
|
||||
public class DataEditor : IDataEditor
|
||||
{
|
||||
private IDictionary<string, object> _defaultConfiguration;
|
||||
private IDataValueEditor _dataValueEditor;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DataEditor"/> class.
|
||||
@@ -91,7 +90,7 @@ namespace Umbraco.Core.PropertyEditors
|
||||
/// simple enough for now.</para>
|
||||
/// </remarks>
|
||||
// TODO: point of that one? shouldn't we always configure?
|
||||
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? (_dataValueEditor ?? (_dataValueEditor = CreateValueEditor()));
|
||||
public IDataValueEditor GetValueEditor() => ExplicitValueEditor ?? CreateValueEditor();
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <remarks>
|
||||
|
||||
@@ -17,6 +17,20 @@ namespace Umbraco.Core
|
||||
/// <returns></returns>
|
||||
public static bool IsLiveFactory(this IPublishedModelFactory factory) => factory is ILivePublishedModelFactory;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the current <see cref="IPublishedModelFactory"/> is an implementation of <see cref="ILivePublishedModelFactory2"/> and is enabled
|
||||
/// </summary>
|
||||
/// <param name="factory"></param>
|
||||
/// <returns></returns>
|
||||
public static bool IsLiveFactoryEnabled(this IPublishedModelFactory factory)
|
||||
{
|
||||
if (factory is ILivePublishedModelFactory2 liveFactory2)
|
||||
return liveFactory2.Enabled;
|
||||
|
||||
// if it's not ILivePublishedModelFactory2 we can't determine if it's enabled or not so return true
|
||||
return factory is ILivePublishedModelFactory;
|
||||
}
|
||||
|
||||
[Obsolete("This method is no longer used or necessary and will be removed from future")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public static void WithSafeLiveFactory(this IPublishedModelFactory factory, Action action)
|
||||
@@ -50,15 +64,17 @@ namespace Umbraco.Core
|
||||
{
|
||||
lock (liveFactory.SyncRoot)
|
||||
{
|
||||
// TODO: Fix this in 8.3! - We need to change the ILivePublishedModelFactory interface to have a Reset method and then when we have an embedded MB
|
||||
// version we will publicize the ResetModels (and change the name to Reset).
|
||||
// For now, this will suffice and we'll use reflection, there should be no other implementation of ILivePublishedModelFactory.
|
||||
// Calling ResetModels resets the MB flag so that the next time EnsureModels is called (which is called when nucache lazily calls CreateModel) it will
|
||||
// trigger the recompiling of pure live models.
|
||||
var resetMethod = liveFactory.GetType().GetMethod("ResetModels", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
||||
if (resetMethod != null)
|
||||
resetMethod.Invoke(liveFactory, null);
|
||||
|
||||
if (liveFactory is ILivePublishedModelFactory2 liveFactory2)
|
||||
{
|
||||
liveFactory2.Reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is purely here for backwards compat and to avoid breaking changes but this code will probably never get executed
|
||||
var resetMethod = liveFactory.GetType().GetMethod("ResetModels", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic);
|
||||
if (resetMethod != null)
|
||||
resetMethod.Invoke(liveFactory, null);
|
||||
}
|
||||
action();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ 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 System.Web;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Persistence;
|
||||
using Umbraco.Core.Persistence.Dtos;
|
||||
@@ -16,7 +18,7 @@ namespace Umbraco.Core.Runtime
|
||||
internal 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 IUmbracoDatabase _db;
|
||||
@@ -32,6 +34,7 @@ namespace Umbraco.Core.Runtime
|
||||
// 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;
|
||||
|
||||
_dbFactory = new UmbracoDatabaseFactory(
|
||||
Constants.System.UmbracoConnectionName,
|
||||
_logger,
|
||||
@@ -40,6 +43,12 @@ namespace Umbraco.Core.Runtime
|
||||
|
||||
public async Task<bool> AcquireLockAsync(int millisecondsTimeout)
|
||||
{
|
||||
if (!_dbFactory.Configured)
|
||||
{
|
||||
// if we aren't configured, then we're in an install state, in which case we have no choice but to assume we can acquire
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(_dbFactory.SqlContext.SqlSyntax is SqlServerSyntaxProvider sqlServerSyntaxProvider))
|
||||
throw new NotSupportedException("SqlMainDomLock is only supported for Sql Server");
|
||||
|
||||
@@ -119,6 +128,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; } = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId()).GenerateHash<SHA1>();
|
||||
|
||||
private void ListeningLoop()
|
||||
{
|
||||
while (true)
|
||||
@@ -126,6 +145,12 @@ namespace Umbraco.Core.Runtime
|
||||
// poll every 1 second
|
||||
Thread.Sleep(1000);
|
||||
|
||||
if (!_dbFactory.Configured)
|
||||
{
|
||||
// if we aren't configured, we just keep looping since we can't query the db
|
||||
continue;
|
||||
}
|
||||
|
||||
lock (_locker)
|
||||
{
|
||||
// If cancellation has been requested we will just exit. Depending on timing of the shutdown,
|
||||
@@ -358,41 +383,44 @@ namespace Umbraco.Core.Runtime
|
||||
_cancellationTokenSource.Cancel();
|
||||
_cancellationTokenSource.Dispose();
|
||||
|
||||
var db = GetDatabase();
|
||||
try
|
||||
if (_dbFactory.Configured)
|
||||
{
|
||||
db.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
// get a write lock
|
||||
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
|
||||
|
||||
// When we are disposed, it means we have released the MainDom lock
|
||||
// and called all MainDom release callbacks, in this case
|
||||
// if another maindom is actually coming online we need
|
||||
// to signal to the MainDom coming online that we have shutdown.
|
||||
// To do that, we update the existing main dom DB record with a suffixed "_updated" string.
|
||||
// Otherwise, if we are just shutting down, we want to just delete the row.
|
||||
if (_mainDomChanging)
|
||||
var db = GetDatabase();
|
||||
try
|
||||
{
|
||||
_logger.Debug<SqlMainDomLock>("Releasing MainDom, updating row, new application is booting.");
|
||||
db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey });
|
||||
db.BeginTransaction(IsolationLevel.ReadCommitted);
|
||||
|
||||
// get a write lock
|
||||
_sqlServerSyntax.WriteLock(db, Constants.Locks.MainDom);
|
||||
|
||||
// When we are disposed, it means we have released the MainDom lock
|
||||
// and called all MainDom release callbacks, in this case
|
||||
// if another maindom is actually coming online we need
|
||||
// to signal to the MainDom coming online that we have shutdown.
|
||||
// To do that, we update the existing main dom DB record with a suffixed "_updated" string.
|
||||
// Otherwise, if we are just shutting down, we want to just delete the row.
|
||||
if (_mainDomChanging)
|
||||
{
|
||||
_logger.Debug<SqlMainDomLock>("Releasing MainDom, updating row, new application is booting.");
|
||||
db.Execute($"UPDATE umbracoKeyValue SET [value] = [value] + '{UpdatedSuffix}' WHERE [key] = @key", new { key = MainDomKey });
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Debug<SqlMainDomLock>("Releasing MainDom, deleting row, application is shutting down.");
|
||||
db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Debug<SqlMainDomLock>("Releasing MainDom, deleting row, application is shutting down.");
|
||||
db.Execute("DELETE FROM umbracoKeyValue WHERE [key] = @key", new { key = MainDomKey });
|
||||
ResetDatabase();
|
||||
_logger.Error<SqlMainDomLock>(ex, "Unexpected error during dipsose.");
|
||||
_hasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
db?.CompleteTransaction();
|
||||
ResetDatabase();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ResetDatabase();
|
||||
_logger.Error<SqlMainDomLock>(ex, "Unexpected error during dipsose.");
|
||||
_hasError = true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
db?.CompleteTransaction();
|
||||
ResetDatabase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,6 +526,6 @@ namespace Umbraco.Core.Services
|
||||
OperationResult Rollback(int id, int versionId, string culture = "*", int userId = Constants.Security.SuperUserId);
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,16 @@
|
||||
namespace Umbraco.Core.Services
|
||||
using Umbraco.Core.Models;
|
||||
|
||||
namespace Umbraco.Core.Services
|
||||
{
|
||||
/// <summary>
|
||||
/// Placeholder for sharing logic between the content, media (and member) services
|
||||
/// TODO: Start sharing the logic!
|
||||
/// </summary>
|
||||
public interface IContentServiceBase : IService
|
||||
{ }
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks/fixes the data integrity of node paths/levels stored in the database
|
||||
/// </summary>
|
||||
ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Umbraco.Core.Services
|
||||
IEnumerable<string> GetAllRoles();
|
||||
IEnumerable<string> GetAllRoles(int memberId);
|
||||
IEnumerable<string> GetAllRoles(string username);
|
||||
IEnumerable<int> GetAllRolesIds();
|
||||
IEnumerable<int> GetAllRolesIds(int memberId);
|
||||
IEnumerable<int> GetAllRolesIds(string username);
|
||||
IEnumerable<T> GetMembersInRole(string roleName);
|
||||
IEnumerable<T> FindMembersInRole(string roleName, string usernameToMatch, StringPropertyMatchType matchType = StringPropertyMatchType.StartsWith);
|
||||
bool DeleteRole(string roleName, bool throwIfBeingUsed);
|
||||
|
||||
@@ -601,23 +601,27 @@ namespace Umbraco.Core.Services.Implement
|
||||
totalChildren = 0;
|
||||
return Enumerable.Empty<IContent>();
|
||||
}
|
||||
return GetPagedDescendantsLocked(contentPath[0].Path, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
return GetPagedLocked(GetPagedDescendantQuery(contentPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
return GetPagedLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IContent> GetPagedDescendantsLocked(string contentPath, long pageIndex, int pageSize, out long totalChildren,
|
||||
private IQuery<IContent> GetPagedDescendantQuery(string contentPath)
|
||||
{
|
||||
var query = Query<IContent>();
|
||||
if (!contentPath.IsNullOrWhiteSpace())
|
||||
query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar));
|
||||
return query;
|
||||
}
|
||||
|
||||
private IEnumerable<IContent> GetPagedLocked(IQuery<IContent> query, long pageIndex, int pageSize, out long totalChildren,
|
||||
IQuery<IContent> filter, Ordering ordering)
|
||||
{
|
||||
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
|
||||
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
|
||||
if (ordering == null) throw new ArgumentNullException(nameof(ordering));
|
||||
|
||||
var query = Query<IContent>();
|
||||
if (!contentPath.IsNullOrWhiteSpace())
|
||||
query.Where(x => x.Path.SqlStartsWith($"{contentPath},", TextColumnType.NVarchar));
|
||||
|
||||
return _documentRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
|
||||
@@ -1866,7 +1870,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
public OperationResult MoveToRecycleBin(IContent content, int userId)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
var moves = new List<Tuple<IContent, string>>();
|
||||
var moves = new List<(IContent, string)>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
@@ -1925,7 +1929,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
return;
|
||||
}
|
||||
|
||||
var moves = new List<Tuple<IContent, string>>();
|
||||
var moves = new List<(IContent, string)>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
@@ -1978,7 +1982,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// MUST be called from within WriteLock
|
||||
// trash indicates whether we are trashing, un-trashing, or not changing anything
|
||||
private void PerformMoveLocked(IContent content, int parentId, IContent parent, int userId,
|
||||
ICollection<Tuple<IContent, string>> moves,
|
||||
ICollection<(IContent, string)> moves,
|
||||
bool? trash)
|
||||
{
|
||||
content.WriterId = userId;
|
||||
@@ -1990,7 +1994,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var paths = new Dictionary<int, string>();
|
||||
|
||||
moves.Add(Tuple.Create(content, content.Path)); // capture original path
|
||||
moves.Add((content, content.Path)); // capture original path
|
||||
|
||||
//need to store the original path to lookup descendants based on it below
|
||||
var originalPath = content.Path;
|
||||
@@ -2007,20 +2011,24 @@ namespace Umbraco.Core.Services.Implement
|
||||
paths[content.Id] = (parent == null ? (parentId == Constants.System.RecycleBinContent ? "-1,-20" : Constants.System.RootString) : parent.Path) + "," + content.Id;
|
||||
|
||||
const int pageSize = 500;
|
||||
var total = long.MaxValue;
|
||||
while (total > 0)
|
||||
var query = GetPagedDescendantQuery(originalPath);
|
||||
long total;
|
||||
do
|
||||
{
|
||||
var descendants = GetPagedDescendantsLocked(originalPath, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending));
|
||||
// We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced
|
||||
var descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending));
|
||||
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path
|
||||
moves.Add((descendant, descendant.Path)); // capture original path
|
||||
|
||||
// update path and level since we do not update parentId
|
||||
descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
|
||||
descendant.Level += levelDelta;
|
||||
PerformMoveContentLocked(descendant, userId, trash);
|
||||
}
|
||||
}
|
||||
|
||||
} while (total > pageSize);
|
||||
|
||||
}
|
||||
|
||||
@@ -2375,6 +2383,25 @@ namespace Umbraco.Core.Services.Implement
|
||||
return OperationResult.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.ContentTree);
|
||||
|
||||
var report = _documentRepository.CheckDataIntegrity(options);
|
||||
|
||||
if (report.FixedIssues.Count > 0)
|
||||
{
|
||||
//The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref
|
||||
var root = new Content("root", -1, new ContentType(-1)) {Id = -1, Key = Guid.Empty};
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IContent>.EventArgs(new TreeChange<IContent>(root, TreeChangeTypes.RefreshAll)));
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Internal Methods
|
||||
@@ -2812,7 +2839,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
|
||||
|
||||
var changes = new List<TreeChange<IContent>>();
|
||||
var moves = new List<Tuple<IContent, string>>();
|
||||
var moves = new List<(IContent, string)>();
|
||||
var contentTypeIdsA = contentTypeIds.ToArray();
|
||||
|
||||
// using an immediate uow here because we keep making changes with
|
||||
|
||||
@@ -530,23 +530,27 @@ namespace Umbraco.Core.Services.Implement
|
||||
totalChildren = 0;
|
||||
return Enumerable.Empty<IMedia>();
|
||||
}
|
||||
return GetPagedDescendantsLocked(mediaPath[0].Path, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
return GetPagedLocked(GetPagedDescendantQuery(mediaPath[0].Path), pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
return GetPagedDescendantsLocked(null, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
return GetPagedLocked(GetPagedDescendantQuery(null), pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<IMedia> GetPagedDescendantsLocked(string mediaPath, long pageIndex, int pageSize, out long totalChildren,
|
||||
private IQuery<IMedia> GetPagedDescendantQuery(string mediaPath)
|
||||
{
|
||||
var query = Query<IMedia>();
|
||||
if (!mediaPath.IsNullOrWhiteSpace())
|
||||
query.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar));
|
||||
return query;
|
||||
}
|
||||
|
||||
private IEnumerable<IMedia> GetPagedLocked(IQuery<IMedia> query, long pageIndex, int pageSize, out long totalChildren,
|
||||
IQuery<IMedia> filter, Ordering ordering)
|
||||
{
|
||||
if (pageIndex < 0) throw new ArgumentOutOfRangeException(nameof(pageIndex));
|
||||
if (pageSize <= 0) throw new ArgumentOutOfRangeException(nameof(pageSize));
|
||||
if (ordering == null) throw new ArgumentNullException(nameof(ordering));
|
||||
|
||||
var query = Query<IMedia>();
|
||||
if (!mediaPath.IsNullOrWhiteSpace())
|
||||
query.Where(x => x.Path.SqlStartsWith(mediaPath + ",", TextColumnType.NVarchar));
|
||||
|
||||
return _mediaRepository.GetPage(query, pageIndex, pageSize, out totalChildren, filter, ordering);
|
||||
}
|
||||
|
||||
@@ -888,7 +892,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
public Attempt<OperationResult> MoveToRecycleBin(IMedia media, int userId = Constants.Security.SuperUserId)
|
||||
{
|
||||
var evtMsgs = EventMessagesFactory.Get();
|
||||
var moves = new List<Tuple<IMedia, string>>();
|
||||
var moves = new List<(IMedia, string)>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
@@ -940,7 +944,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
return OperationResult.Attempt.Succeed(evtMsgs);
|
||||
}
|
||||
|
||||
var moves = new List<Tuple<IMedia, string>>();
|
||||
var moves = new List<(IMedia, string)>();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
@@ -979,7 +983,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
// MUST be called from within WriteLock
|
||||
// trash indicates whether we are trashing, un-trashing, or not changing anything
|
||||
private void PerformMoveLocked(IMedia media, int parentId, IMedia parent, int userId, ICollection<Tuple<IMedia, string>> moves, bool? trash)
|
||||
private void PerformMoveLocked(IMedia media, int parentId, IMedia parent, int userId, ICollection<(IMedia, string)> moves, bool? trash)
|
||||
{
|
||||
media.ParentId = parentId;
|
||||
|
||||
@@ -989,7 +993,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
|
||||
var paths = new Dictionary<int, string>();
|
||||
|
||||
moves.Add(Tuple.Create(media, media.Path)); // capture original path
|
||||
moves.Add((media, media.Path)); // capture original path
|
||||
|
||||
//need to store the original path to lookup descendants based on it below
|
||||
var originalPath = media.Path;
|
||||
@@ -1006,21 +1010,25 @@ namespace Umbraco.Core.Services.Implement
|
||||
paths[media.Id] = (parent == null ? (parentId == Constants.System.RecycleBinMedia ? "-1,-21" : Constants.System.RootString) : parent.Path) + "," + media.Id;
|
||||
|
||||
const int pageSize = 500;
|
||||
var page = 0;
|
||||
var total = long.MaxValue;
|
||||
while (page * pageSize < total)
|
||||
var query = GetPagedDescendantQuery(originalPath);
|
||||
long total;
|
||||
do
|
||||
{
|
||||
var descendants = GetPagedDescendantsLocked(originalPath, page++, pageSize, out total, null, Ordering.By("Path", Direction.Ascending));
|
||||
// We always page a page 0 because for each page, we are moving the result so the resulting total will be reduced
|
||||
var descendants = GetPagedLocked(query, 0, pageSize, out total, null, Ordering.By("Path", Direction.Ascending));
|
||||
|
||||
foreach (var descendant in descendants)
|
||||
{
|
||||
moves.Add(Tuple.Create(descendant, descendant.Path)); // capture original path
|
||||
moves.Add((descendant, descendant.Path)); // capture original path
|
||||
|
||||
// update path and level since we do not update parentId
|
||||
descendant.Path = paths[descendant.Id] = paths[descendant.ParentId] + "," + descendant.Id;
|
||||
descendant.Level += levelDelta;
|
||||
PerformMoveMediaLocked(descendant, userId, trash);
|
||||
}
|
||||
}
|
||||
|
||||
} while (total > pageSize);
|
||||
|
||||
}
|
||||
|
||||
private void PerformMoveMediaLocked(IMedia media, int userId, bool? trash)
|
||||
@@ -1139,6 +1147,26 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
public ContentDataIntegrityReport CheckDataIntegrity(ContentDataIntegrityReportOptions options)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.WriteLock(Constants.Locks.MediaTree);
|
||||
|
||||
var report = _mediaRepository.CheckDataIntegrity(options);
|
||||
|
||||
if (report.FixedIssues.Count > 0)
|
||||
{
|
||||
//The event args needs a content item so we'll make a fake one with enough properties to not cause a null ref
|
||||
var root = new Models.Media("root", -1, new MediaType(-1)) { Id = -1, Key = Guid.Empty };
|
||||
scope.Events.Dispatch(TreeChanged, this, new TreeChange<IMedia>.EventArgs(new TreeChange<IMedia>(root, TreeChangeTypes.RefreshAll)));
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -1277,7 +1305,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
// which we need for many things like keeping caches in sync, but we can surely do this MUCH better.
|
||||
|
||||
var changes = new List<TreeChange<IMedia>>();
|
||||
var moves = new List<Tuple<IMedia, string>>();
|
||||
var moves = new List<(IMedia, string)>();
|
||||
var mediaTypeIdsA = mediaTypeIds.ToArray();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
@@ -1358,5 +1386,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
saveEventArgs.CanCancel = false;
|
||||
scope.Events.Dispatch(Saved, this, saveEventArgs);
|
||||
}
|
||||
|
||||
|
||||
if (withIdentity == false)
|
||||
return;
|
||||
|
||||
@@ -816,8 +816,8 @@ namespace Umbraco.Core.Services.Implement
|
||||
{
|
||||
//trimming username and email to make sure we have no trailing space
|
||||
member.Username = member.Username.Trim();
|
||||
member.Email = member.Email.Trim();
|
||||
|
||||
member.Email = member.Email.Trim();
|
||||
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
{
|
||||
var saveEventArgs = new SaveEventArgs<IMember>(member);
|
||||
@@ -971,6 +971,35 @@ namespace Umbraco.Core.Services.Implement
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetAllRolesIds()
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
return _memberGroupRepository.GetMany().Select(x => x.Id).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetAllRolesIds(int memberId)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
var result = _memberGroupRepository.GetMemberGroupsForMember(memberId);
|
||||
return result.Select(x => x.Id).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<int> GetAllRolesIds(string username)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
{
|
||||
scope.ReadLock(Constants.Locks.MemberTree);
|
||||
var result = _memberGroupRepository.GetMemberGroupsForMember(username);
|
||||
return result.Select(x => x.Id).Distinct();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<IMember> GetMembersInRole(string roleName)
|
||||
{
|
||||
using (var scope = ScopeProvider.CreateScope(autoComplete: true))
|
||||
@@ -1241,7 +1270,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// Exports a member.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is internal for now and is used to export a member in the member editor,
|
||||
/// This is internal for now and is used to export a member in the member editor,
|
||||
/// it will raise an event so that auditing logs can be created.
|
||||
/// </remarks>
|
||||
internal MemberExportModel ExportMember(Guid key)
|
||||
|
||||
@@ -372,7 +372,7 @@ namespace Umbraco.Core.Services.Implement
|
||||
/// <returns></returns>
|
||||
public string GetDefaultMemberType()
|
||||
{
|
||||
return "writer";
|
||||
return Constants.Security.WriterGroupAlias;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web.Security;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
@@ -116,5 +117,17 @@ namespace Umbraco.Core.Services
|
||||
var permissionCollection = userService.GetPermissions(user, nodeId);
|
||||
return permissionCollection.SelectMany(c => c.AssignedPermissions).Distinct().ToArray();
|
||||
}
|
||||
|
||||
internal static IEnumerable<IProfile> GetProfilesById(this IUserService userService, params int[] ids)
|
||||
{
|
||||
var fullUsers = userService.GetUsersById(ids);
|
||||
|
||||
return fullUsers.Select(user =>
|
||||
{
|
||||
var asProfile = user as IProfile;
|
||||
return asProfile ?? new UserProfile(user.Id, user.Name);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,21 @@ namespace Umbraco.Core
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines the extension of the path or URL
|
||||
/// </summary>
|
||||
/// <param name="file"></param>
|
||||
/// <returns>Extension of the file</returns>
|
||||
public static string GetFileExtension(this string file)
|
||||
{
|
||||
//Find any characters between the last . and the start of a query string or the end of the string
|
||||
const string pattern = @"(?<extension>\.[^\.\?]+)(\?.*|$)";
|
||||
var match = Regex.Match(file, pattern);
|
||||
return match.Success
|
||||
? match.Groups["extension"].Value
|
||||
: string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Based on the input string, this will detect if the string is a JS path or a JS snippet.
|
||||
/// If a path cannot be determined, then it is assumed to be a snippet the original text is returned
|
||||
|
||||
@@ -128,10 +128,15 @@
|
||||
</Compile>
|
||||
-->
|
||||
<Compile Include="AssemblyExtensions.cs" />
|
||||
<Compile Include="Constants-SqlTemplates.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\ContentTypeDto80.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyDataDto80.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\Models\PropertyTypeDto80.cs" />
|
||||
<Compile Include="Models\ContentDataIntegrityReport.cs" />
|
||||
<Compile Include="Models\ContentDataIntegrityReportEntry.cs" />
|
||||
<Compile Include="Models\ContentDataIntegrityReportOptions.cs" />
|
||||
<Compile Include="Models\InstallLog.cs" />
|
||||
<Compile Include="Models\RelationTypeExtensions.cs" />
|
||||
<Compile Include="Persistence\Repositories\IInstallationRepository.cs" />
|
||||
<Compile Include="Persistence\Repositories\Implement\InstallationRepository.cs" />
|
||||
<Compile Include="Services\Implement\InstallationService.cs" />
|
||||
@@ -256,6 +261,7 @@
|
||||
<Compile Include="Manifest\ManifestFilterCollectionBuilder.cs" />
|
||||
<Compile Include="Mapping\MapperContext.cs" />
|
||||
<Compile Include="Migrations\Upgrade\Common\DeleteKeysAndIndexes.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\AddPackagesSectionAccess.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\ContentPickerPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DecimalPreValueMigrator.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\IPreValueMigrator.cs" />
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
using Examine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Composing;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Core.Models.Membership;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Core.Strings;
|
||||
|
||||
@@ -16,20 +20,46 @@ namespace Umbraco.Examine
|
||||
{
|
||||
private readonly UrlSegmentProviderCollection _urlSegmentProviders;
|
||||
private readonly IUserService _userService;
|
||||
private readonly IScopeProvider _scopeProvider;
|
||||
|
||||
[Obsolete("Use the other ctor instead")]
|
||||
public ContentValueSetBuilder(PropertyEditorCollection propertyEditors,
|
||||
UrlSegmentProviderCollection urlSegmentProviders,
|
||||
IUserService userService,
|
||||
bool publishedValuesOnly)
|
||||
: this(propertyEditors, urlSegmentProviders, userService, Current.ScopeProvider, publishedValuesOnly)
|
||||
{
|
||||
}
|
||||
|
||||
public ContentValueSetBuilder(PropertyEditorCollection propertyEditors,
|
||||
UrlSegmentProviderCollection urlSegmentProviders,
|
||||
IUserService userService,
|
||||
IScopeProvider scopeProvider,
|
||||
bool publishedValuesOnly)
|
||||
: base(propertyEditors, publishedValuesOnly)
|
||||
{
|
||||
_urlSegmentProviders = urlSegmentProviders;
|
||||
_userService = userService;
|
||||
_scopeProvider = scopeProvider;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override IEnumerable<ValueSet> GetValueSets(params IContent[] content)
|
||||
{
|
||||
Dictionary<int, IProfile> creatorIds;
|
||||
Dictionary<int, IProfile> writerIds;
|
||||
|
||||
// We can lookup all of the creator/writer names at once which can save some
|
||||
// processing below instead of one by one.
|
||||
using (var scope = _scopeProvider.CreateScope())
|
||||
{
|
||||
creatorIds = _userService.GetProfilesById(content.Select(x => x.CreatorId).ToArray())
|
||||
.ToDictionary(x => x.Id, x => x);
|
||||
writerIds = _userService.GetProfilesById(content.Select(x => x.WriterId).ToArray())
|
||||
.ToDictionary(x => x.Id, x => x);
|
||||
scope.Complete();
|
||||
}
|
||||
|
||||
// TODO: There is a lot of boxing going on here and ultimately all values will be boxed by Lucene anyways
|
||||
// but I wonder if there's a way to reduce the boxing that we have to do or if it will matter in the end since
|
||||
// Lucene will do it no matter what? One idea was to create a `FieldValue` struct which would contain `object`, `object[]`, `ValueType` and `ValueType[]`
|
||||
@@ -58,8 +88,8 @@ namespace Umbraco.Examine
|
||||
{"urlName", urlValue?.Yield() ?? Enumerable.Empty<string>()}, //Always add invariant urlName
|
||||
{"path", c.Path?.Yield() ?? Enumerable.Empty<string>()},
|
||||
{"nodeType", c.ContentType.Id.ToString().Yield() ?? Enumerable.Empty<string>()},
|
||||
{"creatorName", (c.GetCreatorProfile(_userService)?.Name ?? "??").Yield() },
|
||||
{"writerName",(c.GetWriterProfile(_userService)?.Name ?? "??").Yield() },
|
||||
{"creatorName", (creatorIds.TryGetValue(c.CreatorId, out var creatorProfile) ? creatorProfile.Name : "??").Yield() },
|
||||
{"writerName", (writerIds.TryGetValue(c.WriterId, out var writerProfile) ? writerProfile.Name : "??").Yield() },
|
||||
{"writerID", new object[] {c.WriterId}},
|
||||
{"templateID", new object[] {c.TemplateId ?? 0}},
|
||||
{UmbracoContentIndex.VariesByCultureFieldName, new object[] {"n"}},
|
||||
|
||||
@@ -21,7 +21,7 @@ using File = System.IO.File;
|
||||
|
||||
namespace Umbraco.ModelsBuilder.Embedded
|
||||
{
|
||||
internal class PureLiveModelFactory : ILivePublishedModelFactory, IRegisteredObject
|
||||
internal class PureLiveModelFactory : ILivePublishedModelFactory2, IRegisteredObject
|
||||
{
|
||||
private Assembly _modelsAssembly;
|
||||
private Infos _infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary<string, Type>() };
|
||||
@@ -134,6 +134,16 @@ namespace Umbraco.ModelsBuilder.Embedded
|
||||
return ctor();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool Enabled => _config.Enable;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Reset()
|
||||
{
|
||||
if (_config.Enable)
|
||||
ResetModels();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Compilation
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace Umbraco.Tests.Cache
|
||||
return "";
|
||||
});
|
||||
|
||||
Assert.AreEqual(counter, 1);
|
||||
Assert.AreEqual(1, counter);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
|
||||
[Test]
|
||||
public void EmailAddress()
|
||||
{
|
||||
Assert.AreEqual(SettingsSection.Content.NotificationEmailAddress, "robot@umbraco.dk");
|
||||
Assert.AreEqual("robot@umbraco.dk", SettingsSection.Content.NotificationEmailAddress);
|
||||
}
|
||||
[Test]
|
||||
public virtual void DisableHtmlEmail()
|
||||
@@ -27,17 +27,17 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
|
||||
[Test]
|
||||
public virtual void Can_Set_Multiple()
|
||||
{
|
||||
Assert.AreEqual(SettingsSection.Content.Error404Collection.Count(), 3);
|
||||
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).Culture, "default");
|
||||
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(0).ContentId, 1047);
|
||||
Assert.AreEqual(3, SettingsSection.Content.Error404Collection.Count());
|
||||
Assert.AreEqual("default", SettingsSection.Content.Error404Collection.ElementAt(0).Culture);
|
||||
Assert.AreEqual(1047, SettingsSection.Content.Error404Collection.ElementAt(0).ContentId);
|
||||
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentId);
|
||||
Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(0).HasContentKey);
|
||||
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).Culture, "en-US");
|
||||
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath, "$site/error [@name = 'error']");
|
||||
Assert.AreEqual("en-US", SettingsSection.Content.Error404Collection.ElementAt(1).Culture);
|
||||
Assert.AreEqual("$site/error [@name = 'error']", SettingsSection.Content.Error404Collection.ElementAt(1).ContentXPath);
|
||||
Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentId);
|
||||
Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(1).HasContentKey);
|
||||
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).Culture, "en-UK");
|
||||
Assert.AreEqual(SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey, new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC"));
|
||||
Assert.AreEqual("en-UK", SettingsSection.Content.Error404Collection.ElementAt(2).Culture);
|
||||
Assert.AreEqual(new Guid("8560867F-B88F-4C74-A9A4-679D8E5B3BFC"), SettingsSection.Content.Error404Collection.ElementAt(2).ContentKey);
|
||||
Assert.IsTrue(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentKey);
|
||||
Assert.IsFalse(SettingsSection.Content.Error404Collection.ElementAt(2).HasContentId);
|
||||
}
|
||||
@@ -47,23 +47,23 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
|
||||
{
|
||||
Assert.IsTrue(SettingsSection.Content.ImageFileTypes.All(x => "jpeg,jpg,gif,bmp,png,tiff,tif".Split(',').Contains(x)));
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public virtual void ImageAutoFillProperties()
|
||||
{
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.Count(), 2);
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias, "umbracoFile");
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias, "umbracoWidth");
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias, "umbracoHeight");
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias, "umbracoBytes");
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias, "umbracoExtension");
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias, "umbracoFile2");
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias, "umbracoWidth2");
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias, "umbracoHeight2");
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias, "umbracoBytes2");
|
||||
Assert.AreEqual(SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias, "umbracoExtension2");
|
||||
Assert.AreEqual(2, SettingsSection.Content.ImageAutoFillProperties.Count());
|
||||
Assert.AreEqual("umbracoFile", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).Alias);
|
||||
Assert.AreEqual("umbracoWidth", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).WidthFieldAlias);
|
||||
Assert.AreEqual("umbracoHeight", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).HeightFieldAlias);
|
||||
Assert.AreEqual("umbracoBytes", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).LengthFieldAlias);
|
||||
Assert.AreEqual("umbracoExtension", SettingsSection.Content.ImageAutoFillProperties.ElementAt(0).ExtensionFieldAlias);
|
||||
Assert.AreEqual("umbracoFile2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).Alias);
|
||||
Assert.AreEqual("umbracoWidth2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).WidthFieldAlias);
|
||||
Assert.AreEqual("umbracoHeight2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).HeightFieldAlias);
|
||||
Assert.AreEqual("umbracoBytes2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).LengthFieldAlias);
|
||||
Assert.AreEqual("umbracoExtension2", SettingsSection.Content.ImageAutoFillProperties.ElementAt(1).ExtensionFieldAlias);
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void PreviewBadge()
|
||||
{
|
||||
@@ -116,7 +116,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
|
||||
Debug.WriteLine("AllowedContainsExtension: {0}", allowedContainsExtension);
|
||||
Debug.WriteLine("DisallowedContainsExtension: {0}", disallowedContainsExtension);
|
||||
|
||||
Assert.AreEqual(SettingsSection.Content.IsFileAllowedForUpload(extension), expected);
|
||||
Assert.AreEqual(expected, SettingsSection.Content.IsFileAllowedForUpload(extension));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace Umbraco.Tests.CoreThings
|
||||
|
||||
var xmlNode = cdata.GetXmlNode(xdoc);
|
||||
|
||||
Assert.AreEqual(xmlNode.InnerText, "hello world");
|
||||
Assert.AreEqual("hello world", xmlNode.InnerText);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -27,7 +27,7 @@ namespace Umbraco.Tests.CoreThings
|
||||
|
||||
var xmlNode = cdata.GetXmlNode(xdoc);
|
||||
|
||||
Assert.AreEqual(xmlNode.InnerText, "hello world");
|
||||
Assert.AreEqual("hello world", xmlNode.InnerText);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -41,7 +41,7 @@ namespace Umbraco.Tests.CoreThings
|
||||
|
||||
var xmlNode = cdata.GetXmlNode(xdoc);
|
||||
|
||||
Assert.AreEqual(xmlNode.InnerText, "hello world");
|
||||
Assert.AreEqual("hello world", xmlNode.InnerText);
|
||||
Assert.AreEqual(xml, xdoc.OuterXml);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,6 @@ using Umbraco.Web.PropertyEditors;
|
||||
|
||||
namespace Umbraco.Tests.Models
|
||||
{
|
||||
|
||||
[TestFixture]
|
||||
public class ContentTests : UmbracoTestBase
|
||||
{
|
||||
@@ -42,7 +41,7 @@ namespace Umbraco.Tests.Models
|
||||
|
||||
// all this is required so we can validate properties...
|
||||
var editor = new TextboxPropertyEditor(Mock.Of<ILogger>()) { Alias = "test" };
|
||||
Composition.Register(_ => new DataEditorCollection(new [] { editor }));
|
||||
Composition.Register(_ => new DataEditorCollection(new[] { editor }));
|
||||
Composition.Register<PropertyEditorCollection>();
|
||||
var dataType = Mock.Of<IDataType>();
|
||||
Mock.Get(dataType).Setup(x => x.Configuration).Returns(() => new object());
|
||||
@@ -55,7 +54,7 @@ namespace Umbraco.Tests.Models
|
||||
var mediaTypeService = Mock.Of<IMediaTypeService>();
|
||||
var memberTypeService = Mock.Of<IMemberTypeService>();
|
||||
Composition.Register(_ => ServiceContext.CreatePartial(dataTypeService: dataTypeService, contentTypeBaseServiceProvider: new ContentTypeBaseServiceProvider(_contentTypeService, mediaTypeService, memberTypeService)));
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -458,7 +457,7 @@ namespace Umbraco.Tests.Models
|
||||
Assert.IsTrue(prop.WasPropertyDirty("Id"));
|
||||
}
|
||||
Assert.IsTrue(content.WasPropertyDirty("CultureInfos"));
|
||||
foreach(var culture in content.CultureInfos)
|
||||
foreach (var culture in content.CultureInfos)
|
||||
{
|
||||
Assert.IsTrue(culture.WasDirty());
|
||||
Assert.IsTrue(culture.WasPropertyDirty("Name"));
|
||||
@@ -539,7 +538,7 @@ namespace Umbraco.Tests.Models
|
||||
var content = MockedContent.CreateTextpageContent(contentType, "Textpage", -1);
|
||||
|
||||
// Act
|
||||
content.PropertyValues(new { title = "This is the new title"});
|
||||
content.PropertyValues(new { title = "This is the new title" });
|
||||
|
||||
// Assert
|
||||
Assert.That(content.Properties.Any(), Is.True);
|
||||
@@ -603,13 +602,13 @@ namespace Umbraco.Tests.Models
|
||||
|
||||
// Act
|
||||
contentType.PropertyGroups["Content"].PropertyTypes.Add(new PropertyType("test", ValueStorageType.Ntext, "subtitle")
|
||||
{
|
||||
Name = "Subtitle",
|
||||
Description = "Optional subtitle",
|
||||
Mandatory = false,
|
||||
SortOrder = 3,
|
||||
DataTypeId = -88
|
||||
});
|
||||
{
|
||||
Name = "Subtitle",
|
||||
Description = "Optional subtitle",
|
||||
Mandatory = false,
|
||||
SortOrder = 3,
|
||||
DataTypeId = -88
|
||||
});
|
||||
|
||||
// Assert
|
||||
Assert.That(contentType.PropertyGroups["Content"].PropertyTypes.Count, Is.EqualTo(3));
|
||||
@@ -626,9 +625,13 @@ namespace Umbraco.Tests.Models
|
||||
|
||||
// Act
|
||||
var propertyType = new PropertyType("test", ValueStorageType.Ntext, "subtitle")
|
||||
{
|
||||
Name = "Subtitle", Description = "Optional subtitle", Mandatory = false, SortOrder = 3, DataTypeId = -88
|
||||
};
|
||||
{
|
||||
Name = "Subtitle",
|
||||
Description = "Optional subtitle",
|
||||
Mandatory = false,
|
||||
SortOrder = 3,
|
||||
DataTypeId = -88
|
||||
};
|
||||
contentType.PropertyGroups["Content"].PropertyTypes.Add(propertyType);
|
||||
var newProperty = new Property(propertyType);
|
||||
newProperty.SetValue("This is a subtitle Test");
|
||||
@@ -650,14 +653,14 @@ namespace Umbraco.Tests.Models
|
||||
|
||||
// Act
|
||||
var propertyType = new PropertyType("test", ValueStorageType.Ntext, "subtitle")
|
||||
{
|
||||
Name = "Subtitle",
|
||||
Description = "Optional subtitle",
|
||||
Mandatory = false,
|
||||
SortOrder = 3,
|
||||
DataTypeId = -88
|
||||
};
|
||||
var propertyGroup = new PropertyGroup(true) { Name = "Test Group", SortOrder = 3};
|
||||
{
|
||||
Name = "Subtitle",
|
||||
Description = "Optional subtitle",
|
||||
Mandatory = false,
|
||||
SortOrder = 3,
|
||||
DataTypeId = -88
|
||||
};
|
||||
var propertyGroup = new PropertyGroup(true) { Name = "Test Group", SortOrder = 3 };
|
||||
propertyGroup.PropertyTypes.Add(propertyType);
|
||||
contentType.PropertyGroups.Add(propertyGroup);
|
||||
var newProperty = new Property(propertyType);
|
||||
@@ -681,9 +684,13 @@ namespace Umbraco.Tests.Models
|
||||
|
||||
// Act - note that the PropertyType's properties like SortOrder is not updated through the Content object
|
||||
var propertyType = new PropertyType("test", ValueStorageType.Ntext, "title")
|
||||
{
|
||||
Name = "Title", Description = "Title description added", Mandatory = false, SortOrder = 10, DataTypeId = -88
|
||||
};
|
||||
{
|
||||
Name = "Title",
|
||||
Description = "Title description added",
|
||||
Mandatory = false,
|
||||
SortOrder = 10,
|
||||
DataTypeId = -88
|
||||
};
|
||||
content.Properties.Add(new Property(propertyType));
|
||||
|
||||
// Assert
|
||||
@@ -911,13 +918,13 @@ namespace Umbraco.Tests.Models
|
||||
|
||||
// Act
|
||||
var propertyType = new PropertyType("test", ValueStorageType.Ntext, "subtitle")
|
||||
{
|
||||
Name = "Subtitle",
|
||||
Description = "Optional subtitle",
|
||||
Mandatory = false,
|
||||
SortOrder = 3,
|
||||
DataTypeId = -88
|
||||
};
|
||||
{
|
||||
Name = "Subtitle",
|
||||
Description = "Optional subtitle",
|
||||
Mandatory = false,
|
||||
SortOrder = 3,
|
||||
DataTypeId = -88
|
||||
};
|
||||
contentType.PropertyGroups["Content"].PropertyTypes.Add(propertyType);
|
||||
|
||||
// Assert
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Tests.Testing;
|
||||
|
||||
namespace Umbraco.Tests.Models
|
||||
{
|
||||
[TestFixture]
|
||||
public class PropertyTests : UmbracoTestBase
|
||||
{
|
||||
[Test]
|
||||
public void Can_Deep_Clone()
|
||||
{
|
||||
// needs to be within collection to support publishing
|
||||
var ptCollection = new PropertyTypeCollection(true, new[] {new PropertyType("TestPropertyEditor", ValueStorageType.Nvarchar, "test")
|
||||
{
|
||||
Id = 3,
|
||||
CreateDate = DateTime.Now,
|
||||
DataTypeId = 5,
|
||||
PropertyEditorAlias = "propTest",
|
||||
Description = "testing",
|
||||
Key = Guid.NewGuid(),
|
||||
Mandatory = true,
|
||||
Name = "Test",
|
||||
PropertyGroupId = new Lazy<int>(() => 11),
|
||||
SortOrder = 9,
|
||||
UpdateDate = DateTime.Now,
|
||||
ValidationRegExp = "xxxx",
|
||||
ValueStorageType = ValueStorageType.Nvarchar
|
||||
}});
|
||||
|
||||
var property = new Property(123, ptCollection[0])
|
||||
{
|
||||
CreateDate = DateTime.Now,
|
||||
Id = 4,
|
||||
Key = Guid.NewGuid(),
|
||||
UpdateDate = DateTime.Now
|
||||
};
|
||||
|
||||
property.SetValue("hello");
|
||||
property.PublishValues();
|
||||
|
||||
var clone = (Property)property.DeepClone();
|
||||
|
||||
Assert.AreNotSame(clone, property);
|
||||
Assert.AreNotSame(clone.Values, property.Values);
|
||||
Assert.AreNotSame(property.PropertyType, clone.PropertyType);
|
||||
for (int i = 0; i < property.Values.Count; i++)
|
||||
{
|
||||
Assert.AreNotSame(property.Values.ElementAt(i), clone.Values.ElementAt(i));
|
||||
}
|
||||
|
||||
|
||||
//This double verifies by reflection
|
||||
var allProps = clone.GetType().GetProperties();
|
||||
foreach (var propertyInfo in allProps)
|
||||
{
|
||||
Assert.AreEqual(propertyInfo.GetValue(clone, null), propertyInfo.GetValue(property, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1693,7 +1693,7 @@ namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
Assert.IsTrue(testReader.Read());
|
||||
|
||||
Assert.AreEqual(testReader.Depth, 0);
|
||||
Assert.AreEqual(0, testReader.Depth);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2067,7 +2067,7 @@ namespace Umbraco.Tests.Persistence
|
||||
{
|
||||
Assert.IsTrue(testReader.Read());
|
||||
|
||||
Assert.AreEqual(testReader.RecordsAffected, -1);
|
||||
Assert.AreEqual(-1, testReader.RecordsAffected);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,13 +107,16 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
var repository = CreateRepository(provider);
|
||||
|
||||
// Act
|
||||
var relationType = repository.Get(RelationTypeDto.NodeIdSeed);
|
||||
var relationType = repository.Get(RelationTypeDto.NodeIdSeed + 2);
|
||||
|
||||
// Assert
|
||||
Assert.That(relationType, Is.Not.Null);
|
||||
Assert.That(relationType.HasIdentity, Is.True);
|
||||
Assert.That(relationType.Alias, Is.EqualTo("relateContentOnCopy"));
|
||||
Assert.That(relationType.Name, Is.EqualTo("Relate Content on Copy"));
|
||||
Assert.That(relationType.IsBidirectional, Is.True);
|
||||
Assert.That(relationType.Alias, Is.EqualTo("relateContentToMedia"));
|
||||
Assert.That(relationType.Name, Is.EqualTo("Relate Content to Media"));
|
||||
Assert.That(relationType.ChildObjectType, Is.EqualTo(Constants.ObjectTypes.Media));
|
||||
Assert.That(relationType.ParentObjectType, Is.EqualTo(Constants.ObjectTypes.Document));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +136,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
Assert.That(relationTypes, Is.Not.Null);
|
||||
Assert.That(relationTypes.Any(), Is.True);
|
||||
Assert.That(relationTypes.Any(x => x == null), Is.False);
|
||||
Assert.That(relationTypes.Count(), Is.EqualTo(7));
|
||||
Assert.That(relationTypes.Count(), Is.EqualTo(8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +193,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
int count = repository.Count(query);
|
||||
|
||||
// Assert
|
||||
Assert.That(count, Is.EqualTo(5));
|
||||
Assert.That(count, Is.EqualTo(6));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,8 +227,9 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
public void CreateTestData()
|
||||
{
|
||||
var relateContent = new RelationType("Relate Content on Copy", "relateContentOnCopy", true, Constants.ObjectTypes.Document, new Guid("C66BA18E-EAF3-4CFF-8A22-41B16D66A972"));
|
||||
var relateContentType = new RelationType("Relate ContentType on Copy", "relateContentTypeOnCopy", true, Constants.ObjectTypes.DocumentType, new Guid("A2CB7800-F571-4787-9638-BC48539A0EFB"));
|
||||
var relateContent = new RelationType("Relate Content on Copy", "relateContentOnCopy", true, Constants.ObjectTypes.Document, Constants.ObjectTypes.Document);
|
||||
var relateContentType = new RelationType("Relate ContentType on Copy", "relateContentTypeOnCopy", true, Constants.ObjectTypes.DocumentType, Constants.ObjectTypes.DocumentType);
|
||||
var relateContentMedia = new RelationType("Relate Content to Media", "relateContentToMedia", true, Constants.ObjectTypes.Document, Constants.ObjectTypes.Media);
|
||||
|
||||
var provider = TestObjects.GetScopeProvider(Logger);
|
||||
using (var scope = ScopeProvider.CreateScope())
|
||||
@@ -234,6 +238,7 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
|
||||
repository.Save(relateContent);//Id 2
|
||||
repository.Save(relateContentType);//Id 3
|
||||
repository.Save(relateContentMedia);//Id 4
|
||||
scope.Complete();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,15 +294,13 @@ namespace Umbraco.Tests.Persistence.Repositories
|
||||
stylesheet = repository.Get("missing.css");
|
||||
Assert.IsNull(stylesheet);
|
||||
|
||||
// fixed in 7.3 - 7.2.8 used to...
|
||||
Assert.Throws<UnauthorizedAccessException>(() =>
|
||||
{
|
||||
stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist
|
||||
});
|
||||
Assert.Throws<UnauthorizedAccessException>(() =>
|
||||
{
|
||||
stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists
|
||||
});
|
||||
// #7713 changes behaviour to return null when outside the filesystem
|
||||
// to accomodate changing the CSS path and not flooding the backoffice with errors
|
||||
stylesheet = repository.Get("\\test-path-4.css"); // outside the filesystem, does not exist
|
||||
Assert.IsNull(stylesheet);
|
||||
|
||||
stylesheet = repository.Get("../packages.config"); // outside the filesystem, exists
|
||||
Assert.IsNull(stylesheet);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
@@ -27,7 +25,6 @@ using Umbraco.Web.Cache;
|
||||
using Umbraco.Web.PublishedCache;
|
||||
using Umbraco.Web.PublishedCache.NuCache;
|
||||
using Umbraco.Web.PublishedCache.NuCache.DataSource;
|
||||
using Umbraco.Web.PublishedCache.NuCache.Snap;
|
||||
|
||||
namespace Umbraco.Tests.PublishedContent
|
||||
{
|
||||
@@ -47,7 +44,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
_snapshotService?.Dispose();
|
||||
}
|
||||
|
||||
private void Init(IEnumerable<ContentNodeKit> kits)
|
||||
private void Init(Func<IEnumerable<ContentNodeKit>> kits)
|
||||
{
|
||||
Current.Reset();
|
||||
|
||||
@@ -136,7 +133,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
_snapshotAccessor = new TestPublishedSnapshotAccessor();
|
||||
|
||||
// create a data source for NuCache
|
||||
_source = new TestDataSource(kits);
|
||||
_source = new TestDataSource(kits());
|
||||
|
||||
// at last, create the complete NuCache snapshot service!
|
||||
var options = new PublishedSnapshotServiceOptions { IgnoreLocalDb = true };
|
||||
@@ -374,7 +371,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void EmptyTest()
|
||||
{
|
||||
Init(Enumerable.Empty<ContentNodeKit>());
|
||||
Init(() => Enumerable.Empty<ContentNodeKit>());
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
@@ -386,7 +383,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void ChildrenTest()
|
||||
{
|
||||
Init(GetInvariantKits());
|
||||
Init(GetInvariantKits);
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
@@ -413,7 +410,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void ParentTest()
|
||||
{
|
||||
Init(GetInvariantKits());
|
||||
Init(GetInvariantKits);
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
@@ -439,7 +436,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void MoveToRootTest()
|
||||
{
|
||||
Init(GetInvariantKits());
|
||||
Init(GetInvariantKits);
|
||||
|
||||
// get snapshot
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
@@ -481,7 +478,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void MoveFromRootTest()
|
||||
{
|
||||
Init(GetInvariantKits());
|
||||
Init(GetInvariantKits);
|
||||
|
||||
// get snapshot
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
@@ -523,7 +520,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void ReOrderTest()
|
||||
{
|
||||
Init(GetInvariantKits());
|
||||
Init(GetInvariantKits);
|
||||
|
||||
// get snapshot
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
@@ -598,7 +595,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void MoveTest()
|
||||
{
|
||||
Init(GetInvariantKits());
|
||||
Init(GetInvariantKits);
|
||||
|
||||
// get snapshot
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
@@ -699,11 +696,61 @@ namespace Umbraco.Tests.PublishedContent
|
||||
Assert.AreEqual(1, snapshot.Content.GetById(7).Parent?.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Clear_Branch_Locked()
|
||||
{
|
||||
// This test replicates an issue we saw here https://github.com/umbraco/Umbraco-CMS/pull/7907#issuecomment-610259393
|
||||
// The data was sent to me and this replicates it's structure
|
||||
|
||||
var paths = new Dictionary<int, string> { { -1, "-1" } };
|
||||
|
||||
Init(() => new List<ContentNodeKit>
|
||||
{
|
||||
CreateInvariantKit(1, -1, 1, paths), // first level
|
||||
CreateInvariantKit(2, 1, 1, paths), // second level
|
||||
CreateInvariantKit(3, 2, 1, paths), // third level
|
||||
|
||||
CreateInvariantKit(4, 3, 1, paths), // fourth level (we'll copy this one to the same level)
|
||||
|
||||
CreateInvariantKit(5, 4, 1, paths), // 6th level
|
||||
|
||||
CreateInvariantKit(6, 5, 2, paths), // 7th level
|
||||
CreateInvariantKit(7, 5, 3, paths),
|
||||
CreateInvariantKit(8, 5, 4, paths),
|
||||
CreateInvariantKit(9, 5, 5, paths),
|
||||
CreateInvariantKit(10, 5, 6, paths)
|
||||
});
|
||||
|
||||
// get snapshot
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
|
||||
var snapshotService = (PublishedSnapshotService)_snapshotService;
|
||||
var contentStore = snapshotService.GetContentStore();
|
||||
//This will set a flag to force creating a new Gen next time the store is locked (i.e. In Notify)
|
||||
contentStore.CreateSnapshot();
|
||||
|
||||
// notify - which ensures there are 2 generations in the cache meaning each LinkedNode has a Next value.
|
||||
_snapshotService.Notify(new[]
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch)
|
||||
}, out _, out _);
|
||||
|
||||
// refresh the branch again, this used to show the issue where a null ref exception would occur
|
||||
// because in the ClearBranchLocked logic, when SetValueLocked was called within a recursive call
|
||||
// to a child, we null out the .Value of the LinkedNode within the while loop because we didn't capture
|
||||
// this value before recursing.
|
||||
Assert.DoesNotThrow(() =>
|
||||
_snapshotService.Notify(new[]
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload(4, Guid.Empty, TreeChangeTypes.RefreshBranch)
|
||||
}, out _, out _));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NestedVariationChildrenTest()
|
||||
{
|
||||
var mixedKits = GetNestedVariantKits();
|
||||
Init(mixedKits);
|
||||
Init(GetNestedVariantKits);
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
@@ -792,7 +839,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void VariantChildrenTest()
|
||||
{
|
||||
Init(GetVariantKits());
|
||||
Init(GetVariantKits);
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
@@ -864,7 +911,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void RemoveTest()
|
||||
{
|
||||
Init(GetInvariantKits());
|
||||
Init(GetInvariantKits);
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
@@ -913,7 +960,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
[Test]
|
||||
public void UpdateTest()
|
||||
{
|
||||
Init(GetInvariantKits());
|
||||
Init(GetInvariantKits);
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
@@ -960,13 +1007,13 @@ namespace Umbraco.Tests.PublishedContent
|
||||
documents = snapshot.Content.GetById(2).Children().ToArray();
|
||||
AssertDocuments(documents, "N9", "N8", "N7");
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AtRootTest()
|
||||
{
|
||||
Init(GetVariantWithDraftKits());
|
||||
Init(GetVariantWithDraftKits);
|
||||
|
||||
var snapshot = _snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
@@ -995,7 +1042,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
yield return CreateInvariantKit(2, 1, 1, paths);
|
||||
}
|
||||
|
||||
Init(GetKits());
|
||||
Init(GetKits);
|
||||
|
||||
var snapshotService = (PublishedSnapshotService)_snapshotService;
|
||||
var contentStore = snapshotService.GetContentStore();
|
||||
@@ -1034,7 +1081,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
yield return CreateInvariantKit(4, 1, 3, paths);
|
||||
}
|
||||
|
||||
Init(GetKits());
|
||||
Init(GetKits);
|
||||
|
||||
var snapshotService = (PublishedSnapshotService)_snapshotService;
|
||||
var contentStore = snapshotService.GetContentStore();
|
||||
@@ -1114,7 +1161,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
yield return CreateInvariantKit(40, 1, 3, paths);
|
||||
}
|
||||
|
||||
Init(GetKits());
|
||||
Init(GetKits);
|
||||
|
||||
var snapshotService = (PublishedSnapshotService)_snapshotService;
|
||||
var contentStore = snapshotService.GetContentStore();
|
||||
@@ -1133,7 +1180,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
|
||||
_snapshotService.Notify(new[]
|
||||
{
|
||||
new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshNode)
|
||||
new ContentCacheRefresher.JsonPayload(1, Guid.Empty, TreeChangeTypes.RefreshNode)
|
||||
}, out _, out _);
|
||||
|
||||
Assert.AreEqual(2, contentStore.Test.LiveGen);
|
||||
@@ -1153,7 +1200,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
/// 2) Save and publish it
|
||||
/// 3) Publish it with descendants
|
||||
/// 4) Repeat steps 2 and 3
|
||||
///
|
||||
///
|
||||
/// Which has caused an exception. To replicate this test:
|
||||
/// 1) RefreshBranch with kits for a branch where the top most node is unpublished
|
||||
/// 2) RefreshBranch with kits for the branch where the top most node is published
|
||||
@@ -1181,12 +1228,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
|
||||
//children of 1
|
||||
yield return CreateInvariantKit(20, 1, 1, paths);
|
||||
yield return CreateInvariantKit(30, 1, 2, paths);
|
||||
yield return CreateInvariantKit(30, 1, 2, paths);
|
||||
yield return CreateInvariantKit(40, 1, 3, paths);
|
||||
}
|
||||
|
||||
//init with all published
|
||||
Init(GetKits());
|
||||
Init(GetKits);
|
||||
|
||||
var snapshotService = (PublishedSnapshotService)_snapshotService;
|
||||
var contentStore = snapshotService.GetContentStore();
|
||||
@@ -1203,7 +1250,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
//Change the root publish flag
|
||||
var kit = rootKit.Clone();
|
||||
kit.DraftData = published ? null : kit.PublishedData;
|
||||
kit.PublishedData = published? kit.PublishedData : null;
|
||||
kit.PublishedData = published ? kit.PublishedData : null;
|
||||
_source.Kits[1] = kit;
|
||||
|
||||
_snapshotService.Notify(new[]
|
||||
@@ -1218,12 +1265,12 @@ namespace Umbraco.Tests.PublishedContent
|
||||
var (gen, contentNode) = contentStore.Test.GetValues(1)[0];
|
||||
Assert.AreEqual(assertGen, gen);
|
||||
//even when unpublishing/re-publishing/etc... the linked list is always maintained
|
||||
AssertLinkedNode(contentNode, 100, 2, 3, 20, 40);
|
||||
AssertLinkedNode(contentNode, 100, 2, 3, 20, 40);
|
||||
}
|
||||
|
||||
//unpublish the root
|
||||
ChangePublishFlagOfRoot(false, 2, TreeChangeTypes.RefreshBranch);
|
||||
|
||||
|
||||
//publish the root (since it's not published, it will cause a RefreshBranch)
|
||||
ChangePublishFlagOfRoot(true, 3, TreeChangeTypes.RefreshBranch);
|
||||
|
||||
@@ -1256,7 +1303,7 @@ namespace Umbraco.Tests.PublishedContent
|
||||
yield return CreateInvariantKit(4, 1, 3, paths);
|
||||
}
|
||||
|
||||
Init(GetKits());
|
||||
Init(GetKits);
|
||||
|
||||
var snapshotService = (PublishedSnapshotService)_snapshotService;
|
||||
var contentStore = snapshotService.GetContentStore();
|
||||
@@ -1312,6 +1359,17 @@ namespace Umbraco.Tests.PublishedContent
|
||||
AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultipleCacheIteration()
|
||||
{
|
||||
//see https://github.com/umbraco/Umbraco-CMS/issues/7798
|
||||
Init(GetInvariantKits);
|
||||
var snapshot = this._snapshotService.CreatePublishedSnapshot(previewToken: null);
|
||||
_snapshotAccessor.PublishedSnapshot = snapshot;
|
||||
|
||||
var items = snapshot.Content.GetByXPath("/root/itype");
|
||||
Assert.AreEqual(items.Count(), items.Count());
|
||||
}
|
||||
private void AssertLinkedNode(ContentNode node, int parent, int prevSibling, int nextSibling, int firstChild, int lastChild)
|
||||
{
|
||||
Assert.AreEqual(parent, node.ParentContentId);
|
||||
|
||||
@@ -1811,7 +1811,7 @@ namespace Umbraco.Tests.Services
|
||||
ServiceContext.ContentService.Save(content2, Constants.Security.SuperUserId);
|
||||
Assert.IsTrue(ServiceContext.ContentService.SaveAndPublish(content2, userId: 0).Success);
|
||||
|
||||
var editorGroup = ServiceContext.UserService.GetUserGroupByAlias("editor");
|
||||
var editorGroup = ServiceContext.UserService.GetUserGroupByAlias(Constants.Security.EditorGroupAlias);
|
||||
editorGroup.StartContentId = content1.Id;
|
||||
ServiceContext.UserService.Save(editorGroup);
|
||||
|
||||
|
||||
@@ -197,7 +197,17 @@ namespace Umbraco.Tests.Services
|
||||
|
||||
Assert.AreEqual(3, found.Count());
|
||||
}
|
||||
[Test]
|
||||
public void Can_Get_All_Roles_IDs()
|
||||
{
|
||||
ServiceContext.MemberService.AddRole("MyTestRole1");
|
||||
ServiceContext.MemberService.AddRole("MyTestRole2");
|
||||
ServiceContext.MemberService.AddRole("MyTestRole3");
|
||||
|
||||
var found = ServiceContext.MemberService.GetAllRolesIds();
|
||||
|
||||
Assert.AreEqual(3, found.Count());
|
||||
}
|
||||
[Test]
|
||||
public void Can_Get_All_Roles_By_Member_Id()
|
||||
{
|
||||
@@ -216,7 +226,24 @@ namespace Umbraco.Tests.Services
|
||||
Assert.AreEqual(2, memberRoles.Count());
|
||||
|
||||
}
|
||||
[Test]
|
||||
public void Can_Get_All_Roles_Ids_By_Member_Id()
|
||||
{
|
||||
IMemberType memberType = MockedContentTypes.CreateSimpleMemberType();
|
||||
ServiceContext.MemberTypeService.Save(memberType);
|
||||
IMember member = MockedMember.CreateSimpleMember(memberType, "test", "test@test.com", "pass", "test");
|
||||
ServiceContext.MemberService.Save(member);
|
||||
|
||||
ServiceContext.MemberService.AddRole("MyTestRole1");
|
||||
ServiceContext.MemberService.AddRole("MyTestRole2");
|
||||
ServiceContext.MemberService.AddRole("MyTestRole3");
|
||||
ServiceContext.MemberService.AssignRoles(new[] { member.Id }, new[] { "MyTestRole1", "MyTestRole2" });
|
||||
|
||||
var memberRoles = ServiceContext.MemberService.GetAllRolesIds(member.Id);
|
||||
|
||||
Assert.AreEqual(2, memberRoles.Count());
|
||||
|
||||
}
|
||||
[Test]
|
||||
public void Can_Get_All_Roles_By_Member_Username()
|
||||
{
|
||||
|
||||
@@ -110,8 +110,8 @@ namespace Umbraco.Tests.Services
|
||||
Assert.AreEqual("Test", rt.Name);
|
||||
Assert.AreEqual("repeatedEventOccurence", rt.Alias);
|
||||
Assert.AreEqual(false, rt.IsBidirectional);
|
||||
Assert.AreEqual(Constants.ObjectTypes.Document, rt.ChildObjectType.Value);
|
||||
Assert.AreEqual(Constants.ObjectTypes.Media, rt.ParentObjectType.Value);
|
||||
Assert.AreEqual(Constants.ObjectTypes.Document, rt.ParentObjectType.Value);
|
||||
Assert.AreEqual(Constants.ObjectTypes.Media, rt.ChildObjectType.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -924,6 +924,24 @@ namespace Umbraco.Tests.Services
|
||||
Assert.AreEqual(user.Id, profile.Id);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_By_Profile_Id_Must_return_null_if_user_not_exists()
|
||||
{
|
||||
var profile = ServiceContext.UserService.GetProfileById(42);
|
||||
|
||||
// Assert
|
||||
Assert.IsNull(profile);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetProfilesById_Must_empty_if_users_not_exists()
|
||||
{
|
||||
var profiles = ServiceContext.UserService.GetProfilesById(42);
|
||||
|
||||
// Assert
|
||||
CollectionAssert.IsEmpty(profiles);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Get_User_By_Username()
|
||||
{
|
||||
|
||||
@@ -70,6 +70,24 @@ namespace Umbraco.Tests.Strings
|
||||
Assert.AreEqual(stripped, result);
|
||||
}
|
||||
|
||||
[TestCase("../wtf.js?x=wtf", ".js")]
|
||||
[TestCase(".htaccess", ".htaccess")]
|
||||
[TestCase("path/to/file/image.png", ".png")]
|
||||
[TestCase("c:\\abc\\def\\ghi.jkl", ".jkl")]
|
||||
[TestCase("/root/folder.name/file.ext", ".ext")]
|
||||
[TestCase("http://www.domain.com/folder/name/file.txt", ".txt")]
|
||||
[TestCase("i/don't\\have\\an/extension", "")]
|
||||
[TestCase("https://some.where/path/to/file.ext?query=this&more=that", ".ext")]
|
||||
[TestCase("double_query.string/file.ext?query=abc?something.else", ".ext")]
|
||||
[TestCase("test.tar.gz", ".gz")]
|
||||
[TestCase("wierd.file,but._legal", "._legal")]
|
||||
[TestCase("one_char.x", ".x")]
|
||||
public void Get_File_Extension(string input, string result)
|
||||
{
|
||||
var extension = input.GetFileExtension();
|
||||
Assert.AreEqual(result, extension);
|
||||
}
|
||||
|
||||
[TestCase("'+alert(1234)+'", "+alert1234+")]
|
||||
[TestCase("'+alert(56+78)+'", "+alert56+78+")]
|
||||
[TestCase("{{file}}", "file")]
|
||||
|
||||
@@ -142,6 +142,7 @@
|
||||
<Compile Include="Models\CultureImpactTests.cs" />
|
||||
<Compile Include="Models\ImageProcessorImageUrlGeneratorTest.cs" />
|
||||
<Compile Include="Models\PathValidationTests.cs" />
|
||||
<Compile Include="Models\PropertyTests.cs" />
|
||||
<Compile Include="Models\VariationTests.cs" />
|
||||
<Compile Include="Persistence\Mappers\MapperTestBase.cs" />
|
||||
<Compile Include="Persistence\Repositories\DocumentRepositoryTest.cs" />
|
||||
|
||||
@@ -30,16 +30,22 @@ namespace Umbraco.Tests.UmbracoExamine
|
||||
/// </summary>
|
||||
internal static class IndexInitializer
|
||||
{
|
||||
public static ContentValueSetBuilder GetContentValueSetBuilder(PropertyEditorCollection propertyEditors, bool publishedValuesOnly)
|
||||
public static ContentValueSetBuilder GetContentValueSetBuilder(PropertyEditorCollection propertyEditors, IScopeProvider scopeProvider, bool publishedValuesOnly)
|
||||
{
|
||||
var contentValueSetBuilder = new ContentValueSetBuilder(propertyEditors, new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }), GetMockUserService(), publishedValuesOnly);
|
||||
var contentValueSetBuilder = new ContentValueSetBuilder(
|
||||
propertyEditors,
|
||||
new UrlSegmentProviderCollection(new[] { new DefaultUrlSegmentProvider() }),
|
||||
GetMockUserService(),
|
||||
scopeProvider,
|
||||
publishedValuesOnly);
|
||||
|
||||
return contentValueSetBuilder;
|
||||
}
|
||||
|
||||
public static ContentIndexPopulator GetContentIndexRebuilder(PropertyEditorCollection propertyEditors, IContentService contentService, ISqlContext sqlContext, bool publishedValuesOnly)
|
||||
public static ContentIndexPopulator GetContentIndexRebuilder(PropertyEditorCollection propertyEditors, IContentService contentService, IScopeProvider scopeProvider, bool publishedValuesOnly)
|
||||
{
|
||||
var contentValueSetBuilder = GetContentValueSetBuilder(propertyEditors, publishedValuesOnly);
|
||||
var contentIndexDataSource = new ContentIndexPopulator(true, null, contentService, sqlContext, contentValueSetBuilder);
|
||||
var contentValueSetBuilder = GetContentValueSetBuilder(propertyEditors, scopeProvider, publishedValuesOnly);
|
||||
var contentIndexDataSource = new ContentIndexPopulator(true, null, contentService, scopeProvider.SqlContext, contentValueSetBuilder);
|
||||
return contentIndexDataSource;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Umbraco.Tests.UmbracoExamine
|
||||
[Test]
|
||||
public void Index_Property_Data_With_Value_Indexer()
|
||||
{
|
||||
var contentValueSetBuilder = IndexInitializer.GetContentValueSetBuilder(Factory.GetInstance<PropertyEditorCollection>(), false);
|
||||
var contentValueSetBuilder = IndexInitializer.GetContentValueSetBuilder(Factory.GetInstance<PropertyEditorCollection>(), ScopeProvider, false);
|
||||
|
||||
using (var luceneDir = new RandomIdRamDirectory())
|
||||
using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir,
|
||||
@@ -121,7 +121,7 @@ namespace Umbraco.Tests.UmbracoExamine
|
||||
[Test]
|
||||
public void Rebuild_Index()
|
||||
{
|
||||
var contentRebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false);
|
||||
var contentRebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockContentService(), ScopeProvider, false);
|
||||
var mediaRebuilder = IndexInitializer.GetMediaIndexRebuilder(Factory.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockMediaService());
|
||||
|
||||
using (var luceneDir = new RandomIdRamDirectory())
|
||||
@@ -149,7 +149,7 @@ namespace Umbraco.Tests.UmbracoExamine
|
||||
[Test]
|
||||
public void Index_Protected_Content_Not_Indexed()
|
||||
{
|
||||
var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false);
|
||||
var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockContentService(), ScopeProvider, false);
|
||||
|
||||
|
||||
using (var luceneDir = new RandomIdRamDirectory())
|
||||
@@ -274,7 +274,7 @@ namespace Umbraco.Tests.UmbracoExamine
|
||||
[Test]
|
||||
public void Index_Reindex_Content()
|
||||
{
|
||||
var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false);
|
||||
var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockContentService(), ScopeProvider, false);
|
||||
using (var luceneDir = new RandomIdRamDirectory())
|
||||
using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir,
|
||||
validator: new ContentValueSetValidator(false)))
|
||||
@@ -315,7 +315,7 @@ namespace Umbraco.Tests.UmbracoExamine
|
||||
public void Index_Delete_Index_Item_Ensure_Heirarchy_Removed()
|
||||
{
|
||||
|
||||
var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockContentService(), ScopeProvider.SqlContext, false);
|
||||
var rebuilder = IndexInitializer.GetContentIndexRebuilder(Factory.GetInstance<PropertyEditorCollection>(), IndexInitializer.GetMockContentService(), ScopeProvider, false);
|
||||
|
||||
using (var luceneDir = new RandomIdRamDirectory())
|
||||
using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir))
|
||||
|
||||
@@ -55,7 +55,7 @@ namespace Umbraco.Tests.UmbracoExamine
|
||||
allRecs);
|
||||
|
||||
var propertyEditors = Factory.GetInstance<PropertyEditorCollection>();
|
||||
var rebuilder = IndexInitializer.GetContentIndexRebuilder(propertyEditors, contentService, ScopeProvider.SqlContext, true);
|
||||
var rebuilder = IndexInitializer.GetContentIndexRebuilder(propertyEditors, contentService, ScopeProvider, true);
|
||||
|
||||
using (var luceneDir = new RandomIdRamDirectory())
|
||||
using (var indexer = IndexInitializer.GetUmbracoIndexer(ProfilingLogger, luceneDir))
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace Umbraco.Tests.Web.AngularIntegration
|
||||
string cookieToken, headerToken;
|
||||
AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken);
|
||||
|
||||
Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken));
|
||||
Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,7 +45,7 @@ namespace Umbraco.Tests.Web.AngularIntegration
|
||||
string cookieToken, headerToken;
|
||||
AngularAntiForgeryHelper.GetTokens(out cookieToken, out headerToken);
|
||||
|
||||
Assert.AreEqual(true, AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken));
|
||||
Assert.IsTrue(AngularAntiForgeryHelper.ValidateTokens(cookieToken, headerToken));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ namespace Umbraco.Tests.Web
|
||||
|
||||
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null); //invariant property
|
||||
|
||||
Assert.AreEqual("_Properties.headerImage.invariant", ms.Keys.First());
|
||||
Assert.AreEqual("_Properties.headerImage.invariant.null", ms.Keys.First());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -73,9 +73,57 @@ namespace Umbraco.Tests.Web
|
||||
var localizationService = new Mock<ILocalizationService>();
|
||||
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
|
||||
|
||||
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //invariant property
|
||||
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US"); //variant property
|
||||
|
||||
Assert.AreEqual("_Properties.headerImage.en-US", ms.Keys.First());
|
||||
Assert.AreEqual("_Properties.headerImage.en-US.null", ms.Keys.First());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Add_Invariant_Segment_Property_Error()
|
||||
{
|
||||
var ms = new ModelStateDictionary();
|
||||
var localizationService = new Mock<ILocalizationService>();
|
||||
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
|
||||
|
||||
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", null, "mySegment"); //invariant/segment property
|
||||
|
||||
Assert.AreEqual("_Properties.headerImage.invariant.mySegment", ms.Keys.First());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Add_Variant_Segment_Property_Error()
|
||||
{
|
||||
var ms = new ModelStateDictionary();
|
||||
var localizationService = new Mock<ILocalizationService>();
|
||||
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
|
||||
|
||||
ms.AddPropertyError(new ValidationResult("no header image"), "headerImage", "en-US", "mySegment"); //variant/segment property
|
||||
|
||||
Assert.AreEqual("_Properties.headerImage.en-US.mySegment", ms.Keys.First());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Add_Invariant_Segment_Field_Property_Error()
|
||||
{
|
||||
var ms = new ModelStateDictionary();
|
||||
var localizationService = new Mock<ILocalizationService>();
|
||||
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
|
||||
|
||||
ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", null, "mySegment"); //invariant/segment property
|
||||
|
||||
Assert.AreEqual("_Properties.headerImage.invariant.mySegment.myField", ms.Keys.First());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Add_Variant_Segment_Field_Property_Error()
|
||||
{
|
||||
var ms = new ModelStateDictionary();
|
||||
var localizationService = new Mock<ILocalizationService>();
|
||||
localizationService.Setup(x => x.GetDefaultLanguageIsoCode()).Returns("en-US");
|
||||
|
||||
ms.AddPropertyError(new ValidationResult("no header image", new[] { "myField" }), "headerImage", "en-US", "mySegment"); //variant/segment property
|
||||
|
||||
Assert.AreEqual("_Properties.headerImage.en-US.mySegment.myField", ms.Keys.First());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
"tinyMCE": false,
|
||||
"FileReader": false,
|
||||
"Umbraco": false,
|
||||
"Utilities": false,
|
||||
"window": false,
|
||||
"LazyLoad": false,
|
||||
"ActiveXObject": false,
|
||||
"Bloodhound": false
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,9 +28,9 @@ module.exports = {
|
||||
installer: { files: "./src/installer/**/*.js", out: "umbraco.installer.js" },
|
||||
filters: { files: "./src/common/filters/**/*.js", out: "umbraco.filters.js" },
|
||||
resources: { files: "./src/common/resources/**/*.js", out: "umbraco.resources.js" },
|
||||
services: { files: "./src/common/services/**/*.js", out: "umbraco.services.js" },
|
||||
services: { files: ["./src/common/services/**/*.js", "./src/utilities.js"], out: "umbraco.services.js" },
|
||||
security: { files: "./src/common/interceptors/**/*.js", out: "umbraco.interceptors.js" },
|
||||
|
||||
|
||||
//the controllers for views
|
||||
controllers: {
|
||||
files: [
|
||||
|
||||
+182
-84
File diff suppressed because it is too large
Load Diff
@@ -42,7 +42,7 @@
|
||||
"npm": "6.13.6",
|
||||
"signalr": "2.4.0",
|
||||
"spectrum-colorpicker": "1.8.0",
|
||||
"tinymce": "4.9.7",
|
||||
"tinymce": "4.9.10",
|
||||
"typeahead.js": "0.11.1",
|
||||
"underscore": "1.9.1"
|
||||
},
|
||||
@@ -50,7 +50,7 @@
|
||||
"@babel/core": "7.6.4",
|
||||
"@babel/preset-env": "7.6.3",
|
||||
"autoprefixer": "9.6.5",
|
||||
"caniuse-lite": "^1.0.30001002",
|
||||
"caniuse-lite": "^1.0.30001037",
|
||||
"cssnano": "4.1.10",
|
||||
"fs": "0.0.2",
|
||||
"gulp": "4.0.2",
|
||||
|
||||
+15
-13
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function AppHeaderDirective(eventsService, appState, userService, focusService) {
|
||||
function AppHeaderDirective(eventsService, appState, userService, focusService, backdropService) {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
@@ -18,20 +18,20 @@
|
||||
];
|
||||
|
||||
// when a user logs out or timesout
|
||||
evts.push(eventsService.on("app.notAuthenticated", function() {
|
||||
evts.push(eventsService.on("app.notAuthenticated", function () {
|
||||
scope.authenticated = false;
|
||||
scope.user = null;
|
||||
}));
|
||||
|
||||
// when the application is ready and the user is authorized setup the data
|
||||
evts.push(eventsService.on("app.ready", function(evt, data) {
|
||||
|
||||
evts.push(eventsService.on("app.ready", function (evt, data) {
|
||||
|
||||
scope.authenticated = true;
|
||||
scope.user = data.user;
|
||||
|
||||
if (scope.user.avatars) {
|
||||
scope.avatar = [];
|
||||
if (angular.isArray(scope.user.avatars)) {
|
||||
if (Utilities.isArray(scope.user.avatars)) {
|
||||
for (var i = 0; i < scope.user.avatars.length; i++) {
|
||||
scope.avatar.push({ value: scope.user.avatars[i] });
|
||||
}
|
||||
@@ -40,13 +40,13 @@
|
||||
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("app.userRefresh", function(evt) {
|
||||
userService.refreshCurrentUser().then(function(data) {
|
||||
evts.push(eventsService.on("app.userRefresh", function (evt) {
|
||||
userService.refreshCurrentUser().then(function (data) {
|
||||
scope.user = data;
|
||||
|
||||
|
||||
if (scope.user.avatars) {
|
||||
scope.avatar = [];
|
||||
if (angular.isArray(scope.user.avatars)) {
|
||||
if (Utilities.isArray(scope.user.avatars)) {
|
||||
for (var i = 0; i < scope.user.avatars.length; i++) {
|
||||
scope.avatar.push({ value: scope.user.avatars[i] });
|
||||
}
|
||||
@@ -54,10 +54,10 @@
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
|
||||
scope.rememberFocus = focusService.rememberFocus;
|
||||
|
||||
scope.searchClick = function() {
|
||||
|
||||
scope.searchClick = function () {
|
||||
var showSearch = appState.getSearchState("show");
|
||||
appState.setSearchState("show", !showSearch);
|
||||
};
|
||||
@@ -71,13 +71,15 @@
|
||||
};
|
||||
|
||||
scope.avatarClick = function () {
|
||||
if(!scope.userDialog) {
|
||||
if (!scope.userDialog) {
|
||||
backdropService.open();
|
||||
scope.userDialog = {
|
||||
view: "user",
|
||||
show: true,
|
||||
close: function (oldModel) {
|
||||
scope.userDialog.show = false;
|
||||
scope.userDialog = null;
|
||||
backdropService.close();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
|
||||
+10
-10
@@ -7,8 +7,8 @@
|
||||
|
||||
var events = [];
|
||||
|
||||
scope.clickBackdrop = function(event) {
|
||||
if(scope.disableEventsOnClick === true) {
|
||||
scope.clickBackdrop = function (event) {
|
||||
if (scope.disableEventsOnClick === true) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
@@ -22,16 +22,16 @@
|
||||
|
||||
}
|
||||
|
||||
function setHighlight () {
|
||||
function setHighlight() {
|
||||
|
||||
scope.loading = true;
|
||||
|
||||
$timeout(function () {
|
||||
|
||||
// The element to highlight
|
||||
var highlightElement = angular.element(scope.highlightElement);
|
||||
var highlightElement = $(scope.highlightElement);
|
||||
|
||||
if(highlightElement && highlightElement.length > 0) {
|
||||
if (highlightElement && highlightElement.length > 0) {
|
||||
|
||||
var offset = highlightElement.offset();
|
||||
var width = highlightElement.outerWidth();
|
||||
@@ -48,7 +48,7 @@
|
||||
var rectRight = el.find(".umb-backdrop__rect--right");
|
||||
var rectBottom = el.find(".umb-backdrop__rect--bottom");
|
||||
var rectLeft = el.find(".umb-backdrop__rect--left");
|
||||
|
||||
|
||||
// Add the css
|
||||
scope.rectTopCss = { "height": topDistance, "left": leftDistance + "px", opacity: scope.backdropOpacity };
|
||||
scope.rectRightCss = { "left": leftAndWidth + "px", "top": topDistance + "px", "height": height, opacity: scope.backdropOpacity };
|
||||
@@ -56,14 +56,14 @@
|
||||
scope.rectLeftCss = { "width": leftDistance, opacity: scope.backdropOpacity };
|
||||
|
||||
// Prevent interaction in the highlighted area
|
||||
if(scope.highlightPreventClick) {
|
||||
if (scope.highlightPreventClick) {
|
||||
var preventClickElement = el.find(".umb-backdrop__highlight-prevent-click");
|
||||
preventClickElement.css({ "width": width, "height": height, "left": offset.left, "top": offset.top });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
scope.loading = false;
|
||||
scope.loading = false;
|
||||
|
||||
});
|
||||
|
||||
@@ -74,8 +74,8 @@
|
||||
}
|
||||
|
||||
events.push(scope.$watch("highlightElement", function (newValue, oldValue) {
|
||||
if(!newValue) {return;}
|
||||
if(newValue === oldValue) {return;}
|
||||
if (!newValue) { return; }
|
||||
if (newValue === oldValue) { return; }
|
||||
setHighlight();
|
||||
}));
|
||||
|
||||
|
||||
+14
-14
@@ -12,7 +12,7 @@
|
||||
onClose: "&"
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function umbSearchController($timeout, backdropService, searchService, focusService) {
|
||||
|
||||
var vm = this;
|
||||
@@ -25,7 +25,7 @@
|
||||
vm.handleKeyDown = handleKeyDown;
|
||||
vm.closeSearch = closeSearch;
|
||||
vm.focusSearch = focusSearch;
|
||||
|
||||
|
||||
//we need to capture the focus before this element is initialized.
|
||||
vm.focusBeforeOpening = focusService.getLastKnownFocus();
|
||||
|
||||
@@ -66,8 +66,8 @@
|
||||
*/
|
||||
function focusSearch() {
|
||||
vm.searchHasFocus = false;
|
||||
$timeout(function(){
|
||||
vm.searchHasFocus = true;
|
||||
$timeout(function () {
|
||||
vm.searchHasFocus = true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -76,14 +76,14 @@
|
||||
* @param {object} event
|
||||
*/
|
||||
function handleKeyDown(event) {
|
||||
|
||||
|
||||
// esc
|
||||
if(event.keyCode === 27) {
|
||||
if (event.keyCode === 27) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
closeSearch();
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
// up/down (navigate search results)
|
||||
@@ -132,7 +132,7 @@
|
||||
}
|
||||
|
||||
$timeout(function () {
|
||||
var resultElementLink = angular.element(".umb-search-item[active-result='true'] .umb-search-result__link");
|
||||
var resultElementLink = $(".umb-search-item[active-result='true'] .umb-search-result__link");
|
||||
resultElementLink[0].focus();
|
||||
});
|
||||
}
|
||||
@@ -142,10 +142,10 @@
|
||||
* Used to proxy a callback
|
||||
*/
|
||||
function closeSearch() {
|
||||
if(vm.focusBeforeOpening) {
|
||||
if (vm.focusBeforeOpening) {
|
||||
vm.focusBeforeOpening.focus();
|
||||
}
|
||||
if(vm.onClose) {
|
||||
if (vm.onClose) {
|
||||
vm.onClose();
|
||||
}
|
||||
}
|
||||
@@ -155,9 +155,9 @@
|
||||
* @param {string} searchQuery
|
||||
*/
|
||||
function search(searchQuery) {
|
||||
if(searchQuery.length > 0) {
|
||||
var search = {"term": searchQuery};
|
||||
searchService.searchAll(search).then(function(result){
|
||||
if (searchQuery.length > 0) {
|
||||
var search = { "term": searchQuery };
|
||||
searchService.searchAll(search).then(function (result) {
|
||||
//result is a dictionary of group Title and it's results
|
||||
var filtered = {};
|
||||
_.each(result, function (value, key) {
|
||||
|
||||
+14
-23
@@ -12,26 +12,25 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
|
||||
|
||||
var sectionItemsWidth = [];
|
||||
var evts = [];
|
||||
var maxSections = 8;
|
||||
|
||||
//setup scope vars
|
||||
scope.maxSections = maxSections;
|
||||
scope.overflowingSections = 0;
|
||||
scope.sections = [];
|
||||
scope.visibleSections = 0;
|
||||
scope.currentSection = appState.getSectionState("currentSection");
|
||||
scope.showTray = false; //appState.getGlobalState("showTray");
|
||||
scope.showTray = false;
|
||||
scope.stickyNavigation = appState.getGlobalState("stickyNavigation");
|
||||
scope.needTray = false;
|
||||
|
||||
function loadSections() {
|
||||
sectionService.getSectionsForUser()
|
||||
.then(function (result) {
|
||||
scope.sections = result;
|
||||
scope.visibleSections = scope.sections.length;
|
||||
|
||||
// store the width of each section so we can hide/show them based on browser width
|
||||
// we store them because the sections get removed from the dom and then we
|
||||
// can't tell when to show them gain
|
||||
$timeout(function () {
|
||||
$("#applications .sections li").each(function (index) {
|
||||
$("#applications .sections li:not(:last)").each(function (index) {
|
||||
sectionItemsWidth.push($(this).outerWidth());
|
||||
});
|
||||
});
|
||||
@@ -42,25 +41,22 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
|
||||
function calculateWidth() {
|
||||
$timeout(function () {
|
||||
//total width minus room for avatar, search, and help icon
|
||||
var windowWidth = $(window).width() - 150;
|
||||
var containerWidth = $(".umb-app-header").outerWidth() - $(".umb-app-header__actions").outerWidth();
|
||||
var trayToggleWidth = $("#applications .sections li.expand").outerWidth();
|
||||
var sectionsWidth = 0;
|
||||
scope.totalSections = scope.sections.length;
|
||||
scope.maxSections = maxSections;
|
||||
scope.overflowingSections = scope.maxSections - scope.totalSections;
|
||||
scope.needTray = scope.sections.length > scope.maxSections;
|
||||
|
||||
|
||||
// detect how many sections we can show on the screen
|
||||
for (var i = 0; i < sectionItemsWidth.length; i++) {
|
||||
var sectionItemWidth = sectionItemsWidth[i];
|
||||
sectionsWidth += sectionItemWidth;
|
||||
|
||||
if (sectionsWidth > windowWidth) {
|
||||
scope.needTray = true;
|
||||
scope.maxSections = i - 1;
|
||||
scope.overflowingSections = scope.maxSections - scope.totalSections;
|
||||
break;
|
||||
if (sectionsWidth + trayToggleWidth > containerWidth) {
|
||||
scope.visibleSections = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
scope.visibleSections = scope.sections.length;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -134,14 +130,9 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
|
||||
};
|
||||
|
||||
scope.currentSectionInOverflow = function () {
|
||||
if (scope.overflowingSections === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var currentSection = scope.sections.filter(s => s.alias === scope.currentSection);
|
||||
|
||||
return (scope.sections.indexOf(currentSection[0]) >= scope.maxSections);
|
||||
|
||||
return currentSection.length > 0 && scope.sections.indexOf(currentSection[0]) > scope.visibleSections - 1;
|
||||
};
|
||||
|
||||
loadSections();
|
||||
|
||||
+50
-50
@@ -198,27 +198,27 @@ In the following example you see how to run some custom logic before a step goes
|
||||
scope.loadingStep = false;
|
||||
scope.elementNotFound = false;
|
||||
|
||||
scope.model.nextStep = function() {
|
||||
scope.model.nextStep = function () {
|
||||
nextStep();
|
||||
};
|
||||
|
||||
scope.model.endTour = function() {
|
||||
scope.model.endTour = function () {
|
||||
unbindEvent();
|
||||
tourService.endTour(scope.model);
|
||||
backdropService.close();
|
||||
};
|
||||
|
||||
scope.model.completeTour = function() {
|
||||
scope.model.completeTour = function () {
|
||||
unbindEvent();
|
||||
tourService.completeTour(scope.model).then(function() {
|
||||
backdropService.close();
|
||||
tourService.completeTour(scope.model).then(function () {
|
||||
backdropService.close();
|
||||
});
|
||||
};
|
||||
|
||||
scope.model.disableTour = function() {
|
||||
scope.model.disableTour = function () {
|
||||
unbindEvent();
|
||||
tourService.disableTour(scope.model).then(function() {
|
||||
backdropService.close();
|
||||
tourService.disableTour(scope.model).then(function () {
|
||||
backdropService.close();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ In the following example you see how to run some custom logic before a step goes
|
||||
pulseElement = el.find(".umb-tour__pulse");
|
||||
popover.hide();
|
||||
scope.model.currentStepIndex = 0;
|
||||
backdropService.open({disableEventsOnClick: true});
|
||||
backdropService.open({ disableEventsOnClick: true });
|
||||
startStep();
|
||||
}
|
||||
|
||||
@@ -249,20 +249,20 @@ In the following example you see how to run some custom logic before a step goes
|
||||
}
|
||||
|
||||
function nextStep() {
|
||||
|
||||
|
||||
popover.hide();
|
||||
pulseElement.hide();
|
||||
$timeout.cancel(pulseTimer);
|
||||
scope.model.currentStepIndex++;
|
||||
|
||||
// make sure we don't go too far
|
||||
if(scope.model.currentStepIndex !== scope.model.steps.length) {
|
||||
if (scope.model.currentStepIndex !== scope.model.steps.length) {
|
||||
startStep();
|
||||
// tour completed - final step
|
||||
// tour completed - final step
|
||||
} else {
|
||||
scope.loadingStep = true;
|
||||
|
||||
waitForPendingRerequests().then(function(){
|
||||
waitForPendingRerequests().then(function () {
|
||||
scope.loadingStep = false;
|
||||
// clear current step
|
||||
scope.model.currentStep = {};
|
||||
@@ -280,17 +280,17 @@ In the following example you see how to run some custom logic before a step goes
|
||||
backdropService.setOpacity(scope.model.steps[scope.model.currentStepIndex].backdropOpacity);
|
||||
backdropService.setHighlight(null);
|
||||
|
||||
waitForPendingRerequests().then(function() {
|
||||
waitForPendingRerequests().then(function () {
|
||||
|
||||
scope.model.currentStep = scope.model.steps[scope.model.currentStepIndex];
|
||||
|
||||
setView();
|
||||
|
||||
|
||||
// if highlight element is set - find it
|
||||
findHighlightElement();
|
||||
|
||||
// if a custom event needs to be bound we do it now
|
||||
if(scope.model.currentStep.event) {
|
||||
if (scope.model.currentStep.event) {
|
||||
bindEvent();
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ In the following example you see how to run some custom logic before a step goes
|
||||
|
||||
function findHighlightElement() {
|
||||
|
||||
scope.elementNotFound = false;
|
||||
scope.elementNotFound = false;
|
||||
|
||||
$timeout(function () {
|
||||
// clear element when step as marked as intro, so it always displays in the center
|
||||
@@ -312,15 +312,15 @@ In the following example you see how to run some custom logic before a step goes
|
||||
}
|
||||
|
||||
// if an element isn't set - show the popover in the center
|
||||
if(scope.model.currentStep && !scope.model.currentStep.element) {
|
||||
if (scope.model.currentStep && !scope.model.currentStep.element) {
|
||||
setPopoverPosition(null);
|
||||
return;
|
||||
}
|
||||
|
||||
var element = angular.element(scope.model.currentStep.element);
|
||||
var element = $(scope.model.currentStep.element);
|
||||
|
||||
// we couldn't find the element in the dom - abort and show error
|
||||
if(element.length === 0) {
|
||||
if (element.length === 0) {
|
||||
scope.elementNotFound = true;
|
||||
setPopoverPosition(null);
|
||||
return;
|
||||
@@ -337,7 +337,7 @@ In the following example you see how to run some custom logic before a step goes
|
||||
el = el.offsetParent();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var scrollToCenterOfContainer = offsetTop - (scrollParent[0].clientHeight / 2);
|
||||
if (element[0].clientHeight < scrollParent[0].clientHeight) {
|
||||
scrollToCenterOfContainer += (element[0].clientHeight / 2);
|
||||
@@ -366,7 +366,7 @@ In the following example you see how to run some custom logic before a step goes
|
||||
function setPopoverPosition(element) {
|
||||
|
||||
$timeout(function () {
|
||||
|
||||
|
||||
var position = "center";
|
||||
var margin = 20;
|
||||
var css = {};
|
||||
@@ -374,10 +374,10 @@ In the following example you see how to run some custom logic before a step goes
|
||||
var popoverWidth = popover.outerWidth();
|
||||
var popoverHeight = popover.outerHeight();
|
||||
var popoverOffset = popover.offset();
|
||||
var documentWidth = angular.element(document).width();
|
||||
var documentHeight = angular.element(document).height();
|
||||
var documentWidth = $(document).width();
|
||||
var documentHeight = $(document).height();
|
||||
|
||||
if(element) {
|
||||
if (element) {
|
||||
|
||||
var offset = element.offset();
|
||||
var width = element.outerWidth();
|
||||
@@ -436,29 +436,29 @@ In the following example you see how to run some custom logic before a step goes
|
||||
|
||||
} else {
|
||||
// if there is no dom element center the popover
|
||||
css.top = "calc(50% - " + popoverHeight/2 + "px)";
|
||||
css.left = "calc(50% - " + popoverWidth/2 + "px)";
|
||||
css.top = "calc(50% - " + popoverHeight / 2 + "px)";
|
||||
css.left = "calc(50% - " + popoverWidth / 2 + "px)";
|
||||
}
|
||||
|
||||
popover.css(css).fadeIn("fast");
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
function setPulsePosition() {
|
||||
if(scope.model.currentStep.event) {
|
||||
if (scope.model.currentStep.event) {
|
||||
|
||||
pulseTimer = $timeout(function () {
|
||||
|
||||
pulseTimer = $timeout(function(){
|
||||
|
||||
var clickElementSelector = scope.model.currentStep.eventElement ? scope.model.currentStep.eventElement : scope.model.currentStep.element;
|
||||
var clickElement = $(clickElementSelector);
|
||||
|
||||
|
||||
var offset = clickElement.offset();
|
||||
var width = clickElement.outerWidth();
|
||||
var height = clickElement.outerHeight();
|
||||
|
||||
|
||||
pulseElement.css({ "width": width, "height": height, "left": offset.left, "top": offset.top });
|
||||
pulseElement.fadeIn();
|
||||
|
||||
@@ -468,24 +468,24 @@ In the following example you see how to run some custom logic before a step goes
|
||||
|
||||
function waitForPendingRerequests() {
|
||||
var deferred = $q.defer();
|
||||
var timer = window.setInterval(function(){
|
||||
|
||||
var timer = window.setInterval(function () {
|
||||
|
||||
var requestsReady = false;
|
||||
var animationsDone = false;
|
||||
|
||||
// check for pending requests both in angular and on the document
|
||||
if($http.pendingRequests.length === 0 && document.readyState === "complete") {
|
||||
if ($http.pendingRequests.length === 0 && document.readyState === "complete") {
|
||||
requestsReady = true;
|
||||
}
|
||||
|
||||
// check for animations. ng-enter and ng-leave are default angular animations.
|
||||
// Also check for infinite editors animating
|
||||
if(document.querySelectorAll(".ng-enter, .ng-leave, .umb-editor--animating").length === 0) {
|
||||
if (document.querySelectorAll(".ng-enter, .ng-leave, .umb-editor--animating").length === 0) {
|
||||
animationsDone = true;
|
||||
}
|
||||
|
||||
if(requestsReady && animationsDone) {
|
||||
$timeout(function(){
|
||||
if (requestsReady && animationsDone) {
|
||||
$timeout(function () {
|
||||
deferred.resolve();
|
||||
clearInterval(timer);
|
||||
});
|
||||
@@ -512,14 +512,14 @@ In the following example you see how to run some custom logic before a step goes
|
||||
var bindToElement = scope.model.currentStep.element;
|
||||
var eventName = scope.model.currentStep.event + ".step-" + scope.model.currentStepIndex;
|
||||
var removeEventName = "remove.step-" + scope.model.currentStepIndex;
|
||||
var handled = false;
|
||||
var handled = false;
|
||||
|
||||
if(scope.model.currentStep.eventElement) {
|
||||
if (scope.model.currentStep.eventElement) {
|
||||
bindToElement = scope.model.currentStep.eventElement;
|
||||
}
|
||||
|
||||
$(bindToElement).on(eventName, function(){
|
||||
if(!handled) {
|
||||
$(bindToElement).on(eventName, function () {
|
||||
if (!handled) {
|
||||
unbindEvent();
|
||||
nextStep();
|
||||
handled = true;
|
||||
@@ -530,7 +530,7 @@ In the following example you see how to run some custom logic before a step goes
|
||||
// for some reason it seems the elements gets removed before the event is raised. This is a temp solution which assumes:
|
||||
// "if you ask me to click on an element, and it suddenly gets removed from the dom, let's go on to the next step".
|
||||
$(bindToElement).on(removeEventName, function () {
|
||||
if(!handled) {
|
||||
if (!handled) {
|
||||
unbindEvent();
|
||||
nextStep();
|
||||
handled = true;
|
||||
@@ -542,13 +542,13 @@ In the following example you see how to run some custom logic before a step goes
|
||||
function unbindEvent() {
|
||||
var eventName = scope.model.currentStep.event + ".step-" + scope.model.currentStepIndex;
|
||||
var removeEventName = "remove.step-" + scope.model.currentStepIndex;
|
||||
|
||||
if(scope.model.currentStep.eventElement) {
|
||||
angular.element(scope.model.currentStep.eventElement).off(eventName);
|
||||
angular.element(scope.model.currentStep.eventElement).off(removeEventName);
|
||||
|
||||
if (scope.model.currentStep.eventElement) {
|
||||
$(scope.model.currentStep.eventElement).off(eventName);
|
||||
$(scope.model.currentStep.eventElement).off(removeEventName);
|
||||
} else {
|
||||
angular.element(scope.model.currentStep.element).off(eventName);
|
||||
angular.element(scope.model.currentStep.element).off(removeEventName);
|
||||
$(scope.model.currentStep.element).off(eventName);
|
||||
$(scope.model.currentStep.element).off(removeEventName);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+47
-38
@@ -31,14 +31,14 @@
|
||||
$scope.page.hideActionsMenu = infiniteMode ? true : false;
|
||||
$scope.page.hideChangeVariant = false;
|
||||
$scope.allowOpen = true;
|
||||
$scope.app = null;
|
||||
$scope.activeApp = null;
|
||||
|
||||
//initializes any watches
|
||||
function startWatches(content) {
|
||||
|
||||
//watch for changes to isNew, set the page.isNew accordingly and load the breadcrumb if we can
|
||||
$scope.$watch('isNew', function (newVal, oldVal) {
|
||||
|
||||
|
||||
$scope.page.isNew = Object.toBoolean(newVal);
|
||||
|
||||
//We fetch all ancestors of the node to generate the footer breadcrumb navigation
|
||||
@@ -74,31 +74,23 @@
|
||||
var isAppPresent = false;
|
||||
|
||||
// on first init, we dont have any apps. but if we are re-initializing, we do, but ...
|
||||
if ($scope.app) {
|
||||
if ($scope.activeApp) {
|
||||
|
||||
// lets check if it still exists as part of our apps array. (if not we have made a change to our docType, even just a re-save of the docType it will turn into new Apps.)
|
||||
_.forEach(content.apps, function (app) {
|
||||
if (app === $scope.app) {
|
||||
if (app.alias === $scope.activeApp.alias) {
|
||||
isAppPresent = true;
|
||||
$scope.appChanged(app);
|
||||
}
|
||||
});
|
||||
|
||||
// if we did reload our DocType, but still have the same app we will try to find it by the alias.
|
||||
if (isAppPresent === false) {
|
||||
_.forEach(content.apps, function (app) {
|
||||
if (app.alias === $scope.app.alias) {
|
||||
isAppPresent = true;
|
||||
app.active = true;
|
||||
$scope.appChanged(app);
|
||||
}
|
||||
});
|
||||
// active app does not exist anymore.
|
||||
$scope.activeApp = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if we still dont have a app, lets show the first one:
|
||||
if (isAppPresent === false && content.apps.length) {
|
||||
content.apps[0].active = true;
|
||||
if ($scope.activeApp === null && content.apps.length) {
|
||||
$scope.appChanged(content.apps[0]);
|
||||
}
|
||||
// otherwise make sure the save options are up to date with the current content state
|
||||
@@ -151,8 +143,8 @@
|
||||
}
|
||||
|
||||
/** Returns true if the content item varies by culture */
|
||||
function isContentCultureVariant() {
|
||||
return $scope.content.variants.length > 1;
|
||||
function hasVariants(content) {
|
||||
return content.variants.length > 1;
|
||||
}
|
||||
|
||||
function reload() {
|
||||
@@ -182,32 +174,32 @@
|
||||
}
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("editors.content.reload", function (name, args) {
|
||||
evts.push(eventsService.on("editors.content.reload", function (name, args) {
|
||||
if (args && args.node && $scope.content.id === args.node.id) {
|
||||
reload();
|
||||
loadBreadcrumb();
|
||||
syncTreeNode($scope.content, $scope.content.path);
|
||||
}
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("rte.file.uploading", function(){
|
||||
|
||||
evts.push(eventsService.on("rte.file.uploading", function () {
|
||||
$scope.page.saveButtonState = "busy";
|
||||
$scope.page.buttonGroupState = "busy";
|
||||
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("rte.file.uploaded", function(){
|
||||
evts.push(eventsService.on("rte.file.uploaded", function () {
|
||||
$scope.page.saveButtonState = "success";
|
||||
$scope.page.buttonGroupState = "success";
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("rte.shortcut.save", function(){
|
||||
evts.push(eventsService.on("rte.shortcut.save", function () {
|
||||
if ($scope.page.showSaveButton) {
|
||||
$scope.save();
|
||||
}
|
||||
}));
|
||||
|
||||
evts.push(eventsService.on("content.saved", function(){
|
||||
evts.push(eventsService.on("content.saved", function () {
|
||||
// Clear out localstorage keys that start with tinymce__
|
||||
// When we save/perist a content node
|
||||
// NOTE: clearAll supports a RegEx pattern of items to remove
|
||||
@@ -215,6 +207,13 @@
|
||||
}));
|
||||
}
|
||||
|
||||
function appendRuntimeData() {
|
||||
$scope.content.variants.forEach((variant) => {
|
||||
variant.compositeId = contentEditingHelper.buildCompositeVariantId(variant);
|
||||
variant.htmlId = "_content_variant_" + variant.compositeId;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This does the content loading and initializes everything, called on first load
|
||||
*/
|
||||
@@ -226,6 +225,7 @@
|
||||
|
||||
$scope.content = data;
|
||||
|
||||
appendRuntimeData();
|
||||
init();
|
||||
|
||||
syncTreeNode($scope.content, $scope.content.path, true);
|
||||
@@ -251,6 +251,7 @@
|
||||
|
||||
$scope.content = data;
|
||||
|
||||
appendRuntimeData();
|
||||
init();
|
||||
startWatches($scope.content);
|
||||
|
||||
@@ -274,7 +275,7 @@
|
||||
$scope.page.saveButtonStyle = content.trashed || content.isElement || content.isBlueprint ? "primary" : "info";
|
||||
// only create the save/publish/preview buttons if the
|
||||
// content app is "Conent"
|
||||
if ($scope.app && $scope.app.alias !== "umbContent" && $scope.app.alias !== "umbInfo" && $scope.app.alias !== "umbListView") {
|
||||
if ($scope.activeApp && $scope.activeApp.alias !== "umbContent" && $scope.activeApp.alias !== "umbInfo" && $scope.activeApp.alias !== "umbListView") {
|
||||
$scope.defaultButton = null;
|
||||
$scope.subButtons = null;
|
||||
$scope.page.showSaveButton = false;
|
||||
@@ -323,7 +324,7 @@
|
||||
.then(function (syncArgs) {
|
||||
$scope.page.menu.currentNode = syncArgs.node;
|
||||
if (reloadChildren && syncArgs.node.expanded) {
|
||||
treeService.loadNodeChildren({node: syncArgs.node});
|
||||
treeService.loadNodeChildren({ node: syncArgs.node });
|
||||
}
|
||||
}, function () {
|
||||
//handle the rejection
|
||||
@@ -589,7 +590,7 @@
|
||||
|
||||
$scope.sendToPublish = function () {
|
||||
clearNotifications($scope.content);
|
||||
if (isContentCultureVariant()) {
|
||||
if (hasVariants($scope.content)) {
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "publish" })) {
|
||||
|
||||
@@ -649,7 +650,7 @@
|
||||
|
||||
$scope.saveAndPublish = function () {
|
||||
clearNotifications($scope.content);
|
||||
if (isContentCultureVariant()) {
|
||||
if (hasVariants($scope.content)) {
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "publish" })) {
|
||||
var dialog = {
|
||||
@@ -711,7 +712,7 @@
|
||||
$scope.save = function () {
|
||||
clearNotifications($scope.content);
|
||||
// TODO: Add "..." to save button label if there are more than one variant to publish - currently it just adds the elipses if there's more than 1 variant
|
||||
if (isContentCultureVariant()) {
|
||||
if (hasVariants($scope.content)) {
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "openSaveDialog" })) {
|
||||
|
||||
@@ -776,7 +777,7 @@
|
||||
clearNotifications($scope.content);
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "schedule" })) {
|
||||
if (!isContentCultureVariant()) {
|
||||
if (!hasVariants($scope.content)) {
|
||||
//ensure the flags are set
|
||||
$scope.content.variants[0].save = true;
|
||||
}
|
||||
@@ -784,7 +785,7 @@
|
||||
var dialog = {
|
||||
parentScope: $scope,
|
||||
view: "views/content/overlays/schedule.html",
|
||||
variants: angular.copy($scope.content.variants), //set a model property for the dialog
|
||||
variants: Utilities.copy($scope.content.variants), //set a model property for the dialog
|
||||
skipFormValidation: true, //when submitting the overlay form, skip any client side validation
|
||||
submitButtonLabelKey: "buttons_schedulePublish",
|
||||
submit: function (model) {
|
||||
@@ -813,12 +814,12 @@
|
||||
}, function (err) {
|
||||
clearDirtyState($scope.content.variants);
|
||||
//if this is invariant, show the notification errors, else they'll be shown inline with the variant
|
||||
if (!isContentCultureVariant()) {
|
||||
if (!hasVariants($scope.content)) {
|
||||
formHelper.showNotifications(err.data);
|
||||
}
|
||||
model.submitButtonState = "error";
|
||||
//re-map the dialog model since we've re-bound the properties
|
||||
dialog.variants = angular.copy($scope.content.variants);
|
||||
dialog.variants = Utilities.copy($scope.content.variants);
|
||||
//don't reject, we've handled the error
|
||||
return $q.when(err);
|
||||
});
|
||||
@@ -840,7 +841,7 @@
|
||||
//before we launch the dialog we want to execute all client side validations first
|
||||
if (formHelper.submitForm({ scope: $scope, action: "publishDescendants" })) {
|
||||
|
||||
if (!isContentCultureVariant()) {
|
||||
if (!hasVariants($scope.content)) {
|
||||
//ensure the flags are set
|
||||
$scope.content.variants[0].save = true;
|
||||
$scope.content.variants[0].publish = true;
|
||||
@@ -873,7 +874,7 @@
|
||||
}, function (err) {
|
||||
clearDirtyState($scope.content.variants);
|
||||
//if this is invariant, show the notification errors, else they'll be shown inline with the variant
|
||||
if (!isContentCultureVariant()) {
|
||||
if (!hasVariants($scope.content)) {
|
||||
formHelper.showNotifications(err.data);
|
||||
}
|
||||
model.submitButtonState = "error";
|
||||
@@ -963,11 +964,18 @@
|
||||
* Call back when a content app changes
|
||||
* @param {any} app
|
||||
*/
|
||||
$scope.appChanged = function (app) {
|
||||
$scope.appChanged = function (activeApp) {
|
||||
|
||||
$scope.app = app;
|
||||
$scope.activeApp = activeApp;
|
||||
|
||||
_.forEach($scope.content.apps, function (app) {
|
||||
app.active = false;
|
||||
if (app.alias === $scope.activeApp.alias) {
|
||||
app.active = true;
|
||||
}
|
||||
});
|
||||
|
||||
$scope.$broadcast("editors.apps.appChanged", { app: app });
|
||||
$scope.$broadcast("editors.apps.appChanged", { app: activeApp });
|
||||
|
||||
createButtons($scope.content);
|
||||
|
||||
@@ -1029,6 +1037,7 @@
|
||||
getMethod: "&",
|
||||
getScaffoldMethod: "&?",
|
||||
culture: "=?",
|
||||
segment: "=?",
|
||||
infiniteModel: "=?"
|
||||
}
|
||||
};
|
||||
|
||||
+1
-1
@@ -323,7 +323,7 @@
|
||||
// find the urls for the currently selected language
|
||||
if (scope.node.variants.length > 1) {
|
||||
// nodes with variants
|
||||
scope.currentUrls = _.filter(scope.node.urls, (url) => scope.currentVariant.language.culture === url.culture);
|
||||
scope.currentUrls = _.filter(scope.node.urls, (url) => (scope.currentVariant.language && scope.currentVariant.language.culture === url.culture));
|
||||
} else {
|
||||
// invariant nodes
|
||||
scope.currentUrls = scope.node.urls;
|
||||
|
||||
+30
-14
@@ -4,7 +4,7 @@
|
||||
/** This directive is used to render out the current variant tabs and properties and exposes an API for other directives to consume */
|
||||
function tabbedContentDirective($timeout) {
|
||||
|
||||
function link($scope, $element, $attrs) {
|
||||
function link($scope, $element) {
|
||||
|
||||
var appRootNode = $element[0];
|
||||
|
||||
@@ -115,21 +115,18 @@
|
||||
|
||||
}
|
||||
|
||||
function controller($scope, $element, $attrs) {
|
||||
|
||||
function controller($scope) {
|
||||
|
||||
//expose the property/methods for other directives to use
|
||||
this.content = $scope.content;
|
||||
this.activeVariant = _.find(this.content.variants, variant => {
|
||||
return variant.active;
|
||||
});
|
||||
|
||||
$scope.activeVariant = this.activeVariant;
|
||||
|
||||
$scope.defaultVariant = _.find(this.content.variants, variant => {
|
||||
return variant.language.isDefault;
|
||||
});
|
||||
|
||||
|
||||
if($scope.contentNodeModel) {
|
||||
$scope.defaultVariant = _.find($scope.contentNodeModel.variants, variant => {
|
||||
// defaultVariant will never have segment. Wether it has a language or not depends on the setup.
|
||||
return !variant.segment && ((variant.language && variant.language.isDefault) || (!variant.language));
|
||||
});
|
||||
}
|
||||
|
||||
$scope.unlockInvariantValue = function(property) {
|
||||
property.unlockInvariantValue = !property.unlockInvariantValue;
|
||||
};
|
||||
@@ -141,6 +138,24 @@
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$scope.propertyEditorDisabled = function (property) {
|
||||
if (property.unlockInvariantValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var contentLanguage = $scope.content.language;
|
||||
|
||||
var canEditCulture = !contentLanguage ||
|
||||
// If the property culture equals the content culture it can be edited
|
||||
property.culture === contentLanguage.culture ||
|
||||
// A culture-invariant property can only be edited by the default language variant
|
||||
(property.culture == null && contentLanguage.isDefault);
|
||||
|
||||
var canEditSegment = property.segment === $scope.content.segment;
|
||||
|
||||
return !canEditCulture || !canEditSegment;
|
||||
}
|
||||
}
|
||||
|
||||
var directive = {
|
||||
@@ -150,7 +165,8 @@
|
||||
controller: controller,
|
||||
link: link,
|
||||
scope: {
|
||||
content: "="
|
||||
content: "=", // in this context the content is the variant model.
|
||||
contentNodeModel: "=?" //contentNodeModel is the content model for the node,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+24
-15
@@ -12,7 +12,6 @@
|
||||
editor: "<",
|
||||
editorIndex: "<",
|
||||
editorCount: "<",
|
||||
openVariants: "<",
|
||||
onCloseSplitView: "&",
|
||||
onSelectVariant: "&",
|
||||
onOpenSplitView: "&",
|
||||
@@ -25,7 +24,7 @@
|
||||
controller: umbVariantContentController
|
||||
};
|
||||
|
||||
function umbVariantContentController($scope, $element, $location) {
|
||||
function umbVariantContentController($scope) {
|
||||
|
||||
var unsubscribe = [];
|
||||
|
||||
@@ -42,13 +41,14 @@
|
||||
vm.showBackButton = showBackButton;
|
||||
|
||||
function onInit() {
|
||||
// disable the name field if the active content app is not "Content"
|
||||
vm.nameDisabled = false;
|
||||
angular.forEach(vm.editor.content.apps, function(app){
|
||||
if(app.active && app.alias !== "umbContent" && app.alias !== "umbInfo" && app.alias !== "umbListView") {
|
||||
vm.nameDisabled = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Make copy of apps, so we can have a variant specific model for the App. (needed for validation etc.)
|
||||
vm.editor.variantApps = Utilities.copy(vm.content.apps);
|
||||
|
||||
var activeApp = vm.content.apps.find((app) => app.active);
|
||||
|
||||
onAppChanged(activeApp);
|
||||
|
||||
}
|
||||
|
||||
function showBackButton() {
|
||||
@@ -94,14 +94,23 @@
|
||||
}
|
||||
|
||||
$scope.$on("editors.apps.appChanged", function($event, $args) {
|
||||
var app = $args.app;
|
||||
// disable the name field if the active content app is not "Content" or "Info"
|
||||
vm.nameDisabled = false;
|
||||
if(app && app.alias !== "umbContent" && app.alias !== "umbInfo" && app.alias !== "umbListView") {
|
||||
vm.nameDisabled = true;
|
||||
}
|
||||
var activeApp = $args.app;
|
||||
|
||||
// sync varaintApps active with new active.
|
||||
_.forEach(vm.editor.variantApps, function (app) {
|
||||
app.active = (app.alias === activeApp.alias);
|
||||
});
|
||||
|
||||
onAppChanged(activeApp);
|
||||
});
|
||||
|
||||
function onAppChanged(activeApp) {
|
||||
|
||||
// disable the name field if the active content app is not "Content" or "Info"
|
||||
vm.nameDisabled = (activeApp && activeApp.alias !== "umbContent" && activeApp.alias !== "umbInfo" && activeApp.alias !== "umbListView");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to proxy a callback
|
||||
* @param {any} item
|
||||
|
||||
+72
-170
@@ -8,8 +8,9 @@
|
||||
templateUrl: 'views/components/content/umb-variant-content-editors.html',
|
||||
bindings: {
|
||||
page: "<",
|
||||
content: "<", // TODO: Not sure if this should be = since we are changing the 'active' property of a variant
|
||||
content: "<",
|
||||
culture: "<",
|
||||
segment: "<",
|
||||
onSelectApp: "&?",
|
||||
onSelectAppAnchor: "&?",
|
||||
onBack: "&?",
|
||||
@@ -19,12 +20,11 @@
|
||||
controller: umbVariantContentEditorsController
|
||||
};
|
||||
|
||||
function umbVariantContentEditorsController($scope, $location, $timeout) {
|
||||
function umbVariantContentEditorsController($scope, $location, contentEditingHelper) {
|
||||
|
||||
var prevContentDateUpdated = null;
|
||||
|
||||
var vm = this;
|
||||
var activeAppAlias = null;
|
||||
|
||||
vm.$onInit = onInit;
|
||||
vm.$onChanges = onChanges;
|
||||
@@ -39,13 +39,11 @@
|
||||
|
||||
//Used to track how many content views there are (for split view there will be 2, it could support more in theory)
|
||||
vm.editors = [];
|
||||
//Used to track the open variants across the split views
|
||||
vm.openVariants = [];
|
||||
|
||||
/** Called when the component initializes */
|
||||
function onInit() {
|
||||
prevContentDateUpdated = angular.copy(vm.content.updateDate);
|
||||
setActiveCulture();
|
||||
prevContentDateUpdated = Utilities.copy(vm.content.updateDate);
|
||||
setActiveVariant();
|
||||
}
|
||||
|
||||
/** Called when the component has linked all elements, this is when the form controller is available */
|
||||
@@ -60,15 +58,17 @@
|
||||
function onChanges(changes) {
|
||||
|
||||
if (changes.culture && !changes.culture.isFirstChange() && changes.culture.currentValue !== changes.culture.previousValue) {
|
||||
setActiveCulture();
|
||||
setActiveVariant();
|
||||
} else if (changes.segment && !changes.segment.isFirstChange() && changes.segment.currentValue !== changes.segment.previousValue) {
|
||||
setActiveVariant();
|
||||
}
|
||||
}
|
||||
|
||||
/** Allows us to deep watch whatever we want - executes on every digest cycle */
|
||||
function doCheck() {
|
||||
if (!angular.equals(vm.content.updateDate, prevContentDateUpdated)) {
|
||||
setActiveCulture();
|
||||
prevContentDateUpdated = angular.copy(vm.content.updateDate);
|
||||
setActiveVariant();
|
||||
prevContentDateUpdated = Utilities.copy(vm.content.updateDate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,37 +79,32 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the active variant based on the current culture (query string)
|
||||
* Set the active variant based on the current culture or segment (query string)
|
||||
*/
|
||||
function setActiveCulture() {
|
||||
function setActiveVariant() {
|
||||
// set the active variant
|
||||
var activeVariant = null;
|
||||
_.each(vm.content.variants, function (v) {
|
||||
if (v.language && v.language.culture === vm.culture) {
|
||||
v.active = true;
|
||||
if ((vm.culture === "invariant" || v.language && v.language.culture === vm.culture) && v.segment === vm.segment) {
|
||||
activeVariant = v;
|
||||
}
|
||||
else {
|
||||
v.active = false;
|
||||
}
|
||||
});
|
||||
if (!activeVariant) {
|
||||
// Set the first variant to active if we can't find it.
|
||||
// If the content item is invariant, then only one item exists in the array.
|
||||
vm.content.variants[0].active = true;
|
||||
activeVariant = vm.content.variants[0];
|
||||
}
|
||||
|
||||
insertVariantEditor(0, initVariant(activeVariant, 0));
|
||||
insertVariantEditor(0, activeVariant);
|
||||
|
||||
if (vm.editors.length > 1) {
|
||||
//now re-sync any other editor content (i.e. if split view is open)
|
||||
for (var s = 1; s < vm.editors.length; s++) {
|
||||
//get the variant from the scope model
|
||||
var variant = _.find(vm.content.variants, function (v) {
|
||||
return v.language.culture === vm.editors[s].content.language.culture;
|
||||
return (!v.language || v.language.culture === vm.editors[s].content.language.culture) && v.segment === vm.editors[s].content.segment;
|
||||
});
|
||||
vm.editors[s].content = initVariant(variant, s);
|
||||
vm.editors[s].content = variant;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,157 +117,84 @@
|
||||
*/
|
||||
function insertVariantEditor(index, variant) {
|
||||
|
||||
if (vm.editors[index]) {
|
||||
if (vm.editors[index].content === variant) {
|
||||
// This variant is already the content of the editor in this index.
|
||||
return;
|
||||
}
|
||||
vm.editors[index].content.active = false;
|
||||
}
|
||||
variant.active = true;
|
||||
|
||||
var variantCulture = variant.language ? variant.language.culture : "invariant";
|
||||
var variantSegment = variant.segment;
|
||||
|
||||
//check if the culture at the index is the same, if it's null an editor will be added
|
||||
var currentCulture = vm.editors.length === 0 || vm.editors.length <= index ? null : vm.editors[index].culture;
|
||||
var currentCulture = index < vm.editors.length ? vm.editors[index].culture : null;
|
||||
var currentSegment = index < vm.editors.length ? vm.editors[index].segment : null;
|
||||
|
||||
// if index not already exists or if the culture or segment isnt identical then we do a replacement.
|
||||
if (index >= vm.editors.length || currentCulture !== variantCulture || currentSegment !== variantSegment) {
|
||||
|
||||
if (currentCulture !== variantCulture) {
|
||||
//Not the current culture which means we need to modify the array.
|
||||
//Not the current culture or segment which means we need to modify the array.
|
||||
//NOTE: It is not good enough to just replace the `content` object at a given index in the array
|
||||
// since that would mean that directives are not re-initialized.
|
||||
vm.editors.splice(index, 1, {
|
||||
compositeId: variant.compositeId,
|
||||
content: variant,
|
||||
//used for "track-by" ng-repeat
|
||||
culture: variantCulture
|
||||
culture: variantCulture,
|
||||
segment: variantSegment
|
||||
});
|
||||
}
|
||||
else {
|
||||
//replace the editor for the same culture
|
||||
//replace the content of the editor, since the culture and segment is the same.
|
||||
vm.editors[index].content = variant;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function initVariant(variant, editorIndex) {
|
||||
//The model that is assigned to the editor contains the current content variant along
|
||||
//with a copy of the contentApps. This is required because each editor renders it's own
|
||||
//header and content apps section and the content apps contains the view for editing content itself
|
||||
//and we need to assign a view model to the subView so that it is scoped to the current
|
||||
//editor so that split views work.
|
||||
|
||||
//copy the apps from the main model if not assigned yet to the variant
|
||||
if (!variant.apps) {
|
||||
variant.apps = angular.copy(vm.content.apps);
|
||||
}
|
||||
|
||||
//if this is a variant has a culture/language than we need to assign the language drop down info
|
||||
if (variant.language) {
|
||||
//if the variant list that defines the header drop down isn't assigned to the variant then assign it now
|
||||
if (!variant.variants) {
|
||||
variant.variants = _.map(vm.content.variants,
|
||||
function (v) {
|
||||
return _.pick(v, "active", "language", "state");
|
||||
});
|
||||
}
|
||||
else {
|
||||
//merge the scope variants on top of the header variants collection (handy when needing to refresh)
|
||||
angular.extend(variant.variants,
|
||||
_.map(vm.content.variants,
|
||||
function (v) {
|
||||
return _.pick(v, "active", "language", "state");
|
||||
}));
|
||||
}
|
||||
|
||||
//ensure the current culture is set as the active one
|
||||
for (var i = 0; i < variant.variants.length; i++) {
|
||||
if (variant.variants[i].language.culture === variant.language.culture) {
|
||||
variant.variants[i].active = true;
|
||||
}
|
||||
else {
|
||||
variant.variants[i].active = false;
|
||||
}
|
||||
}
|
||||
|
||||
// keep track of the open variants across the different split views
|
||||
// push the first variant then update the variant index based on the editor index
|
||||
if(vm.openVariants && vm.openVariants.length === 0) {
|
||||
vm.openVariants.push(variant.language.culture);
|
||||
} else {
|
||||
vm.openVariants[editorIndex] = variant.language.culture;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//then assign the variant to a view model to the content app
|
||||
var contentApp = _.find(variant.apps, function (a) {
|
||||
return a.alias === "umbContent";
|
||||
});
|
||||
|
||||
if (contentApp) {
|
||||
//The view model for the content app is simply the index of the variant being edited
|
||||
var variantIndex = vm.content.variants.indexOf(variant);
|
||||
contentApp.viewModel = variantIndex;
|
||||
}
|
||||
|
||||
// make sure the same app it set to active in the new variant
|
||||
if(activeAppAlias) {
|
||||
angular.forEach(variant.apps, function(app) {
|
||||
app.active = false;
|
||||
if(app.alias === activeAppAlias) {
|
||||
app.active = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return variant;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a new editor to the editors array to show content in a split view
|
||||
* @param {any} selectedVariant
|
||||
*/
|
||||
function openSplitView(selectedVariant) {
|
||||
var selectedCulture = selectedVariant.language.culture;
|
||||
// enforce content contentApp in splitview.
|
||||
var contentApp = vm.content.apps.find((app) => app.alias === "umbContent");
|
||||
if(contentApp) {
|
||||
selectApp(contentApp);
|
||||
}
|
||||
|
||||
insertVariantEditor(vm.editors.length, selectedVariant);
|
||||
|
||||
splitViewChanged();
|
||||
|
||||
}
|
||||
|
||||
$scope.$on("editors.content.splitViewRequest", function(event, args) {requestSplitView(args);});
|
||||
vm.requestSplitView = requestSplitView;
|
||||
function requestSplitView(args) {
|
||||
var culture = args.culture;
|
||||
var segment = args.segment;
|
||||
|
||||
//Find the whole variant model based on the culture that was chosen
|
||||
var variant = _.find(vm.content.variants, function (v) {
|
||||
return v.language.culture === selectedCulture;
|
||||
return (!v.language || v.language.culture === culture) && v.segment === segment;
|
||||
});
|
||||
|
||||
insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length));
|
||||
|
||||
//only the content app can be selected since no other apps are shown, and because we copy all of these apps
|
||||
//to the "editors" we need to update this across all editors
|
||||
for (var e = 0; e < vm.editors.length; e++) {
|
||||
var editor = vm.editors[e];
|
||||
for (var i = 0; i < editor.content.apps.length; i++) {
|
||||
var app = editor.content.apps[i];
|
||||
if (app.alias === "umbContent") {
|
||||
app.active = true;
|
||||
// tell the world that the app has changed (but do it only once)
|
||||
if (e === 0) {
|
||||
selectApp(app);
|
||||
}
|
||||
}
|
||||
else {
|
||||
app.active = false;
|
||||
}
|
||||
}
|
||||
if (variant != null) {
|
||||
openSplitView(variant);
|
||||
}
|
||||
|
||||
// TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
|
||||
editor.collapsed = true;
|
||||
editor.loading = true;
|
||||
$timeout(function () {
|
||||
editor.collapsed = false;
|
||||
editor.loading = false;
|
||||
splitViewChanged();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/** Closes the split view */
|
||||
function closeSplitView(editorIndex) {
|
||||
// TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
|
||||
var editor = vm.editors[editorIndex];
|
||||
editor.loading = true;
|
||||
editor.collapsed = true;
|
||||
$timeout(function () {
|
||||
vm.editors.splice(editorIndex, 1);
|
||||
//remove variant from open variants
|
||||
vm.openVariants.splice(editorIndex, 1);
|
||||
//update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant)
|
||||
$location.search("cculture", vm.openVariants[0]);
|
||||
splitViewChanged();
|
||||
}, 400);
|
||||
vm.editors.splice(editorIndex, 1);
|
||||
editor.content.active = false;
|
||||
|
||||
//update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant)
|
||||
|
||||
$location.search({"cculture": vm.editors[0].content.language ? vm.editors[0].content.language.culture : null, "csegment": vm.editors[0].content.segment});
|
||||
splitViewChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -282,8 +204,11 @@
|
||||
*/
|
||||
function selectVariant(variant, editorIndex) {
|
||||
|
||||
// prevent variants already open in a split view to be opened
|
||||
if(vm.openVariants.indexOf(variant.language.culture) !== -1) {
|
||||
var variantCulture = variant.language ? variant.language.culture : "invariant";
|
||||
var variantSegment = variant.segment || null;
|
||||
|
||||
// Check if we already have this editor open, if so, do nothing.
|
||||
if (vm.editors.find((editor) => (!editor.content.language || editor.content.language.culture === variantCulture) && editor.content.segment === variantSegment)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -292,27 +217,12 @@
|
||||
if (editorIndex === 0) {
|
||||
//If we've made it this far, then update the query string.
|
||||
//The editor will respond to this query string changing.
|
||||
$location.search("cculture", variant.language.culture);
|
||||
$location.search("cculture", variantCulture).search("csegment", variantSegment);
|
||||
}
|
||||
else {
|
||||
|
||||
//Update the 'active' variant for this editor
|
||||
var editor = vm.editors[editorIndex];
|
||||
//set all variant drop down items as inactive for this editor and then set the selected one as active
|
||||
for (var i = 0; i < editor.content.variants.length; i++) {
|
||||
editor.content.variants[i].active = false;
|
||||
}
|
||||
variant.active = true;
|
||||
|
||||
//get the variant content model and initialize the editor with that
|
||||
var contentVariant = _.find(vm.content.variants,
|
||||
function (v) {
|
||||
return v.language.culture === variant.language.culture;
|
||||
});
|
||||
editor.content = initVariant(contentVariant, editorIndex);
|
||||
|
||||
//update the editors collection
|
||||
insertVariantEditor(editorIndex, contentVariant);
|
||||
insertVariantEditor(editorIndex, variant);
|
||||
|
||||
}
|
||||
}
|
||||
@@ -332,14 +242,6 @@
|
||||
vm.onSelectAppAnchor({"app": app, "anchor": anchor});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$scope.$on("editors.apps.appChanged", function($event, $args) {
|
||||
var app = $args.app;
|
||||
if(app && app.alias) {
|
||||
activeAppAlias = app.alias;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ Use this directive to generate a list of breadcrumbs.
|
||||
|
||||
var path = scope.pathTo(ancestor);
|
||||
$location.path(path);
|
||||
navigationService.clearSearch(["cculture"]);
|
||||
navigationService.clearSearch(["cculture", "csegment"]);
|
||||
}
|
||||
|
||||
scope.pathTo = function (ancestor) {
|
||||
|
||||
+166
-153
@@ -2,11 +2,10 @@
|
||||
'use strict';
|
||||
|
||||
function EditorContentHeader(serverValidationManager, localizationService, editorState) {
|
||||
function link(scope) {
|
||||
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
var unsubscribe = [];
|
||||
|
||||
|
||||
if (!scope.serverValidationNameField) {
|
||||
scope.serverValidationNameField = "Name";
|
||||
}
|
||||
@@ -14,19 +13,20 @@
|
||||
scope.serverValidationAliasField = "Alias";
|
||||
}
|
||||
|
||||
scope.isNew = scope.content.state == "NotCreated";
|
||||
|
||||
localizationService.localizeMany([
|
||||
scope.isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit",
|
||||
"visuallyHiddenTexts_name",
|
||||
scope.isNew ? "general_new" : "general_edit"]
|
||||
).then(function (data) {
|
||||
scope.isNew = scope.editor.content.state == "NotCreated";
|
||||
|
||||
localizationService.localizeMany(
|
||||
[
|
||||
scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit",
|
||||
"placeholders_a11yName",
|
||||
scope.isNew ? "general_new" : "general_edit"
|
||||
]
|
||||
).then(function (data) {
|
||||
scope.a11yMessage = data[0];
|
||||
scope.a11yName = data[1];
|
||||
var title = data[2] + ": ";
|
||||
if (!scope.isNew) {
|
||||
scope.a11yMessage += " " + scope.content.name;
|
||||
scope.a11yMessage += " " + scope.editor.content.name;
|
||||
title += scope.content.name;
|
||||
} else {
|
||||
var name = editorState.current.contentTypeName;
|
||||
@@ -34,199 +34,212 @@
|
||||
scope.a11yName = name + " " + scope.a11yName;
|
||||
title += name;
|
||||
}
|
||||
|
||||
|
||||
scope.$emit("$changeTitle", title);
|
||||
});
|
||||
scope.vm = {};
|
||||
scope.vm.hasVariants = false;
|
||||
scope.vm.hasSubVariants = false;
|
||||
scope.vm.hasCulture = false;
|
||||
scope.vm.hasSegments = false;
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.vm.currentVariant = "";
|
||||
scope.vm.variantsWithError = [];
|
||||
scope.vm.defaultVariant = null;
|
||||
|
||||
scope.vm.errorsOnOtherVariants = false;// indicating wether to show that other variants, than the current, have errors.
|
||||
|
||||
function updateVaraintErrors() {
|
||||
scope.content.variants.forEach( function (variant) {
|
||||
variant.hasError = scope.variantHasError(variant);
|
||||
|
||||
});
|
||||
checkErrorsOnOtherVariants();
|
||||
}
|
||||
|
||||
function checkErrorsOnOtherVariants() {
|
||||
var check = false;
|
||||
angular.forEach(scope.content.variants, function (variant) {
|
||||
if (scope.openVariants.indexOf(variant.language.culture) === -1 && scope.variantHasError(variant.language.culture)) {
|
||||
scope.content.variants.forEach( function (variant) {
|
||||
if (variant.active !== true && variant.hasError) {
|
||||
check = true;
|
||||
}
|
||||
});
|
||||
scope.vm.errorsOnOtherVariants = check;
|
||||
}
|
||||
|
||||
function onVariantValidation(valid, errors, allErrors, culture, segment) {
|
||||
|
||||
function onCultureValidation(valid, errors, allErrors, culture) {
|
||||
var index = scope.vm.variantsWithError.indexOf(culture);
|
||||
if (valid === true) {
|
||||
// only want to react to property errors:
|
||||
if(errors.findIndex(error => {return error.propertyAlias !== null;}) === -1) {
|
||||
// we dont have any errors for properties, meaning we will back out.
|
||||
return;
|
||||
}
|
||||
|
||||
// If error coming back is invariant, we will assign the error to the default variant by picking the defaultVariant language.
|
||||
if(culture === "invariant") {
|
||||
culture = scope.vm.defaultVariant.language.culture;
|
||||
}
|
||||
|
||||
var index = scope.vm.variantsWithError.findIndex((item) => item.culture === culture && item.segment === segment)
|
||||
if(valid === true) {
|
||||
if (index !== -1) {
|
||||
scope.vm.variantsWithError.splice(index, 1);
|
||||
}
|
||||
} else {
|
||||
if (index === -1) {
|
||||
scope.vm.variantsWithError.push(culture);
|
||||
scope.vm.variantsWithError.push({"culture": culture, "segment": segment});
|
||||
}
|
||||
}
|
||||
checkErrorsOnOtherVariants();
|
||||
scope.$evalAsync(updateVaraintErrors);
|
||||
}
|
||||
|
||||
|
||||
function onInit() {
|
||||
|
||||
// find default.
|
||||
angular.forEach(scope.content.variants, function (variant) {
|
||||
if (variant.language.isDefault) {
|
||||
|
||||
// find default + check if we have variants.
|
||||
scope.content.variants.forEach( function (variant) {
|
||||
if (variant.language !== null && variant.language.isDefault) {
|
||||
scope.vm.defaultVariant = variant;
|
||||
}
|
||||
});
|
||||
|
||||
setCurrentVariant();
|
||||
|
||||
angular.forEach(scope.content.apps, (app) => {
|
||||
if (app.alias === "umbContent") {
|
||||
app.anchors = scope.content.tabs;
|
||||
if (variant.language !== null) {
|
||||
scope.vm.hasCulture = true;
|
||||
}
|
||||
if (variant.segment !== null) {
|
||||
scope.vm.hasSegments = true;
|
||||
}
|
||||
});
|
||||
|
||||
scope.vm.hasVariants = (scope.vm.hasCulture || scope.vm.hasSegments);
|
||||
scope.vm.hasSubVariants = (scope.vm.hasCulture && scope.vm.hasSegments);
|
||||
|
||||
angular.forEach(scope.content.variants, function (variant) {
|
||||
unsubscribe.push(serverValidationManager.subscribe(null, variant.language.culture, null, onCultureValidation));
|
||||
});
|
||||
updateVaraintErrors();
|
||||
|
||||
unsubscribe.push(serverValidationManager.subscribe(null, null, null, onCultureValidation));
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
function setCurrentVariant() {
|
||||
angular.forEach(scope.content.variants, function (variant) {
|
||||
if (variant.active) {
|
||||
scope.vm.currentVariant = variant;
|
||||
checkErrorsOnOtherVariants();
|
||||
scope.vm.variantMenu = [];
|
||||
if (scope.vm.hasCulture) {
|
||||
scope.content.variants.forEach( (v) => {
|
||||
if (v.language !== null && v.segment === null) {
|
||||
var variantMenuEntry = {
|
||||
key: String.CreateGuid(),
|
||||
open: v.language && v.language.culture === scope.editor.culture,
|
||||
variant: v,
|
||||
subVariants: scope.content.variants.filter( (subVariant) => subVariant.language.culture === v.language.culture && subVariant.segment !== null)
|
||||
};
|
||||
scope.vm.variantMenu.push(variantMenuEntry);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
scope.content.variants.forEach( (v) => {
|
||||
scope.vm.variantMenu.push({
|
||||
key: String.CreateGuid(),
|
||||
variant: v
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scope.editor.variantApps.forEach( (app) => {
|
||||
if (app.alias === "umbContent") {
|
||||
app.anchors = scope.editor.content.tabs;
|
||||
}
|
||||
});
|
||||
|
||||
scope.goBack = function () {
|
||||
if (scope.onBack) {
|
||||
scope.onBack();
|
||||
scope.content.variants.forEach( function (variant) {
|
||||
|
||||
// if we are looking for the variant with default language then we also want to check for invariant variant.
|
||||
if (variant.language && variant.language.culture === scope.vm.defaultVariant.language.culture && variant.segment === null) {
|
||||
unsubscribe.push(serverValidationManager.subscribe(null, "invariant", null, onVariantValidation, null));
|
||||
}
|
||||
unsubscribe.push(serverValidationManager.subscribe(null, variant.language !== null ? variant.language.culture : null, null, onVariantValidation, variant.segment));
|
||||
});
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
scope.selectVariant = function (event, variant) {
|
||||
scope.goBack = function () {
|
||||
if (scope.onBack) {
|
||||
scope.onBack();
|
||||
}
|
||||
};
|
||||
|
||||
if (scope.onSelectVariant) {
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.onSelectVariant({ "variant": variant });
|
||||
scope.selectVariant = function (event, variant) {
|
||||
|
||||
if (scope.onSelectVariant) {
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.onSelectVariant({ "variant": variant });
|
||||
}
|
||||
};
|
||||
|
||||
scope.selectNavigationItem = function(item) {
|
||||
if(scope.onSelectNavigationItem) {
|
||||
scope.onSelectNavigationItem({"item": item});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scope.selectNavigationItem = function (item) {
|
||||
if (scope.onSelectNavigationItem) {
|
||||
scope.onSelectNavigationItem({ "item": item });
|
||||
scope.selectAnchorItem = function(item, anchor) {
|
||||
if(scope.onSelectAnchorItem) {
|
||||
scope.onSelectAnchorItem({"item": item, "anchor": anchor});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scope.selectAnchorItem = function (item, anchor) {
|
||||
if (scope.onSelectAnchorItem) {
|
||||
scope.onSelectAnchorItem({ "item": item, "anchor": anchor });
|
||||
}
|
||||
}
|
||||
scope.closeSplitView = function () {
|
||||
if (scope.onCloseSplitView) {
|
||||
scope.onCloseSplitView();
|
||||
}
|
||||
};
|
||||
|
||||
scope.closeSplitView = function () {
|
||||
if (scope.onCloseSplitView) {
|
||||
scope.onCloseSplitView();
|
||||
}
|
||||
};
|
||||
|
||||
scope.openInSplitView = function (event, variant) {
|
||||
if (scope.onOpenInSplitView) {
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.onOpenInSplitView({ "variant": variant });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* keep track of open variants - this is used to prevent the same variant to be open in more than one split view
|
||||
* @param {any} culture
|
||||
*/
|
||||
scope.variantIsOpen = function (culture) {
|
||||
return (scope.openVariants.indexOf(culture) !== -1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a variant has a error, used to display errors in variant switcher.
|
||||
* @param {any} culture
|
||||
*/
|
||||
scope.variantHasError = function (culture) {
|
||||
// if we are looking for the default language we also want to check for invariant.
|
||||
if (culture === scope.vm.defaultVariant.language.culture) {
|
||||
if (scope.vm.variantsWithError.indexOf("invariant") !== -1) {
|
||||
scope.openInSplitView = function (event, variant) {
|
||||
if (scope.onOpenInSplitView) {
|
||||
scope.vm.dropdownOpen = false;
|
||||
scope.onOpenInSplitView({ "variant": variant });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether a variant has a error, used to display errors in variant switcher.
|
||||
* @param {any} culture
|
||||
*/
|
||||
scope.variantHasError = function(variant) {
|
||||
if(scope.vm.variantsWithError.find((item) => (!variant.language || item.culture === variant.language.culture) && item.segment === variant.segment) !== undefined) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (scope.vm.variantsWithError.indexOf(culture) !== -1) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
onInit();
|
||||
|
||||
//watch for the active culture changing, if it changes, update the current variant
|
||||
if (scope.content.variants) {
|
||||
scope.$watch(function () {
|
||||
for (var i = 0; i < scope.content.variants.length; i++) {
|
||||
var v = scope.content.variants[i];
|
||||
if (v.active) {
|
||||
return v.language.culture;
|
||||
}
|
||||
}
|
||||
return scope.vm.currentVariant.language.culture; //should never get here
|
||||
}, function (newValue, oldValue) {
|
||||
if (newValue !== scope.vm.currentVariant.language.culture) {
|
||||
setCurrentVariant();
|
||||
onInit();
|
||||
|
||||
scope.$on('$destroy', function () {
|
||||
for (var u in unsubscribe) {
|
||||
unsubscribe[u]();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
scope.$on('$destroy', function () {
|
||||
for (var u in unsubscribe) {
|
||||
unsubscribe[u]();
|
||||
}
|
||||
});
|
||||
|
||||
var directive = {
|
||||
transclude: true,
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/editor/umb-editor-content-header.html',
|
||||
scope: {
|
||||
name: "=",
|
||||
nameDisabled: "<?",
|
||||
menu: "=",
|
||||
hideActionsMenu: "<?",
|
||||
content: "=",
|
||||
editor: "=",
|
||||
hideChangeVariant: "<?",
|
||||
onSelectNavigationItem: "&?",
|
||||
onSelectAnchorItem: "&?",
|
||||
showBackButton: "<?",
|
||||
onBack: "&?",
|
||||
splitViewOpen: "=?",
|
||||
onOpenInSplitView: "&?",
|
||||
onCloseSplitView: "&?",
|
||||
onSelectVariant: "&?",
|
||||
serverValidationNameField: "@?",
|
||||
serverValidationAliasField: "@?"
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
}
|
||||
|
||||
|
||||
var directive = {
|
||||
transclude: true,
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/editor/umb-editor-content-header.html',
|
||||
scope: {
|
||||
name: "=",
|
||||
nameDisabled: "<?",
|
||||
menu: "=",
|
||||
hideActionsMenu: "<?",
|
||||
content: "=",
|
||||
openVariants: "<",
|
||||
hideChangeVariant: "<?",
|
||||
onSelectNavigationItem: "&?",
|
||||
onSelectAnchorItem: "&?",
|
||||
showBackButton: "<?",
|
||||
onBack: "&?",
|
||||
splitViewOpen: "=?",
|
||||
onOpenInSplitView: "&?",
|
||||
onCloseSplitView: "&?",
|
||||
onSelectVariant: "&?",
|
||||
serverValidationNameField: "@?",
|
||||
serverValidationAliasField: "@?"
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbEditorContentHeader', EditorContentHeader);
|
||||
|
||||
}) ();
|
||||
})();
|
||||
|
||||
+2
-1
@@ -6,7 +6,7 @@
|
||||
**/
|
||||
function EditorSubViewDirective() {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
function link(scope) {
|
||||
//The model can contain: view, viewModel, name, alias, icon
|
||||
|
||||
if (!scope.model.view) {
|
||||
@@ -20,6 +20,7 @@
|
||||
templateUrl: 'views/components/editor/umb-editor-sub-view.html',
|
||||
scope: {
|
||||
model: "=",
|
||||
variantContent: "=?",
|
||||
content: "="
|
||||
},
|
||||
link: link
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ angular.module('umbraco.directives')
|
||||
.directive('checklistModel', ['$parse', '$compile', function($parse, $compile) {
|
||||
// contains
|
||||
function contains(arr, item) {
|
||||
if (angular.isArray(arr)) {
|
||||
if (Utilities.isArray(arr)) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (angular.equals(arr[i], item)) {
|
||||
return true;
|
||||
@@ -21,7 +21,7 @@ angular.module('umbraco.directives')
|
||||
|
||||
// add
|
||||
function add(arr, item) {
|
||||
arr = angular.isArray(arr) ? arr : [];
|
||||
arr = Utilities.isArray(arr) ? arr : [];
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (angular.equals(arr[i], item)) {
|
||||
return arr;
|
||||
@@ -33,7 +33,7 @@ angular.module('umbraco.directives')
|
||||
|
||||
// remove
|
||||
function remove(arr, item) {
|
||||
if (angular.isArray(arr)) {
|
||||
if (Utilities.isArray(arr)) {
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
if (angular.equals(arr[i], item)) {
|
||||
arr.splice(i, 1);
|
||||
|
||||
+2
-2
@@ -32,7 +32,7 @@ function fixNumber($parse) {
|
||||
|
||||
//always try to format the model value as an int
|
||||
ctrl.$formatters.push(function (value) {
|
||||
if (angular.isString(value)) {
|
||||
if (Utilities.isString(value)) {
|
||||
return parseFloat(value, 10);
|
||||
}
|
||||
return value;
|
||||
@@ -55,4 +55,4 @@ function fixNumber($parse) {
|
||||
}
|
||||
};
|
||||
}
|
||||
angular.module('umbraco.directives').directive("fixNumber", fixNumber);
|
||||
angular.module('umbraco.directives').directive("fixNumber", fixNumber);
|
||||
|
||||
+3
-4
@@ -7,13 +7,12 @@
|
||||
* a color will not be set.
|
||||
**/
|
||||
function hexBgColor() {
|
||||
return {
|
||||
return {
|
||||
restrict: "A",
|
||||
link: function (scope, element, attr, formCtrl) {
|
||||
|
||||
function setBackgroundColor(color) {
|
||||
// note: can't use element.css(), it doesn't support hexa background colors
|
||||
angular.element(element)[0].style.backgroundColor = "#" + color;
|
||||
element[0].style.backgroundColor = "#" + color;
|
||||
}
|
||||
|
||||
// Only add inline hex background color if defined and not "true".
|
||||
@@ -24,7 +23,7 @@ function hexBgColor() {
|
||||
// Set the orig based on the attribute if there is one.
|
||||
origColor = attr.hexBgOrig;
|
||||
}
|
||||
|
||||
|
||||
attr.$observe("hexBgColor", function (newVal) {
|
||||
if (newVal) {
|
||||
if (!origColor) {
|
||||
|
||||
+80
-153
@@ -1,161 +1,88 @@
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbAutoResize', function($timeout) {
|
||||
return {
|
||||
require: ["^?umbTabs", "ngModel"],
|
||||
link: function(scope, element, attr, controllersArr) {
|
||||
.directive('umbAutoResize', function ($timeout) {
|
||||
return {
|
||||
require: ["^?umbTabs", "ngModel"],
|
||||
link: function (scope, element, attr, controllersArr) {
|
||||
|
||||
var domEl = element[0];
|
||||
var domElType = domEl.type;
|
||||
var umbTabsController = controllersArr[0];
|
||||
var ngModelController = controllersArr[1];
|
||||
var domEl = element[0];
|
||||
var domElType = domEl.type;
|
||||
var umbTabsController = controllersArr[0];
|
||||
var ngModelController = controllersArr[1];
|
||||
|
||||
// IE elements
|
||||
var isIEFlag = false;
|
||||
var wrapper = angular.element('#umb-ie-resize-input-wrapper');
|
||||
var mirror = angular.element('<span style="white-space:pre;"></span>');
|
||||
function resizeInput() {
|
||||
|
||||
function isIE() {
|
||||
|
||||
var ua = window.navigator.userAgent;
|
||||
var msie = ua.indexOf("MSIE ");
|
||||
|
||||
if (msie > 0 || !!navigator.userAgent.match(/Trident.*rv\:11\./) || navigator.userAgent.match(/Edge\/\d+/)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function activate() {
|
||||
|
||||
// check if browser is Internet Explorere
|
||||
isIEFlag = isIE();
|
||||
|
||||
// scrollWidth on element does not work in IE on inputs
|
||||
// we have to do some dirty dom element copying.
|
||||
if (isIEFlag === true && domElType === "text") {
|
||||
setupInternetExplorerElements();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setupInternetExplorerElements() {
|
||||
|
||||
if (!wrapper.length) {
|
||||
wrapper = angular.element('<div id="umb-ie-resize-input-wrapper" style="position:fixed; top:-999px; left:0;"></div>');
|
||||
angular.element('body').append(wrapper);
|
||||
}
|
||||
|
||||
angular.forEach(['fontFamily', 'fontSize', 'fontWeight', 'fontStyle',
|
||||
'letterSpacing', 'textTransform', 'wordSpacing', 'textIndent',
|
||||
'boxSizing', 'borderRightWidth', 'borderLeftWidth', 'borderLeftStyle', 'borderRightStyle',
|
||||
'paddingLeft', 'paddingRight', 'marginLeft', 'marginRight'
|
||||
], function(value) {
|
||||
mirror.css(value, element.css(value));
|
||||
});
|
||||
|
||||
wrapper.append(mirror);
|
||||
|
||||
}
|
||||
|
||||
function resizeInternetExplorerInput() {
|
||||
|
||||
mirror.text(element.val() || attr.placeholder);
|
||||
element.css('width', mirror.outerWidth() + 1);
|
||||
|
||||
}
|
||||
|
||||
function resizeInput() {
|
||||
|
||||
if (domEl.scrollWidth !== domEl.clientWidth) {
|
||||
if (ngModelController.$modelValue) {
|
||||
element.width(domEl.scrollWidth);
|
||||
}
|
||||
}
|
||||
|
||||
if(!ngModelController.$modelValue && attr.placeholder) {
|
||||
attr.$set('size', attr.placeholder.length);
|
||||
element.width('auto');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function resizeTextarea() {
|
||||
|
||||
if(domEl.scrollHeight !== domEl.clientHeight) {
|
||||
|
||||
element.height(domEl.scrollHeight);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var update = function(force) {
|
||||
|
||||
|
||||
if (force === true) {
|
||||
|
||||
if (domElType === "textarea") {
|
||||
element.height(0);
|
||||
} else if (domElType === "text") {
|
||||
element.width(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (isIEFlag === true && domElType === "text") {
|
||||
|
||||
resizeInternetExplorerInput();
|
||||
|
||||
} else {
|
||||
|
||||
if (domElType === "textarea") {
|
||||
|
||||
resizeTextarea();
|
||||
|
||||
} else if (domElType === "text") {
|
||||
|
||||
resizeInput();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
activate();
|
||||
|
||||
//listen for tab changes
|
||||
if (umbTabsController != null) {
|
||||
umbTabsController.onTabShown(function(args) {
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
// listen for ng-model changes
|
||||
var unbindModelWatcher = scope.$watch(function() {
|
||||
return ngModelController.$modelValue;
|
||||
}, function(newValue) {
|
||||
$timeout(
|
||||
function() {
|
||||
update(true);
|
||||
if (domEl.scrollWidth !== domEl.clientWidth) {
|
||||
if (ngModelController.$modelValue) {
|
||||
element.width(domEl.scrollWidth);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function() {
|
||||
element.off('keyup keydown keypress change', update);
|
||||
element.off('blur', update(true));
|
||||
unbindModelWatcher();
|
||||
if (!ngModelController.$modelValue && attr.placeholder) {
|
||||
attr.$set('size', attr.placeholder.length);
|
||||
element.width('auto');
|
||||
}
|
||||
|
||||
// clean up IE dom element
|
||||
if (isIEFlag === true && domElType === "text") {
|
||||
mirror.remove();
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
function resizeTextarea() {
|
||||
|
||||
if (domEl.scrollHeight !== domEl.clientHeight) {
|
||||
|
||||
element.height(domEl.scrollHeight);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var update = function (force) {
|
||||
|
||||
|
||||
if (force === true) {
|
||||
|
||||
if (domElType === "textarea") {
|
||||
element.height(0);
|
||||
} else if (domElType === "text") {
|
||||
element.width(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (domElType === "textarea") {
|
||||
|
||||
resizeTextarea();
|
||||
|
||||
} else if (domElType === "text") {
|
||||
|
||||
resizeInput();
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
//listen for tab changes
|
||||
if (umbTabsController != null) {
|
||||
umbTabsController.onTabShown(function (args) {
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
// listen for ng-model changes
|
||||
var unbindModelWatcher = scope.$watch(function () {
|
||||
return ngModelController.$modelValue;
|
||||
}, function (newValue) {
|
||||
$timeout(
|
||||
function () {
|
||||
update(true);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
scope.$on('$destroy', function () {
|
||||
element.off('keyup keydown keypress change', update);
|
||||
element.off('blur', update(true));
|
||||
unbindModelWatcher();
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
+76
-76
@@ -9,91 +9,91 @@ will override element type to textarea and add own attribute ngModel tied to jso
|
||||
*/
|
||||
|
||||
angular.module("umbraco.directives")
|
||||
.directive('umbRawModel', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
template: '<textarea ng-model="jsonEditing"></textarea>',
|
||||
replace : true,
|
||||
scope: {
|
||||
model: '=umbRawModel',
|
||||
validateOn:'='
|
||||
},
|
||||
link: function (scope, element, attrs, ngModelCtrl) {
|
||||
.directive('umbRawModel', function () {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
template: '<textarea ng-model="jsonEditing"></textarea>',
|
||||
replace: true,
|
||||
scope: {
|
||||
model: '=umbRawModel',
|
||||
validateOn: '='
|
||||
},
|
||||
link: function (scope, element, attrs, ngModelCtrl) {
|
||||
|
||||
function setEditing (value) {
|
||||
scope.jsonEditing = angular.copy( jsonToString(value));
|
||||
}
|
||||
function setEditing(value) {
|
||||
scope.jsonEditing = Utilities.copy(jsonToString(value));
|
||||
}
|
||||
|
||||
function updateModel (value) {
|
||||
scope.model = stringToJson(value);
|
||||
}
|
||||
function updateModel(value) {
|
||||
scope.model = stringToJson(value);
|
||||
}
|
||||
|
||||
function setValid() {
|
||||
ngModelCtrl.$setValidity('json', true);
|
||||
}
|
||||
function setValid() {
|
||||
ngModelCtrl.$setValidity('json', true);
|
||||
}
|
||||
|
||||
function setInvalid () {
|
||||
ngModelCtrl.$setValidity('json', false);
|
||||
}
|
||||
function setInvalid() {
|
||||
ngModelCtrl.$setValidity('json', false);
|
||||
}
|
||||
|
||||
function stringToJson(text) {
|
||||
try {
|
||||
return angular.fromJson(text);
|
||||
} catch (err) {
|
||||
setInvalid();
|
||||
return text;
|
||||
}
|
||||
}
|
||||
function stringToJson(text) {
|
||||
try {
|
||||
return Utilities.fromJson(text);
|
||||
} catch (err) {
|
||||
setInvalid();
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
function jsonToString(object) {
|
||||
// better than JSON.stringify(), because it formats + filters $$hashKey etc.
|
||||
// NOTE that this will remove all $-prefixed values
|
||||
return angular.toJson(object, true);
|
||||
}
|
||||
function jsonToString(object) {
|
||||
// better than JSON.stringify(), because it formats + filters $$hashKey etc.
|
||||
// NOTE that this will remove all $-prefixed values
|
||||
return Utilities.toJson(object, true);
|
||||
}
|
||||
|
||||
function isValidJson(model) {
|
||||
var flag = true;
|
||||
try {
|
||||
angular.fromJson(model);
|
||||
} catch (err) {
|
||||
flag = false;
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
function isValidJson(model) {
|
||||
var flag = true;
|
||||
try {
|
||||
Utilities.fromJson(model)
|
||||
} catch (err) {
|
||||
flag = false;
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
//init
|
||||
setEditing(scope.model);
|
||||
//init
|
||||
setEditing(scope.model);
|
||||
|
||||
var onInputChange = function(newval,oldval){
|
||||
if (newval !== oldval) {
|
||||
if (isValidJson(newval)) {
|
||||
setValid();
|
||||
updateModel(newval);
|
||||
} else {
|
||||
setInvalid();
|
||||
}
|
||||
}
|
||||
};
|
||||
var onInputChange = function (newval, oldval) {
|
||||
if (newval !== oldval) {
|
||||
if (isValidJson(newval)) {
|
||||
setValid();
|
||||
updateModel(newval);
|
||||
} else {
|
||||
setInvalid();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if(scope.validateOn){
|
||||
element.on(scope.validateOn, function(){
|
||||
scope.$apply(function(){
|
||||
onInputChange(scope.jsonEditing);
|
||||
});
|
||||
});
|
||||
}else{
|
||||
//check for changes going out
|
||||
scope.$watch('jsonEditing', onInputChange, true);
|
||||
}
|
||||
if (scope.validateOn) {
|
||||
element.on(scope.validateOn, function () {
|
||||
scope.$apply(function () {
|
||||
onInputChange(scope.jsonEditing);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
//check for changes going out
|
||||
scope.$watch('jsonEditing', onInputChange, true);
|
||||
}
|
||||
|
||||
//check for changes coming in
|
||||
scope.$watch('model', function (newval, oldval) {
|
||||
if (newval !== oldval) {
|
||||
setEditing(newval);
|
||||
}
|
||||
}, true);
|
||||
//check for changes coming in
|
||||
scope.$watch('model', function (newval, oldval) {
|
||||
if (newval !== oldval) {
|
||||
setEditing(newval);
|
||||
}
|
||||
}, true);
|
||||
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ angular.module("umbraco.directives")
|
||||
}
|
||||
|
||||
var editorConfig = scope.configuration ? scope.configuration : null;
|
||||
if (!editorConfig || angular.isString(editorConfig)) {
|
||||
if (!editorConfig || Utilities.isString(editorConfig)) {
|
||||
editorConfig = tinyMceService.defaultPrevalues();
|
||||
//for the grid by default, we don't want to include the macro toolbar
|
||||
editorConfig.toolbar = _.without(editorConfig, "umbmacro");
|
||||
|
||||
+10
-10
@@ -30,7 +30,7 @@ angular.module("umbraco.directives")
|
||||
.directive('localize', function ($log, localizationService) {
|
||||
return {
|
||||
restrict: 'E',
|
||||
scope:{
|
||||
scope: {
|
||||
key: '@',
|
||||
tokens: '=',
|
||||
watchTokens: '@'
|
||||
@@ -40,13 +40,13 @@ angular.module("umbraco.directives")
|
||||
link: function (scope, element, attrs) {
|
||||
var key = scope.key;
|
||||
scope.text = "";
|
||||
|
||||
|
||||
// A render function to be able to update tokens as values update.
|
||||
function render() {
|
||||
element.html(localizationService.tokenReplace(scope.text, scope.tokens || null));
|
||||
}
|
||||
|
||||
localizationService.localize(key).then(function(value){
|
||||
|
||||
localizationService.localize(key).then(function (value) {
|
||||
scope.text = value;
|
||||
render();
|
||||
});
|
||||
@@ -64,19 +64,19 @@ angular.module("umbraco.directives")
|
||||
//Support one or more attribute properties to update
|
||||
var keys = attrs.localize.split(',');
|
||||
|
||||
angular.forEach(keys, function(value, key){
|
||||
Utilities.forEach(keys, (value, key) => {
|
||||
var attr = element.attr(value);
|
||||
|
||||
if(attr){
|
||||
if(attr[0] === '@'){
|
||||
if (attr) {
|
||||
if (attr[0] === '@') {
|
||||
//If the translation key starts with @ then remove it
|
||||
attr = attr.substring(1);
|
||||
}
|
||||
|
||||
var t = localizationService.tokenize(attr, scope);
|
||||
|
||||
localizationService.localize(t.key, t.tokens).then(function(val){
|
||||
element.attr(value, val);
|
||||
|
||||
localizationService.localize(t.key, t.tokens).then(function (val) {
|
||||
element.attr(value, val);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
+316
-317
@@ -193,352 +193,351 @@ Opens an overlay to show a custom YSOD. </br>
|
||||
@param {string} position The overlay position ("left", "right", "center": "target").
|
||||
**/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function OverlayDirective($timeout, formHelper, overlayHelper, localizationService, $q, $templateCache, $http, $compile) {
|
||||
|
||||
function link(scope, el, attr, ctrl) {
|
||||
function link(scope, el, attr, ctrl) {
|
||||
|
||||
scope.directive = {
|
||||
enableConfirmButton: false
|
||||
};
|
||||
|
||||
var overlayNumber = 0;
|
||||
var numberOfOverlays = 0;
|
||||
var isRegistered = false;
|
||||
|
||||
|
||||
var modelCopy = {};
|
||||
var unsubscribe = [];
|
||||
|
||||
function activate() {
|
||||
setView();
|
||||
|
||||
setButtonText();
|
||||
|
||||
modelCopy = makeModelCopy(scope.model);
|
||||
|
||||
$timeout(function() {
|
||||
|
||||
if (scope.position === "target" && scope.model.event) {
|
||||
setTargetPosition();
|
||||
|
||||
// update the position of the overlay on content changes
|
||||
// as these affect the layout/size of the overlay
|
||||
if ('ResizeObserver' in window)
|
||||
{
|
||||
var resizeObserver = new ResizeObserver(setTargetPosition);
|
||||
var contentArea = document.getElementById("contentwrapper");
|
||||
resizeObserver.observe(el[0]);
|
||||
if (contentArea) {
|
||||
resizeObserver.observe(contentArea);
|
||||
}
|
||||
unsubscribe.push(function () {
|
||||
resizeObserver.disconnect();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// this has to be done inside a timeout to ensure the destroy
|
||||
// event on other overlays is run before registering a new one
|
||||
registerOverlay();
|
||||
|
||||
setOverlayIndent();
|
||||
|
||||
focusOnOverlayHeading()
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Ideally this would focus on the first natively focusable element in the overlay, but as the content can be dynamic, it is focusing on the heading.
|
||||
function focusOnOverlayHeading() {
|
||||
var heading = el.find(".umb-overlay__title");
|
||||
|
||||
if(heading) {
|
||||
heading.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function setView() {
|
||||
|
||||
if (scope.view) {
|
||||
|
||||
if (scope.view.indexOf(".html") === -1) {
|
||||
var viewAlias = scope.view.toLowerCase();
|
||||
scope.view = "views/common/overlays/" + viewAlias + "/" + viewAlias + ".html";
|
||||
}
|
||||
|
||||
//if a custom parent scope is defined then we need to manually compile the view
|
||||
if (scope.parentScope) {
|
||||
var element = el.find(".scoped-view");
|
||||
$http.get(scope.view, { cache: $templateCache })
|
||||
.then(function (response) {
|
||||
var templateScope = scope.parentScope.$new();
|
||||
unsubscribe.push(function() {
|
||||
templateScope.$destroy();
|
||||
});
|
||||
templateScope.model = scope.model;
|
||||
element.html(response.data);
|
||||
element.show();
|
||||
$compile(element.contents())(templateScope);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setButtonText() {
|
||||
|
||||
var labelKeys = [
|
||||
"general_close",
|
||||
"general_submit"
|
||||
];
|
||||
|
||||
localizationService.localizeMany(labelKeys).then(function (values) {
|
||||
if (!scope.model.closeButtonLabelKey && !scope.model.closeButtonLabel) {
|
||||
scope.model.closeButtonLabel = values[0];
|
||||
}
|
||||
if (!scope.model.submitButtonLabelKey && !scope.model.submitButtonLabel) {
|
||||
scope.model.submitButtonLabel = values[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function registerOverlay() {
|
||||
|
||||
overlayNumber = overlayHelper.registerOverlay();
|
||||
|
||||
$(document).on("keydown.overlay-" + overlayNumber, function(event) {
|
||||
|
||||
if (event.which === 27) {
|
||||
|
||||
numberOfOverlays = overlayHelper.getNumberOfOverlays();
|
||||
|
||||
if (numberOfOverlays === overlayNumber && !scope.model.disableEscKey) {
|
||||
scope.$apply(function () {
|
||||
scope.closeOverLay();
|
||||
});
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.which === 13) {
|
||||
|
||||
numberOfOverlays = overlayHelper.getNumberOfOverlays();
|
||||
|
||||
if(numberOfOverlays === overlayNumber) {
|
||||
|
||||
var activeElementType = document.activeElement.tagName;
|
||||
var clickableElements = ["A", "BUTTON"];
|
||||
var submitOnEnter = document.activeElement.hasAttribute("overlay-submit-on-enter");
|
||||
var submitOnEnterValue = submitOnEnter ? document.activeElement.getAttribute("overlay-submit-on-enter") : "";
|
||||
|
||||
if(clickableElements.indexOf(activeElementType) >= 0) {
|
||||
// don't do anything, let the browser Enter key handle this
|
||||
} else if(activeElementType === "TEXTAREA" && !submitOnEnter) {
|
||||
|
||||
|
||||
} else if (submitOnEnter && submitOnEnterValue === "false") {
|
||||
// don't do anything
|
||||
}else {
|
||||
scope.$apply(function () {
|
||||
scope.submitForm(scope.model);
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
isRegistered = true;
|
||||
|
||||
}
|
||||
|
||||
function unregisterOverlay() {
|
||||
|
||||
if(isRegistered) {
|
||||
|
||||
overlayHelper.unregisterOverlay();
|
||||
|
||||
$(document).off("keydown.overlay-" + overlayNumber);
|
||||
|
||||
isRegistered = false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function makeModelCopy(object) {
|
||||
|
||||
var newObject = {};
|
||||
|
||||
for (var key in object) {
|
||||
if (key !== "event" && key !== "parentScope") {
|
||||
newObject[key] = angular.copy(object[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return newObject;
|
||||
|
||||
}
|
||||
|
||||
function setOverlayIndent() {
|
||||
|
||||
var overlayIndex = overlayNumber - 1;
|
||||
var indentSize = overlayIndex * 20;
|
||||
var overlayWidth = el[0].clientWidth;
|
||||
|
||||
el.css('width', overlayWidth - indentSize);
|
||||
|
||||
if(scope.position === "center" && overlayIndex > 0 || scope.position === "target" && overlayIndex > 0) {
|
||||
var overlayTopPosition = el[0].offsetTop;
|
||||
el.css('top', overlayTopPosition + indentSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setTargetPosition() {
|
||||
|
||||
var container = $("#contentwrapper");
|
||||
var containerLeft = container[0].offsetLeft;
|
||||
var containerRight = containerLeft + container[0].offsetWidth;
|
||||
var containerTop = container[0].offsetTop;
|
||||
var containerBottom = containerTop + container[0].offsetHeight;
|
||||
|
||||
var mousePositionClickX = null;
|
||||
var mousePositionClickY = null;
|
||||
var elementHeight = null;
|
||||
var elementWidth = null;
|
||||
|
||||
var position = {
|
||||
right: "inherit",
|
||||
left: "inherit",
|
||||
top: "inherit",
|
||||
bottom: "inherit"
|
||||
scope.directive = {
|
||||
enableConfirmButton: false
|
||||
};
|
||||
|
||||
// click position
|
||||
mousePositionClickX = scope.model.event.pageX;
|
||||
mousePositionClickY = scope.model.event.pageY;
|
||||
var overlayNumber = 0;
|
||||
var numberOfOverlays = 0;
|
||||
var isRegistered = false;
|
||||
|
||||
// element size
|
||||
elementHeight = el[0].clientHeight;
|
||||
elementWidth = el[0].clientWidth;
|
||||
|
||||
// move element to this position
|
||||
position.left = mousePositionClickX - (elementWidth / 2);
|
||||
position.top = mousePositionClickY - (elementHeight / 2);
|
||||
var modelCopy = {};
|
||||
var unsubscribe = [];
|
||||
|
||||
function activate() {
|
||||
setView();
|
||||
|
||||
setButtonText();
|
||||
|
||||
modelCopy = makeModelCopy(scope.model);
|
||||
|
||||
$timeout(function () {
|
||||
|
||||
if (scope.position === "target" && scope.model.event) {
|
||||
setTargetPosition();
|
||||
|
||||
// update the position of the overlay on content changes
|
||||
// as these affect the layout/size of the overlay
|
||||
if ('ResizeObserver' in window) {
|
||||
var resizeObserver = new ResizeObserver(setTargetPosition);
|
||||
var contentArea = document.getElementById("contentwrapper");
|
||||
resizeObserver.observe(el[0]);
|
||||
if (contentArea) {
|
||||
resizeObserver.observe(contentArea);
|
||||
}
|
||||
unsubscribe.push(function () {
|
||||
resizeObserver.disconnect();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// this has to be done inside a timeout to ensure the destroy
|
||||
// event on other overlays is run before registering a new one
|
||||
registerOverlay();
|
||||
|
||||
setOverlayIndent();
|
||||
|
||||
focusOnOverlayHeading()
|
||||
});
|
||||
|
||||
// check to see if element is outside screen
|
||||
// outside right
|
||||
if (position.left + elementWidth > containerRight) {
|
||||
position.right = 10;
|
||||
position.left = "inherit";
|
||||
}
|
||||
|
||||
// outside bottom
|
||||
if (position.top + elementHeight > containerBottom) {
|
||||
position.bottom = 10;
|
||||
position.top = "inherit";
|
||||
// Ideally this would focus on the first natively focusable element in the overlay, but as the content can be dynamic, it is focusing on the heading.
|
||||
function focusOnOverlayHeading() {
|
||||
var heading = el.find(".umb-overlay__title");
|
||||
|
||||
if (heading) {
|
||||
heading.focus();
|
||||
}
|
||||
}
|
||||
|
||||
// outside left
|
||||
if (position.left < containerLeft) {
|
||||
position.left = containerLeft + 10;
|
||||
position.right = "inherit";
|
||||
function setView() {
|
||||
|
||||
if (scope.view) {
|
||||
|
||||
if (scope.view.indexOf(".html") === -1) {
|
||||
var viewAlias = scope.view.toLowerCase();
|
||||
scope.view = "views/common/overlays/" + viewAlias + "/" + viewAlias + ".html";
|
||||
}
|
||||
|
||||
//if a custom parent scope is defined then we need to manually compile the view
|
||||
if (scope.parentScope) {
|
||||
var element = el.find(".scoped-view");
|
||||
$http.get(scope.view, { cache: $templateCache })
|
||||
.then(function (response) {
|
||||
var templateScope = scope.parentScope.$new();
|
||||
unsubscribe.push(function () {
|
||||
templateScope.$destroy();
|
||||
});
|
||||
templateScope.model = scope.model;
|
||||
element.html(response.data);
|
||||
element.show();
|
||||
$compile(element.contents())(templateScope);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// outside top
|
||||
if (position.top < containerTop) {
|
||||
position.top = 10;
|
||||
position.bottom = "inherit";
|
||||
function setButtonText() {
|
||||
|
||||
var labelKeys = [
|
||||
"general_close",
|
||||
"general_submit"
|
||||
];
|
||||
|
||||
localizationService.localizeMany(labelKeys).then(function (values) {
|
||||
if (!scope.model.closeButtonLabelKey && !scope.model.closeButtonLabel) {
|
||||
scope.model.closeButtonLabel = values[0];
|
||||
}
|
||||
if (!scope.model.submitButtonLabelKey && !scope.model.submitButtonLabel) {
|
||||
scope.model.submitButtonLabel = values[1];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
el.css(position);
|
||||
}
|
||||
function registerOverlay() {
|
||||
|
||||
scope.submitForm = function(model) {
|
||||
if(scope.model.submit) {
|
||||
if (formHelper.submitForm({ scope: scope, skipValidation: scope.model.skipFormValidation})) {
|
||||
|
||||
if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) {
|
||||
//wrap in a when since we don't know if this is a promise or not
|
||||
$q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then(
|
||||
function() {
|
||||
formHelper.resetForm({ scope: scope });
|
||||
});
|
||||
} else {
|
||||
unregisterOverlay();
|
||||
//wrap in a when since we don't know if this is a promise or not
|
||||
$q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then(
|
||||
function() {
|
||||
formHelper.resetForm({ scope: scope });
|
||||
});
|
||||
}
|
||||
overlayNumber = overlayHelper.registerOverlay();
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
$(document).on("keydown.overlay-" + overlayNumber, function (event) {
|
||||
|
||||
scope.cancelConfirmSubmit = function() {
|
||||
scope.model.confirmSubmit.show = false;
|
||||
};
|
||||
if (event.which === 27) {
|
||||
|
||||
scope.closeOverLay = function() {
|
||||
numberOfOverlays = overlayHelper.getNumberOfOverlays();
|
||||
|
||||
unregisterOverlay();
|
||||
if (numberOfOverlays === overlayNumber && !scope.model.disableEscKey) {
|
||||
scope.$apply(function () {
|
||||
scope.closeOverLay();
|
||||
});
|
||||
}
|
||||
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
if (event.which === 13) {
|
||||
|
||||
numberOfOverlays = overlayHelper.getNumberOfOverlays();
|
||||
|
||||
if (numberOfOverlays === overlayNumber) {
|
||||
|
||||
var activeElementType = document.activeElement.tagName;
|
||||
var clickableElements = ["A", "BUTTON"];
|
||||
var submitOnEnter = document.activeElement.hasAttribute("overlay-submit-on-enter");
|
||||
var submitOnEnterValue = submitOnEnter ? document.activeElement.getAttribute("overlay-submit-on-enter") : "";
|
||||
|
||||
if (clickableElements.indexOf(activeElementType) >= 0) {
|
||||
// don't do anything, let the browser Enter key handle this
|
||||
} else if (activeElementType === "TEXTAREA" && !submitOnEnter) {
|
||||
|
||||
|
||||
} else if (submitOnEnter && submitOnEnterValue === "false") {
|
||||
// don't do anything
|
||||
} else {
|
||||
scope.$apply(function () {
|
||||
scope.submitForm(scope.model);
|
||||
});
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
isRegistered = true;
|
||||
|
||||
if (scope.model && scope.model.close) {
|
||||
scope.model = modelCopy;
|
||||
scope.model.close(scope.model);
|
||||
} else {
|
||||
scope.model.show = false;
|
||||
scope.model = null;
|
||||
}
|
||||
|
||||
};
|
||||
function unregisterOverlay() {
|
||||
|
||||
if (isRegistered) {
|
||||
|
||||
overlayHelper.unregisterOverlay();
|
||||
|
||||
$(document).off("keydown.overlay-" + overlayNumber);
|
||||
|
||||
isRegistered = false;
|
||||
}
|
||||
|
||||
scope.outSideClick = function() {
|
||||
if(!scope.model.disableBackdropClick) {
|
||||
scope.closeOverLay();
|
||||
}
|
||||
|
||||
function makeModelCopy(object) {
|
||||
|
||||
var newObject = {};
|
||||
|
||||
for (var key in object) {
|
||||
if (key !== "event" && key !== "parentScope") {
|
||||
newObject[key] = Utilities.copy(object[key]);
|
||||
}
|
||||
}
|
||||
|
||||
return newObject;
|
||||
|
||||
}
|
||||
|
||||
function setOverlayIndent() {
|
||||
|
||||
var overlayIndex = overlayNumber - 1;
|
||||
var indentSize = overlayIndex * 20;
|
||||
var overlayWidth = el[0].clientWidth;
|
||||
|
||||
el.css('width', overlayWidth - indentSize);
|
||||
|
||||
if (scope.position === "center" && overlayIndex > 0 || scope.position === "target" && overlayIndex > 0) {
|
||||
var overlayTopPosition = el[0].offsetTop;
|
||||
el.css('top', overlayTopPosition + indentSize);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function setTargetPosition() {
|
||||
|
||||
var container = $("#contentwrapper");
|
||||
var containerLeft = container[0].offsetLeft;
|
||||
var containerRight = containerLeft + container[0].offsetWidth;
|
||||
var containerTop = container[0].offsetTop;
|
||||
var containerBottom = containerTop + container[0].offsetHeight;
|
||||
|
||||
var mousePositionClickX = null;
|
||||
var mousePositionClickY = null;
|
||||
var elementHeight = null;
|
||||
var elementWidth = null;
|
||||
|
||||
var position = {
|
||||
right: "inherit",
|
||||
left: "inherit",
|
||||
top: "inherit",
|
||||
bottom: "inherit"
|
||||
};
|
||||
|
||||
// click position
|
||||
mousePositionClickX = scope.model.event.pageX;
|
||||
mousePositionClickY = scope.model.event.pageY;
|
||||
|
||||
// element size
|
||||
elementHeight = el[0].clientHeight;
|
||||
elementWidth = el[0].clientWidth;
|
||||
|
||||
// move element to this position
|
||||
position.left = mousePositionClickX - (elementWidth / 2);
|
||||
position.top = mousePositionClickY - (elementHeight / 2);
|
||||
|
||||
// check to see if element is outside screen
|
||||
// outside right
|
||||
if (position.left + elementWidth > containerRight) {
|
||||
position.right = 10;
|
||||
position.left = "inherit";
|
||||
}
|
||||
|
||||
// outside bottom
|
||||
if (position.top + elementHeight > containerBottom) {
|
||||
position.bottom = 10;
|
||||
position.top = "inherit";
|
||||
}
|
||||
|
||||
// outside left
|
||||
if (position.left < containerLeft) {
|
||||
position.left = containerLeft + 10;
|
||||
position.right = "inherit";
|
||||
}
|
||||
|
||||
// outside top
|
||||
if (position.top < containerTop) {
|
||||
position.top = 10;
|
||||
position.bottom = "inherit";
|
||||
}
|
||||
|
||||
el.css(position);
|
||||
}
|
||||
|
||||
scope.submitForm = function (model) {
|
||||
if (scope.model.submit) {
|
||||
if (formHelper.submitForm({ scope: scope, skipValidation: scope.model.skipFormValidation })) {
|
||||
|
||||
if (scope.model.confirmSubmit && scope.model.confirmSubmit.enable && !scope.directive.enableConfirmButton) {
|
||||
//wrap in a when since we don't know if this is a promise or not
|
||||
$q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then(
|
||||
function () {
|
||||
formHelper.resetForm({ scope: scope });
|
||||
});
|
||||
} else {
|
||||
unregisterOverlay();
|
||||
//wrap in a when since we don't know if this is a promise or not
|
||||
$q.when(scope.model.submit(model, modelCopy, scope.directive.enableConfirmButton)).then(
|
||||
function () {
|
||||
formHelper.resetForm({ scope: scope });
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
scope.cancelConfirmSubmit = function () {
|
||||
scope.model.confirmSubmit.show = false;
|
||||
};
|
||||
|
||||
scope.closeOverLay = function () {
|
||||
|
||||
unregisterOverlay();
|
||||
|
||||
if (scope.model && scope.model.close) {
|
||||
scope.model = modelCopy;
|
||||
scope.model.close(scope.model);
|
||||
} else {
|
||||
scope.model.show = false;
|
||||
scope.model = null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
scope.outSideClick = function () {
|
||||
if (!scope.model.disableBackdropClick) {
|
||||
scope.closeOverLay();
|
||||
}
|
||||
};
|
||||
|
||||
unsubscribe.push(unregisterOverlay);
|
||||
scope.$on('$destroy', function () {
|
||||
for (var i = 0; i < unsubscribe.length; i++) {
|
||||
unsubscribe[i]();
|
||||
}
|
||||
});
|
||||
|
||||
activate();
|
||||
|
||||
}
|
||||
|
||||
var directive = {
|
||||
transclude: true,
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/overlays/umb-overlay.html',
|
||||
scope: {
|
||||
ngShow: "=",
|
||||
model: "=",
|
||||
view: "=",
|
||||
position: "@",
|
||||
size: "=?",
|
||||
parentScope: "=?"
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
unsubscribe.push(unregisterOverlay);
|
||||
scope.$on('$destroy', function () {
|
||||
for (var i = 0; i < unsubscribe.length; i++) {
|
||||
unsubscribe[i]();
|
||||
}
|
||||
});
|
||||
return directive;
|
||||
}
|
||||
|
||||
activate();
|
||||
|
||||
}
|
||||
|
||||
var directive = {
|
||||
transclude: true,
|
||||
restrict: 'E',
|
||||
replace: true,
|
||||
templateUrl: 'views/components/overlays/umb-overlay.html',
|
||||
scope: {
|
||||
ngShow: "=",
|
||||
model: "=",
|
||||
view: "=",
|
||||
position: "@",
|
||||
size: "=?",
|
||||
parentScope: "=?"
|
||||
},
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
}
|
||||
|
||||
angular.module('umbraco.directives').directive('umbOverlay', OverlayDirective);
|
||||
angular.module('umbraco.directives').directive('umbOverlay', OverlayDirective);
|
||||
|
||||
})();
|
||||
|
||||
+2
-2
@@ -159,7 +159,7 @@
|
||||
|
||||
function configureViewModel(isInitLoad) {
|
||||
if (vm.value) {
|
||||
if (angular.isString(vm.value) && vm.value.length > 0) {
|
||||
if (Utilities.isString(vm.value) && vm.value.length > 0) {
|
||||
if (vm.config.storageType === "Json") {
|
||||
//json storage
|
||||
vm.viewModel = JSON.parse(vm.value);
|
||||
@@ -191,7 +191,7 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (angular.isArray(vm.value)) {
|
||||
else if (Utilities.isArray(vm.value)) {
|
||||
vm.viewModel = vm.value;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -87,7 +87,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
|
||||
|
||||
/** Helper function to emit tree events */
|
||||
function emitEvent(eventName, args) {
|
||||
if (registeredCallbacks[eventName] && angular.isArray(registeredCallbacks[eventName])) {
|
||||
if (registeredCallbacks[eventName] && Utilities.isArray(registeredCallbacks[eventName])) {
|
||||
_.each(registeredCallbacks[eventName], function (c) {
|
||||
c(args);//call it
|
||||
});
|
||||
@@ -100,7 +100,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
|
||||
* @param {any} args either a string representing the 'section' or an object containing: 'section', 'treeAlias', 'customTreeParams', 'cacheKey'
|
||||
*/
|
||||
function load(args) {
|
||||
if (angular.isString(args)) {
|
||||
if (Utilities.isString(args)) {
|
||||
$scope.section = args;
|
||||
}
|
||||
else if (args) {
|
||||
@@ -147,7 +147,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
|
||||
throw "args.path cannot be null";
|
||||
}
|
||||
|
||||
if (angular.isString(args.path)) {
|
||||
if (Utilities.isString(args.path)) {
|
||||
args.path = args.path.replace('"', '').split(',');
|
||||
}
|
||||
|
||||
|
||||
+16
-16
@@ -24,40 +24,40 @@
|
||||
|
||||
// sets the ace worker path, if running from concatenated
|
||||
// or minified source
|
||||
if (angular.isDefined(opts.workerPath)) {
|
||||
if (Utilities.isDefined(opts.workerPath)) {
|
||||
var config = window.ace.require('ace/config');
|
||||
config.set('workerPath', opts.workerPath);
|
||||
}
|
||||
|
||||
// ace requires loading
|
||||
if (angular.isDefined(opts.require)) {
|
||||
if (Utilities.isDefined(opts.require)) {
|
||||
opts.require.forEach(function(n) {
|
||||
window.ace.require(n);
|
||||
});
|
||||
}
|
||||
|
||||
// Boolean options
|
||||
if (angular.isDefined(opts.showGutter)) {
|
||||
if (Utilities.isDefined(opts.showGutter)) {
|
||||
acee.renderer.setShowGutter(opts.showGutter);
|
||||
}
|
||||
if (angular.isDefined(opts.useWrapMode)) {
|
||||
if (Utilities.isDefined(opts.useWrapMode)) {
|
||||
session.setUseWrapMode(opts.useWrapMode);
|
||||
}
|
||||
if (angular.isDefined(opts.showInvisibles)) {
|
||||
if (Utilities.isDefined(opts.showInvisibles)) {
|
||||
acee.renderer.setShowInvisibles(opts.showInvisibles);
|
||||
}
|
||||
if (angular.isDefined(opts.showIndentGuides)) {
|
||||
if (Utilities.isDefined(opts.showIndentGuides)) {
|
||||
acee.renderer.setDisplayIndentGuides(opts.showIndentGuides);
|
||||
}
|
||||
if (angular.isDefined(opts.useSoftTabs)) {
|
||||
if (Utilities.isDefined(opts.useSoftTabs)) {
|
||||
session.setUseSoftTabs(opts.useSoftTabs);
|
||||
}
|
||||
if (angular.isDefined(opts.showPrintMargin)) {
|
||||
if (Utilities.isDefined(opts.showPrintMargin)) {
|
||||
acee.setShowPrintMargin(opts.showPrintMargin);
|
||||
}
|
||||
|
||||
// commands
|
||||
if (angular.isDefined(opts.disableSearch) && opts.disableSearch) {
|
||||
if (Utilities.isDefined(opts.disableSearch) && opts.disableSearch) {
|
||||
acee.commands.addCommands([{
|
||||
name: 'unfind',
|
||||
bindKey: {
|
||||
@@ -72,15 +72,15 @@
|
||||
}
|
||||
|
||||
// Basic options
|
||||
if (angular.isString(opts.theme)) {
|
||||
if (Utilities.isString(opts.theme)) {
|
||||
acee.setTheme('ace/theme/' + opts.theme);
|
||||
}
|
||||
if (angular.isString(opts.mode)) {
|
||||
if (Utilities.isString(opts.mode)) {
|
||||
session.setMode('ace/mode/' + opts.mode);
|
||||
}
|
||||
// Advanced options
|
||||
if (angular.isDefined(opts.firstLineNumber)) {
|
||||
if (angular.isNumber(opts.firstLineNumber)) {
|
||||
if (Utilities.isDefined(opts.firstLineNumber)) {
|
||||
if (Utilities.isNumber(opts.firstLineNumber)) {
|
||||
session.setOption('firstLineNumber', opts.firstLineNumber);
|
||||
} else if (angular.isFunction(opts.firstLineNumber)) {
|
||||
session.setOption('firstLineNumber', opts.firstLineNumber());
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
// advanced options
|
||||
var key, obj;
|
||||
if (angular.isDefined(opts.advanced)) {
|
||||
if (Utilities.isDefined(opts.advanced)) {
|
||||
for (key in opts.advanced) {
|
||||
// create a javascript object with the key and value
|
||||
obj = {
|
||||
@@ -102,7 +102,7 @@
|
||||
}
|
||||
|
||||
// advanced options for the renderer
|
||||
if (angular.isDefined(opts.rendererOptions)) {
|
||||
if (Utilities.isDefined(opts.rendererOptions)) {
|
||||
for (key in opts.rendererOptions) {
|
||||
// create a javascript object with the key and value
|
||||
obj = {
|
||||
@@ -206,7 +206,7 @@
|
||||
*/
|
||||
var args = Array.prototype.slice.call(arguments, 1);
|
||||
|
||||
if (angular.isDefined(callback)) {
|
||||
if (Utilities.isDefined(callback)) {
|
||||
scope.$evalAsync(function() {
|
||||
if (angular.isFunction(callback)) {
|
||||
callback(args);
|
||||
|
||||
+121
-121
@@ -67,37 +67,37 @@ Use this directive to render a date time picker
|
||||
@param {callback} onDayCreate (<code>callback</code>): Take full control of every date cell with theonDayCreate()hook.
|
||||
**/
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var umbDateTimePicker = {
|
||||
template: '<ng-transclude>' +
|
||||
'<input type="text" ng-if="!$ctrl.options.inline" ng-model="$ctrl.ngModel" placeholder="Select Date.."></input>' +
|
||||
'<div ng-if="$ctrl.options.inline"></div>' +
|
||||
'</ng-transclude>',
|
||||
controller: umbDateTimePickerCtrl,
|
||||
transclude: true,
|
||||
bindings: {
|
||||
ngModel: '<',
|
||||
options: '<',
|
||||
onSetup: '&?',
|
||||
onChange: '&?',
|
||||
onOpen: '&?',
|
||||
onClose: '&?',
|
||||
onMonthChange: '&?',
|
||||
onYearChange: '&?',
|
||||
onReady: '&?',
|
||||
onValueUpdate: '&?',
|
||||
onDayCreate: '&?'
|
||||
}
|
||||
var umbDateTimePicker = {
|
||||
template: '<ng-transclude>' +
|
||||
'<input type="text" ng-if="!$ctrl.options.inline" ng-model="$ctrl.ngModel" placeholder="Select Date.."></input>' +
|
||||
'<div ng-if="$ctrl.options.inline"></div>' +
|
||||
'</ng-transclude>',
|
||||
controller: umbDateTimePickerCtrl,
|
||||
transclude: true,
|
||||
bindings: {
|
||||
ngModel: '<',
|
||||
options: '<',
|
||||
onSetup: '&?',
|
||||
onChange: '&?',
|
||||
onOpen: '&?',
|
||||
onClose: '&?',
|
||||
onMonthChange: '&?',
|
||||
onYearChange: '&?',
|
||||
onReady: '&?',
|
||||
onValueUpdate: '&?',
|
||||
onDayCreate: '&?'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function umbDateTimePickerCtrl($element, $timeout, $scope, assetsService, userService) {
|
||||
|
||||
var ctrl = this;
|
||||
var userLocale = null;
|
||||
|
||||
ctrl.$onInit = function() {
|
||||
ctrl.$onInit = function () {
|
||||
|
||||
// load css file for the date picker
|
||||
assetsService.loadCss('lib/flatpickr/flatpickr.css', $scope).then(function () {
|
||||
@@ -113,27 +113,27 @@ Use this directive to render a date time picker
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
function grabElementAndRunFlatpickr() {
|
||||
$timeout(function() {
|
||||
var transcludeEl = $element.find('ng-transclude')[0];
|
||||
var element = transcludeEl.children[0];
|
||||
function grabElementAndRunFlatpickr() {
|
||||
$timeout(function () {
|
||||
var transcludeEl = $element.find('ng-transclude')[0];
|
||||
var element = transcludeEl.children[0];
|
||||
|
||||
setDatepicker(element);
|
||||
}, 0, true);
|
||||
}
|
||||
setDatepicker(element);
|
||||
}, 0, true);
|
||||
}
|
||||
|
||||
function setDatepicker(element) {
|
||||
var fpLib = flatpickr ? flatpickr : FlatpickrInstance;
|
||||
function setDatepicker(element) {
|
||||
var fpLib = flatpickr ? flatpickr : FlatpickrInstance;
|
||||
|
||||
if (!fpLib) {
|
||||
return console.warn('Unable to find any flatpickr installation');
|
||||
}
|
||||
if (!fpLib) {
|
||||
return console.warn('Unable to find any flatpickr installation');
|
||||
}
|
||||
|
||||
var fpInstance;
|
||||
|
||||
setUpCallbacks();
|
||||
setUpCallbacks();
|
||||
|
||||
if (!ctrl.options.locale) {
|
||||
ctrl.options.locale = userLocale;
|
||||
@@ -149,101 +149,101 @@ Use this directive to render a date time picker
|
||||
};
|
||||
|
||||
fpInstance = new fpLib(element, ctrl.options);
|
||||
|
||||
if (ctrl.onSetup) {
|
||||
ctrl.onSetup({
|
||||
fpItem: fpInstance
|
||||
});
|
||||
}
|
||||
|
||||
// If has ngModel set the date
|
||||
if (ctrl.ngModel) {
|
||||
fpInstance.setDate(ctrl.ngModel);
|
||||
}
|
||||
if (ctrl.onSetup) {
|
||||
ctrl.onSetup({
|
||||
fpItem: fpInstance
|
||||
});
|
||||
}
|
||||
|
||||
// destroy the flatpickr instance when the dom element is removed
|
||||
angular.element(element).on('$destroy', function() {
|
||||
fpInstance.destroy();
|
||||
});
|
||||
// If has ngModel set the date
|
||||
if (ctrl.ngModel) {
|
||||
fpInstance.setDate(ctrl.ngModel);
|
||||
}
|
||||
|
||||
// Refresh the scope
|
||||
$scope.$applyAsync();
|
||||
}
|
||||
// destroy the flatpickr instance when the dom element is removed
|
||||
$(element).on('$destroy', function () {
|
||||
fpInstance.destroy();
|
||||
});
|
||||
|
||||
function setUpCallbacks() {
|
||||
// bind hook for onChange
|
||||
if(ctrl.options && ctrl.onChange) {
|
||||
ctrl.options.onChange = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
// Refresh the scope
|
||||
$scope.$applyAsync();
|
||||
}
|
||||
|
||||
// bind hook for onOpen
|
||||
if(ctrl.options && ctrl.onOpen) {
|
||||
ctrl.options.onOpen = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onOpen({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
function setUpCallbacks() {
|
||||
// bind hook for onChange
|
||||
if (ctrl.options && ctrl.onChange) {
|
||||
ctrl.options.onChange = function (selectedDates, dateStr, instance) {
|
||||
$timeout(function () {
|
||||
ctrl.onChange({ selectedDates: selectedDates, dateStr: dateStr, instance: instance });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onOpen
|
||||
if(ctrl.options && ctrl.onClose) {
|
||||
ctrl.options.onClose = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onClose({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
// bind hook for onOpen
|
||||
if (ctrl.options && ctrl.onOpen) {
|
||||
ctrl.options.onOpen = function (selectedDates, dateStr, instance) {
|
||||
$timeout(function () {
|
||||
ctrl.onOpen({ selectedDates: selectedDates, dateStr: dateStr, instance: instance });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onMonthChange
|
||||
if(ctrl.options && ctrl.onMonthChange) {
|
||||
ctrl.options.onMonthChange = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onMonthChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
// bind hook for onOpen
|
||||
if (ctrl.options && ctrl.onClose) {
|
||||
ctrl.options.onClose = function (selectedDates, dateStr, instance) {
|
||||
$timeout(function () {
|
||||
ctrl.onClose({ selectedDates: selectedDates, dateStr: dateStr, instance: instance });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onYearChange
|
||||
if(ctrl.options && ctrl.onYearChange) {
|
||||
ctrl.options.onYearChange = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onYearChange({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
// bind hook for onMonthChange
|
||||
if (ctrl.options && ctrl.onMonthChange) {
|
||||
ctrl.options.onMonthChange = function (selectedDates, dateStr, instance) {
|
||||
$timeout(function () {
|
||||
ctrl.onMonthChange({ selectedDates: selectedDates, dateStr: dateStr, instance: instance });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onReady
|
||||
if(ctrl.options && ctrl.onReady) {
|
||||
ctrl.options.onReady = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onReady({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
// bind hook for onYearChange
|
||||
if (ctrl.options && ctrl.onYearChange) {
|
||||
ctrl.options.onYearChange = function (selectedDates, dateStr, instance) {
|
||||
$timeout(function () {
|
||||
ctrl.onYearChange({ selectedDates: selectedDates, dateStr: dateStr, instance: instance });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onValueUpdate
|
||||
if(ctrl.onValueUpdate) {
|
||||
ctrl.options.onValueUpdate = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onValueUpdate({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
// bind hook for onReady
|
||||
if (ctrl.options && ctrl.onReady) {
|
||||
ctrl.options.onReady = function (selectedDates, dateStr, instance) {
|
||||
$timeout(function () {
|
||||
ctrl.onReady({ selectedDates: selectedDates, dateStr: dateStr, instance: instance });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// bind hook for onDayCreate
|
||||
if(ctrl.onDayCreate) {
|
||||
ctrl.options.onDayCreate = function(selectedDates, dateStr, instance) {
|
||||
$timeout(function() {
|
||||
ctrl.onDayCreate({selectedDates: selectedDates, dateStr: dateStr, instance: instance});
|
||||
});
|
||||
};
|
||||
}
|
||||
// bind hook for onValueUpdate
|
||||
if (ctrl.onValueUpdate) {
|
||||
ctrl.options.onValueUpdate = function (selectedDates, dateStr, instance) {
|
||||
$timeout(function () {
|
||||
ctrl.onValueUpdate({ selectedDates: selectedDates, dateStr: dateStr, instance: instance });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
// bind hook for onDayCreate
|
||||
if (ctrl.onDayCreate) {
|
||||
ctrl.options.onDayCreate = function (selectedDates, dateStr, instance) {
|
||||
$timeout(function () {
|
||||
ctrl.onDayCreate({ selectedDates: selectedDates, dateStr: dateStr, instance: instance });
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// umbFlatpickr (umb-flatpickr) is deprecated, but we keep it for backwards compatibility
|
||||
|
||||
+671
-669
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -31,7 +31,7 @@ Use this directive to lazy-load an image only when it is scrolled into view.
|
||||
|
||||
**/
|
||||
|
||||
(function() {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
function ImageLazyLoadDirective() {
|
||||
@@ -41,7 +41,7 @@ Use this directive to lazy-load an image only when it is scrolled into view.
|
||||
function link(scope, element, attrs) {
|
||||
|
||||
const observer = new IntersectionObserver(loadImg);
|
||||
const img = angular.element(element)[0];
|
||||
const img = element[0];
|
||||
img.src = placeholder;
|
||||
observer.observe(img);
|
||||
|
||||
|
||||
+16
-17
@@ -120,26 +120,26 @@ When this combination is hit an overview is opened with shortcuts based on the m
|
||||
|
||||
scope.toggleShortcutsOverlay = function () {
|
||||
|
||||
if(overlay) {
|
||||
if (overlay) {
|
||||
scope.close();
|
||||
} else {
|
||||
scope.open();
|
||||
}
|
||||
|
||||
if(scope.onToggle) {
|
||||
if (scope.onToggle) {
|
||||
scope.onToggle();
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
scope.open = function() {
|
||||
if(!overlay) {
|
||||
scope.open = function () {
|
||||
if (!overlay) {
|
||||
overlay = {
|
||||
title: "Keyboard shortcuts",
|
||||
view: "keyboardshortcuts",
|
||||
hideSubmitButton: true,
|
||||
shortcuts: scope.model,
|
||||
close: function() {
|
||||
close: function () {
|
||||
scope.close();
|
||||
}
|
||||
};
|
||||
@@ -147,20 +147,20 @@ When this combination is hit an overview is opened with shortcuts based on the m
|
||||
}
|
||||
};
|
||||
|
||||
scope.close = function() {
|
||||
if(overlay) {
|
||||
scope.close = function () {
|
||||
if (overlay) {
|
||||
overlayService.close();
|
||||
overlay = null;
|
||||
if(scope.onClose) {
|
||||
if (scope.onClose) {
|
||||
scope.onClose();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function onInit() {
|
||||
angular.forEach(scope.model, function (shortcutGroup) {
|
||||
angular.forEach(shortcutGroup.shortcuts, function (shortcut) {
|
||||
Utilities.forEach(scope.model, shortcutGroup => {
|
||||
|
||||
Utilities.forEach(shortcutGroup.shortcuts, shortcut => {
|
||||
shortcut.platformKeys = [];
|
||||
|
||||
// get shortcut keys for mac
|
||||
@@ -173,30 +173,29 @@ When this combination is hit an overview is opened with shortcuts based on the m
|
||||
} else if (shortcut.keys && shortcut && shortcut.keys.length > 0) {
|
||||
shortcut.platformKeys = shortcut.keys;
|
||||
}
|
||||
|
||||
});
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
onInit();
|
||||
|
||||
eventBindings.push(scope.$watch('model', function(newValue, oldValue){
|
||||
eventBindings.push(scope.$watch('model', function (newValue, oldValue) {
|
||||
if (newValue !== oldValue) {
|
||||
onInit();
|
||||
}
|
||||
}));
|
||||
|
||||
eventBindings.push(scope.$watch('showOverlay', function(newValue, oldValue){
|
||||
eventBindings.push(scope.$watch('showOverlay', function (newValue, oldValue) {
|
||||
|
||||
if(newValue === oldValue) {
|
||||
if (newValue === oldValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(newValue === true) {
|
||||
if (newValue === true) {
|
||||
scope.open();
|
||||
}
|
||||
|
||||
if(newValue === false) {
|
||||
if (newValue === false) {
|
||||
scope.close();
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user