Merge remote-tracking branch 'origin/v8/dev' into v8/feature/mb-embed

This commit is contained in:
Bjarke Berg
2019-10-29 16:09:43 +01:00
139 changed files with 6546 additions and 6034 deletions
+32 -24
View File
@@ -40,9 +40,9 @@ This document gives you a quick overview on how to get started.
Not all changes are wanted, so on occasion we might close a PR without merging it. We will give you feedback why we can't accept your changes and we'll be nice about it, thanking you for spending your valuable time.
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes.
We have [documented what we consider small and large changes](CONTRIBUTION_GUIDELINES.md). Make sure to talk to us before making large changes, so we can ensure that you don't put all your hard work into something we would not be able to merge.
Remember, if an issue is in the `Up for grabs` list or you've asked for some feedback before you sent us a PR, your PR will not be closed as unwanted.
Remember, it is always worth working on an issue from the `Up for grabs` list or even asking for some feedback before you send us a PR. This way, your PR will not be closed as unwanted.
### What can I start with?
@@ -64,32 +64,32 @@ Great question! The short version goes like this:
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
* **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/dev`, create a new branch first.
* **Push** - great, now you can push the changes up to your fork on GitHub
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here] (https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
![Create a pull request](img/createpullrequest.png)
### Pull requests
The most successful pull requests usually look a like this:
* Fill in the required template
* 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.
* 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
* 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.
Again, these are guidelines, not strict requirements.
Again, these are guidelines, not strict requirements. However, the more information that you give to us, the more we have to work with when considering your contributions. Good documentation of a pull request can really speed up the time it takes to review and merge your work!
## Reviews
You've sent us your first contribution, congratulations! Now what?
You've sent us your first contribution - congratulations! Now what?
The [pull request team](#the-pr-team) can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request you to make some additional changes.
The [pull request team](#the-pr-team) can now start reviewing your proposed changes and give you feedback on them. If it's not perfect, we'll either fix up what we need or we can request that you make some additional changes.
We have [a process in place which you can read all about](REVIEW_PROCESS.md). The very abbreviated version is:
- Your PR will get a reply within 48 hours
- An in-depth reply will be added within at most 2 weeks
- The PR will be either merged or rejected within at most 4 weeks
- Sometimes it is difficult to meet these timelines and we'll talk to you
- Sometimes it is difficult to meet these timelines and we'll talk to you if this is the case.
### Styleguides
@@ -99,21 +99,21 @@ That said, the Umbraco development team likes to follow the hints that ReSharper
### The PR team
The pull request team consists of a member of Umbraco HQ, [Sebastiaan](https://github.com/nul800sebastiaan), who gets assistance from the following community members
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:
- [Anders Bjerner](https://github.com/abjerner)
- [Dave Woestenborghs](https://github.com/dawoe)
- [Emma Burstow](https://github.com/emmaburstow)
- [Poornima Nayar](https://github.com/poornimanayar)
These wonderful volunteers will provide you with a first reply to your PR, review and test out your changes and might ask more questions. After that they'll let Umbraco HQ know if everything seems okay.
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 questions you have usually helps out multiple people with the same question. Ask away:
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:
- 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
- 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.
## Working with the code
@@ -125,19 +125,19 @@ In order to build the Umbraco source code locally, first make sure you have the
* Node v10+
* npm v6.4.1+
The easiest way to get started is to run `build.ps1` which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
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.
Alternatively, you can 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.
Alternatively, you can run `build.ps1` from the Powershell command line, which will build both the backoffice (also known as "Belle") and the Umbraco core. You can then easily start debugging from Visual Studio, or if you need to debug Belle you can run `gulp dev` in `src\Umbraco.Web.UI.Client`. See [this page](BUILD.md) for more details.
![Gulp build in Visual Studio](img/gulpbuild.png)
After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser, follow the directions there to get a working Umbraco install up and running.
After this build completes, you should be able to hit `F5` in Visual Studio to build and run the project. A IISExpress webserver will start and the Umbraco installer will pop up in your browser. Follow the directions there to get a working Umbraco install up and running.
### Working with the source code
Some parts of our source code are over 10 years old now. And when we say "old", we mean "mature" of course!
There's two big areas that you should know about:
There are two big areas that you should know about:
1. The Umbraco backoffice is a extensible AngularJS app and requires you to run a `gulp dev` command while you're working with it, so changes are copied over to the appropriate directories and you can refresh your browser to view the results of your changes.
You may need to run the following commands to set up gulp properly:
@@ -146,20 +146,24 @@ There's two big areas that you should know about:
npm install
npm run build
```
The caching for the back office has been described as 'aggressive' so we often find it's best when making back office changes to disable caching in the browser to help you to see the changes you're making.
2. "The rest" is a C# based codebase, which is mostly ASP.NET MVC based. You can make changes, build them in Visual Studio, and hit `F5` to see the result.
To find the general areas of something you're looking to fix or improve, have a look at the following two parts of the API documentation.
To find the general areas for something you're looking to fix or improve, have a look at the following two parts of the API documentation.
* [The AngularJS based backoffice files](https://our.umbraco.com/apidocs/ui/#/api) (to be found in `src\Umbraco.Web.UI.Client\src`)
* [The C# application](https://our.umbraco.com/apidocs/csharp/)
### Which branch should I target for my contributions?
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/dev`. Whatever the default is, that's where we'd like you to target your contributions.
We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/dev`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise.
![Which branch should I target?](img/defaultbranch.png)
### Making changes after the PR was opened
### Making changes after the PR is open
If you make the corrections we ask for in the same branch and push them to your fork again, the pull request automatically updates with the additional commit(s) so we can review it again. If all is well, we'll merge the code and your commits are forever part of Umbraco!
@@ -167,9 +171,9 @@ If you make the corrections we ask for in the same branch and push them to your
We recommend you sync with our repository before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier.
Also, if you've submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course.
Also, if you submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course.
To sync your fork with this original one, you'll have to add the upstream url, you only have to do this once:
To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once:
```
git remote add upstream https://github.com/umbraco/Umbraco-CMS.git
@@ -185,3 +189,7 @@ git rebase upstream/v8/dev
In this command we're syncing with the `v8/dev` branch, but you can of course choose another one if needed.
(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
### 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.
+1 -1
View File
@@ -26,6 +26,6 @@ The following items will now be automatically included when creating a deploy pa
system: umbraco, config\splashes and global.asax.
Please read the release notes on our.umbraco.com:
http://our.umbraco.com/contribute/releases
https://our.umbraco.com/contribute/releases
- Umbraco
@@ -6,6 +6,7 @@ namespace Umbraco.Core.Configuration.Grid
public interface IGridEditorConfig
{
string Name { get; }
string NameTemplate { get; }
string Alias { get; }
string View { get; }
string Render { get; }
@@ -6,7 +6,7 @@ namespace Umbraco.Core.Configuration.UmbracoSettings
{
internal class ContentElement : UmbracoConfigurationElement, IContentSection
{
private const string DefaultPreviewBadge = @"<a id=""umbracoPreviewBadge"" style=""z-index:99999; position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;"" href=""#"" OnClick=""javascript:window.top.location.href = '{0}/preview/end?redir={1}'""><span style=""display:none;"">In Preview Mode - click to end</span></a>";
private const string DefaultPreviewBadge = @"<div id=""umbracoPreviewBadge"" class=""umbraco-preview-badge""><span class=""umbraco-preview-badge__header"">Preview mode</span><a href=""{0}/preview/end?redir={1}"" class=""umbraco-preview-badge__end""><svg viewBox=""0 0 100 100"" xmlns=""http://www.w3.org/2000/svg""><title>Click to end</title><path d=""M5273.1 2400.1v-2c0-2.8-5-4-9.7-4s-9.7 1.3-9.7 4v2a7 7 0 002 4.9l5 4.9c.3.3.4.6.4 1v6.4c0 .4.2.7.6.8l2.9.9c.5.1 1-.2 1-.8v-7.2c0-.4.2-.7.4-1l5.1-5a7 7 0 002-4.9zm-9.7-.1c-4.8 0-7.4-1.3-7.5-1.8.1-.5 2.7-1.8 7.5-1.8s7.3 1.3 7.5 1.8c-.2.5-2.7 1.8-7.5 1.8z""/><path d=""M5268.4 2410.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1s-.4-1-1-1h-4.3zM5272.7 2413.7h-4.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1s-.4-1-1-1zM5272.7 2417h-4.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1 0-.5-.4-1-1-1z""/><path d=""M78.2 13l-8.7 11.7a32.5 32.5 0 11-51.9 25.8c0-10.3 4.7-19.7 12.9-25.8L21.8 13a47 47 0 1056.4 0z""/><path d=""M42.7 2.5h14.6v49.4H42.7z""/></svg></a></div><style type=""text/css"">.umbraco-preview-badge {{position: absolute;top: 1em;right: 1em;display: inline-flex;background: #1b264f;color: #fff;padding: 1em;font-size: 12px;z-index: 99999999;justify-content: center;align-items: center;box-shadow: 0 10px 50px rgba(0, 0, 0, .1), 0 6px 20px rgba(0, 0, 0, .16);line-height: 1;}}.umbraco-preview-badge__header {{font-weight: bold;}}.umbraco-preview-badge__end {{width: 3em;padding: 1em;margin: -1em -1em -1em 2em;display: flex;flex-shrink: 0;align-items: center;align-self: stretch;}}.umbraco-preview-badge__end:hover,.umbraco-preview-badge__end:focus {{background: #f5c1bc;}}.umbraco-preview-badge__end svg {{fill: #fff;width:1em;}}</style>";
[ConfigurationProperty("imaging")]
internal ContentImagingElement Imaging => (ContentImagingElement) this["imaging"];
+2 -2
View File
@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Web.Hosting;
using Umbraco.Core.Logging;
@@ -65,7 +65,7 @@ namespace Umbraco.Core
// a new process for the same application path
var appPath = HostingEnvironment.ApplicationPhysicalPath;
var hash = (appId + ":::" + appPath).ToSHA1();
var hash = (appId + ":::" + appPath).GenerateHash<SHA1>();
var lockName = "UMBRACO-" + hash + "-MAINDOM-LCK";
_asyncLock = new AsyncLock(lockName);
@@ -67,6 +67,12 @@ namespace Umbraco.Core.Models.ContentEditing
/// </remarks>
[DataMember(Name = "active")]
public bool Active { get; set; }
/// <summary>
/// Gets or sets the content app badge.
/// </summary>
[DataMember(Name = "badge")]
public ContentAppBadge Badge { get; set; }
}
}
@@ -0,0 +1,39 @@
namespace Umbraco.Core.Models.ContentEditing
{
using System.Runtime.Serialization;
using Umbraco.Core.Events;
/// <summary>
/// Represents a content app badge
/// </summary>
[DataContract(Name = "badge", Namespace = "")]
public class ContentAppBadge
{
/// <summary>
/// Initializes a new instance of the <see cref="ContentAppBadge"/> class.
/// </summary>
public ContentAppBadge()
{
this.Type = ContentAppBadgeType.Default;
}
/// <summary>
/// Gets or sets the number displayed in the badge
/// </summary>
[DataMember(Name = "count")]
public int Count { get; set; }
/// <summary>
/// Gets or sets the type of badge to display
/// </summary>
/// <remarks>
/// <para>This controls the background color of the badge.</para>
/// <para>Warning will display a dark yellow badge</para>
/// <para>Alert will display a red badge</para>
/// <para>Default will display a turquoise badge</para>
/// </remarks>
[DataMember(Name = "type")]
public ContentAppBadgeType Type { get; set; }
}
}
@@ -0,0 +1,24 @@
namespace Umbraco.Core.Models.ContentEditing
{
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
/// <summary>
/// Represent the content app badge types
/// </summary>
[DataContract(Name = "contentAppBadgeType")]
[JsonConverter(typeof(StringEnumConverter))]
public enum ContentAppBadgeType
{
[EnumMember(Value = "default")]
Default = 0,
[EnumMember(Value = "warning")]
Warning = 1,
[EnumMember(Value = "alert")]
Alert = 2
}
}
+1 -1
View File
@@ -67,7 +67,7 @@ namespace Umbraco.Core.Models
if (user.Avatar.IsNullOrWhiteSpace())
{
var gravatarHash = user.Email.ToMd5();
var gravatarHash = user.Email.GenerateHash<MD5>();
var gravatarUrl = "https://www.gravatar.com/avatar/" + gravatarHash + "?d=404";
//try Gravatar
+3 -3
View File
@@ -542,7 +542,7 @@ namespace Umbraco.Core
{
return "\"{0}\"".InvariantFormat(obj);
}
if (obj is int || obj is Int16 || obj is Int64 || obj is float || obj is double || obj is bool || obj is int? || obj is Int16? || obj is Int64? || obj is float? || obj is double? || obj is bool?)
if (obj is int || obj is short || obj is long || obj is float || obj is double || obj is bool || obj is int? || obj is float? || obj is double? || obj is bool?)
{
return "{0}".InvariantFormat(obj);
}
@@ -723,7 +723,7 @@ namespace Umbraco.Core
{
return typeConverter;
}
var converter = TypeDescriptor.GetConverter(target);
if (converter.CanConvertFrom(source))
{
@@ -788,6 +788,6 @@ namespace Umbraco.Core
return BoolConvertCache[type] = false;
}
}
}
+30 -30
View File
@@ -470,7 +470,7 @@ namespace Umbraco.Core.Persistence
break;
case SqlDbType.SmallInt:
dataType = typeof(Int16);
dataType = typeof(short);
dataTypeName = "smallint";
break;
@@ -688,34 +688,34 @@ namespace Umbraco.Core.Persistence
DataColumnCollection columns = _schemaTable.Columns;
columns.Add(SchemaTableColumn.ColumnName, typeof(System.String));
columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(System.Int32));
columns.Add(SchemaTableColumn.ColumnSize, typeof(System.Int32));
columns.Add(SchemaTableColumn.NumericPrecision, typeof(System.Int16));
columns.Add(SchemaTableColumn.NumericScale, typeof(System.Int16));
columns.Add(SchemaTableColumn.IsUnique, typeof(System.Boolean));
columns.Add(SchemaTableColumn.IsKey, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(System.String));
columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(System.String));
columns.Add(SchemaTableColumn.BaseColumnName, typeof(System.String));
columns.Add(SchemaTableColumn.BaseSchemaName, typeof(System.String));
columns.Add(SchemaTableColumn.BaseTableName, typeof(System.String));
columns.Add(SchemaTableColumn.DataType, typeof(System.Type));
columns.Add(SchemaTableColumn.AllowDBNull, typeof(System.Boolean));
columns.Add(SchemaTableColumn.ProviderType, typeof(System.Int32));
columns.Add(SchemaTableColumn.IsAliased, typeof(System.Boolean));
columns.Add(SchemaTableColumn.IsExpression, typeof(System.Boolean));
columns.Add(BulkDataReader.IsIdentitySchemaColumn, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(System.Boolean));
columns.Add(SchemaTableColumn.IsLong, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(System.Boolean));
columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(System.Type));
columns.Add(BulkDataReader.DataTypeNameSchemaColumn, typeof(System.String));
columns.Add(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, typeof(System.String));
columns.Add(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(System.String));
columns.Add(BulkDataReader.XmlSchemaCollectionNameSchemaColumn, typeof(System.String));
columns.Add(SchemaTableColumn.ColumnName, typeof(string));
columns.Add(SchemaTableColumn.ColumnOrdinal, typeof(int));
columns.Add(SchemaTableColumn.ColumnSize, typeof(int));
columns.Add(SchemaTableColumn.NumericPrecision, typeof(short));
columns.Add(SchemaTableColumn.NumericScale, typeof(short));
columns.Add(SchemaTableColumn.IsUnique, typeof(bool));
columns.Add(SchemaTableColumn.IsKey, typeof(bool));
columns.Add(SchemaTableOptionalColumn.BaseServerName, typeof(string));
columns.Add(SchemaTableOptionalColumn.BaseCatalogName, typeof(string));
columns.Add(SchemaTableColumn.BaseColumnName, typeof(string));
columns.Add(SchemaTableColumn.BaseSchemaName, typeof(string));
columns.Add(SchemaTableColumn.BaseTableName, typeof(string));
columns.Add(SchemaTableColumn.DataType, typeof(Type));
columns.Add(SchemaTableColumn.AllowDBNull, typeof(bool));
columns.Add(SchemaTableColumn.ProviderType, typeof(int));
columns.Add(SchemaTableColumn.IsAliased, typeof(bool));
columns.Add(SchemaTableColumn.IsExpression, typeof(bool));
columns.Add(BulkDataReader.IsIdentitySchemaColumn, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsAutoIncrement, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsRowVersion, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsHidden, typeof(bool));
columns.Add(SchemaTableColumn.IsLong, typeof(bool));
columns.Add(SchemaTableOptionalColumn.IsReadOnly, typeof(bool));
columns.Add(SchemaTableOptionalColumn.ProviderSpecificDataType, typeof(Type));
columns.Add(BulkDataReader.DataTypeNameSchemaColumn, typeof(string));
columns.Add(BulkDataReader.XmlSchemaCollectionDatabaseSchemaColumn, typeof(string));
columns.Add(BulkDataReader.XmlSchemaCollectionOwningSchemaSchemaColumn, typeof(string));
columns.Add(BulkDataReader.XmlSchemaCollectionNameSchemaColumn, typeof(string));
}
#endregion
@@ -1090,7 +1090,7 @@ namespace Umbraco.Core.Persistence
/// <seealso cref="IDataRecord.GetDecimal(Int32)"/>
public decimal GetDecimal(int i)
{
return (Decimal)GetValue(i);
return (decimal)GetValue(i);
}
/// <summary>
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using NPoco;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
@@ -105,7 +106,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
CreateDateUtc = redirectUrl.CreateDateUtc,
Url = redirectUrl.Url,
Culture = redirectUrl.Culture,
UrlHash = redirectUrl.Url.ToSHA1()
UrlHash = redirectUrl.Url.GenerateHash<SHA1>()
};
}
@@ -134,7 +135,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
public IRedirectUrl Get(string url, Guid contentKey, string culture)
{
var urlHash = url.ToSHA1();
var urlHash = url.GenerateHash<SHA1>();
var sql = GetBaseQuery(false).Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash && x.ContentKey == contentKey && x.Culture == culture);
var dto = Database.Fetch<RedirectUrlDto>(sql).FirstOrDefault();
return dto == null ? null : Map(dto);
@@ -157,7 +158,7 @@ JOIN umbracoNode ON umbracoRedirectUrl.contentKey=umbracoNode.uniqueID");
public IRedirectUrl GetMostRecentUrl(string url)
{
var urlHash = url.ToSHA1();
var urlHash = url.GenerateHash<SHA1>();
var sql = GetBaseQuery(false)
.Where<RedirectUrlDto>(x => x.Url == url && x.UrlHash == urlHash)
.OrderByDescending<RedirectUrlDto>(x => x.CreateDateUtc);
@@ -18,6 +18,9 @@ namespace Umbraco.Core.PropertyEditors
[JsonProperty("name", Required = Required.Always)]
public string Name { get; set; }
[JsonProperty("nameTemplate")]
public string NameTemplate { get; set; }
[JsonProperty("alias", Required = Required.Always)]
public string Alias { get; set; }
@@ -131,6 +131,12 @@ namespace Umbraco.Core.Services
private bool IsPropertyValueValid(PropertyType propertyType, object value)
{
var editor = _propertyEditors[propertyType.PropertyEditorAlias];
if (editor == null)
{
// nothing much we can do validation wise if the property editor has been removed.
// the property will be displayed as a label, so flagging it as invalid would be pointless.
return true;
}
var configuration = _dataTypeService.GetDataType(propertyType.DataTypeId).Configuration;
var valueEditor = editor.GetValueEditor(configuration);
return !valueEditor.Validate(value, propertyType.Mandatory, propertyType.ValidationRegExp).Any();
+28 -39
View File
@@ -724,67 +724,56 @@ namespace Umbraco.Core
/// <summary>
/// Generates a hash of a string based on the FIPS compliance setting.
/// </summary>
/// <param name="str">Refers to itself</param>
/// <returns>The hashed string</returns>
/// <param name="str">The <see cref="string" /> to hash.</param>
/// <returns>
/// The hashed string.
/// </returns>
public static string GenerateHash(this string str)
{
return CryptoConfig.AllowOnlyFipsAlgorithms
? str.ToSHA1()
: str.ToMd5();
return str.GenerateHash(CryptoConfig.AllowOnlyFipsAlgorithms ? "SHA1" : "MD5");
}
/// <summary>
/// Converts the string to MD5
/// Generate a hash of a string based on the specified hash algorithm.
/// </summary>
/// <param name="stringToConvert">Refers to itself</param>
/// <returns>The MD5 hashed string</returns>
[Obsolete("Please use the GenerateHash method instead. This may be removed in future versions")]
internal static string ToMd5(this string stringToConvert)
/// <typeparam name="T">The hash algorithm implementation to use.</typeparam>
/// <param name="str">The <see cref="string" /> to hash.</param>
/// <returns>
/// The hashed string.
/// </returns>
internal static string GenerateHash<T>(this string str)
where T : HashAlgorithm
{
return stringToConvert.GenerateHash("MD5");
return str.GenerateHash(typeof(T).FullName);
}
/// <summary>
/// Converts the string to SHA1
/// Generate a hash of a string based on the specified <paramref name="hashType" />.
/// </summary>
/// <param name="stringToConvert">refers to itself</param>
/// <returns>The SHA1 hashed string</returns>
[Obsolete("Please use the GenerateHash method instead. This may be removed in future versions")]
internal static string ToSHA1(this string stringToConvert)
/// <param name="str">The <see cref="string" /> to hash.</param>
/// <param name="hashType">The hash algorithm implementation to use.</param>
/// <returns>
/// The hashed string.
/// </returns>
/// <exception cref="System.InvalidOperationException">No hashing type found by name <paramref name="hashType" />.</exception>
/// <seealso cref="https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.hashalgorithm.create#System_Security_Cryptography_HashAlgorithm_Create_System_String_" />
internal static string GenerateHash(this string str, string hashType)
{
return stringToConvert.GenerateHash("SHA1");
}
/// <summary>Generate a hash of a string based on the hashType passed in
/// </summary>
/// <param name="str">Refers to itself</param>
/// <param name="hashType">String with the hash type. See remarks section of the CryptoConfig Class in MSDN docs for a list of possible values.</param>
/// <returns>The hashed string</returns>
private static string GenerateHash(this string str, string hashType)
{
//create an instance of the correct hashing provider based on the type passed in
var hasher = HashAlgorithm.Create(hashType);
if (hasher == null) throw new InvalidOperationException("No hashing type found by name " + hashType);
if (hasher == null) throw new InvalidOperationException($"No hashing type found by name {hashType}.");
using (hasher)
{
//convert our string into byte array
var byteArray = Encoding.UTF8.GetBytes(str);
//get the hashed values created by our selected provider
var hashedByteArray = hasher.ComputeHash(byteArray);
//create a StringBuilder object
var stringBuilder = new StringBuilder();
//loop to each byte
var sb = new StringBuilder();
foreach (var b in hashedByteArray)
{
//append it to our StringBuilder
stringBuilder.Append(b.ToString("x2"));
sb.Append(b.ToString("x2"));
}
//return the hashed value
return stringBuilder.ToString();
return sb.ToString();
}
}
+2
View File
@@ -245,6 +245,8 @@
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\RichTextPreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\DropDownFlexiblePreValueMigrator.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\MergeDateAndDateTimePropertyEditor.cs" />
<Compile Include="Models\ContentEditing\ContentAppBadge.cs" />
<Compile Include="Models\ContentEditing\ContentAppBadgeType.cs" />
<Compile Include="Models\Entities\EntityExtensions.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueMigratorBase.cs" />
<Compile Include="Migrations\Upgrade\V_8_0_0\DataTypes\PreValueDto.cs" />
+1 -1
View File
@@ -236,7 +236,7 @@ namespace Umbraco.Core.Xml
_name = name;
_value = value;
if (value is String)
if (value is string)
_type = XPathResultType.String;
else if (value is bool)
_type = XPathResultType.Boolean;
@@ -165,7 +165,7 @@ namespace Umbraco.Tests.Composing
Assert.IsTrue(TypeHelper.MatchType(typeof(int?), typeof(Nullable<>)));
Assert.IsTrue(TypeHelper.MatchType(typeof(Derived<int>), typeof(Object)));
Assert.IsTrue(TypeHelper.MatchType(typeof(Derived<int>), typeof(object)));
Assert.IsFalse(TypeHelper.MatchType(typeof(Derived<int>), typeof(List<>)));
Assert.IsFalse(TypeHelper.MatchType(typeof(Derived<int>), typeof(IEnumerable<>)));
Assert.IsTrue(TypeHelper.MatchType(typeof(Derived<int>), typeof(Base<int>)));
@@ -67,7 +67,7 @@ namespace Umbraco.Tests.Configurations.UmbracoSettings
[Test]
public void PreviewBadge()
{
Assert.AreEqual(SettingsSection.Content.PreviewBadge, @"<a id=""umbracoPreviewBadge"" style=""z-index:99999; position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;"" href=""#"" OnClick=""javascript:window.top.location.href = '{0}/preview/end?redir={1}'""><span style=""display:none;"">In Preview Mode - click to end</span></a>");
Assert.AreEqual(SettingsSection.Content.PreviewBadge, @"<div id=""umbracoPreviewBadge"" class=""umbraco-preview-badge""><span class=""umbraco-preview-badge__header"">Preview mode</span><a href=""{0}/preview/end?redir={1}"" class=""umbraco-preview-badge__end""><svg viewBox=""0 0 100 100"" xmlns=""http://www.w3.org/2000/svg""><title>Click to end</title><path d=""M5273.1 2400.1v-2c0-2.8-5-4-9.7-4s-9.7 1.3-9.7 4v2a7 7 0 002 4.9l5 4.9c.3.3.4.6.4 1v6.4c0 .4.2.7.6.8l2.9.9c.5.1 1-.2 1-.8v-7.2c0-.4.2-.7.4-1l5.1-5a7 7 0 002-4.9zm-9.7-.1c-4.8 0-7.4-1.3-7.5-1.8.1-.5 2.7-1.8 7.5-1.8s7.3 1.3 7.5 1.8c-.2.5-2.7 1.8-7.5 1.8z""/><path d=""M5268.4 2410.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1s-.4-1-1-1h-4.3zM5272.7 2413.7h-4.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1s-.4-1-1-1zM5272.7 2417h-4.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1 0-.5-.4-1-1-1z""/><path d=""M78.2 13l-8.7 11.7a32.5 32.5 0 11-51.9 25.8c0-10.3 4.7-19.7 12.9-25.8L21.8 13a47 47 0 1056.4 0z""/><path d=""M42.7 2.5h14.6v49.4H42.7z""/></svg></a></div><style type=""text/css"">.umbraco-preview-badge {{position: absolute;top: 1em;right: 1em;display: inline-flex;background: #1b264f;color: #fff;padding: 1em;font-size: 12px;z-index: 99999999;justify-content: center;align-items: center;box-shadow: 0 10px 50px rgba(0, 0, 0, .1), 0 6px 20px rgba(0, 0, 0, .16);line-height: 1;}}.umbraco-preview-badge__header {{font-weight: bold;}}.umbraco-preview-badge__end {{width: 3em;padding: 1em;margin: -1em -1em -1em 2em;display: flex;flex-shrink: 0;align-items: center;align-self: stretch;}}.umbraco-preview-badge__end:hover,.umbraco-preview-badge__end:focus {{background: #f5c1bc;}}.umbraco-preview-badge__end svg {{fill: #fff;width:1em;}}</style>");
}
[Test]
public void ResolveUrlsFromTextString()
@@ -41,9 +41,7 @@
</notifications>
<PreviewBadge>
<![CDATA[
<a id="umbracoPreviewBadge" style="z-index:99999; position: absolute; top: 0; right: 0; border: 0; width: 149px; height: 149px; background: url('{0}/assets/img/preview-mode-badge.png') no-repeat;" href="#" OnClick="javascript:window.top.location.href = '{0}/preview/end?redir={1}'"><span style="display:none;">In Preview Mode - click to end</span></a>
]]></PreviewBadge>
<![CDATA[<div id="umbracoPreviewBadge" class="umbraco-preview-badge"><span class="umbraco-preview-badge__header">Preview mode</span><a href="{0}/preview/end?redir={1}" class="umbraco-preview-badge__end"><svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"><title>Click to end</title><path d="M5273.1 2400.1v-2c0-2.8-5-4-9.7-4s-9.7 1.3-9.7 4v2a7 7 0 002 4.9l5 4.9c.3.3.4.6.4 1v6.4c0 .4.2.7.6.8l2.9.9c.5.1 1-.2 1-.8v-7.2c0-.4.2-.7.4-1l5.1-5a7 7 0 002-4.9zm-9.7-.1c-4.8 0-7.4-1.3-7.5-1.8.1-.5 2.7-1.8 7.5-1.8s7.3 1.3 7.5 1.8c-.2.5-2.7 1.8-7.5 1.8z"/><path d="M5268.4 2410.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1s-.4-1-1-1h-4.3zM5272.7 2413.7h-4.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1s-.4-1-1-1zM5272.7 2417h-4.3c-.6 0-1 .4-1 1s.4 1 1 1h4.3c.6 0 1-.4 1-1 0-.5-.4-1-1-1z"/><path d="M78.2 13l-8.7 11.7a32.5 32.5 0 11-51.9 25.8c0-10.3 4.7-19.7 12.9-25.8L21.8 13a47 47 0 1056.4 0z"/><path d="M42.7 2.5h14.6v49.4H42.7z"/></svg></a></div><style type="text/css">.umbraco-preview-badge {{position: absolute;top: 1em;right: 1em;display: inline-flex;background: #1b264f;color: #fff;padding: 1em;font-size: 12px;z-index: 99999999;justify-content: center;align-items: center;box-shadow: 0 10px 50px rgba(0, 0, 0, .1), 0 6px 20px rgba(0, 0, 0, .16);line-height: 1;}}.umbraco-preview-badge__header {{font-weight: bold;}}.umbraco-preview-badge__end {{width: 3em;padding: 1em;margin: -1em -1em -1em 2em;display: flex;flex-shrink: 0;align-items: center;align-self: stretch;}}.umbraco-preview-badge__end:hover,.umbraco-preview-badge__end:focus {{background: #f5c1bc;}}.umbraco-preview-badge__end svg {{fill: #fff;width:1em;}}</style>]]></PreviewBadge>
<!-- How Umbraco should handle errors during macro execution. Can be one of the following values:
- inline - show an inline error within the macro but allow the page to continue rendering. Historial Umbraco behaviour.
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NUnit.Framework;
@@ -8,6 +9,8 @@ namespace Umbraco.Tests.Testing
{
public abstract class TestOptionAttributeBase : Attribute
{
public static readonly List<Assembly> ScanAssemblies = new List<Assembly>();
public static TOptions GetTestOptions<TOptions>(MethodInfo method)
where TOptions : TestOptionAttributeBase, new()
{
@@ -28,9 +31,18 @@ namespace Umbraco.Tests.Testing
var test = TestContext.CurrentContext.Test;
var typeName = test.ClassName;
var methodName = test.MethodName;
var type = Type.GetType(typeName, true);
var type = Type.GetType(typeName, false);
if (type == null)
throw new PanicException($"Could not resolve the type from type name {typeName}"); // makes no sense
{
type = ScanAssemblies
.Select(assembly => assembly.GetType(typeName, false))
.FirstOrDefault(x => x != null);
if (type == null)
{
throw new PanicException($"Could not resolve the running test fixture from type name {typeName}.\n" +
$"To use base classes from Umbraco.Tests, add your test assembly to TestOptionAttributeBase.ScanAssemblies");
}
}
var methodInfo = type.GetMethod(methodName); // what about overloads?
var options = GetTestOptions<TOptions>(methodInfo);
return options;
@@ -7,10 +7,13 @@ using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Composing;
using Umbraco.Core.Configuration;
using Umbraco.Core.Dictionary;
using Umbraco.Core.Logging;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Tests.TestHelpers;
using Umbraco.Tests.TestHelpers.Stubs;
@@ -19,6 +22,7 @@ using Umbraco.Web;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using Umbraco.Web.Security;
using Umbraco.Web.WebApi;
using Current = Umbraco.Web.Composing.Current;
namespace Umbraco.Tests.Testing.TestingTests
@@ -88,5 +92,26 @@ namespace Umbraco.Tests.Testing.TestingTests
Assert.AreEqual("/hello/world/1234", theUrlProvider.GetUrl(publishedContent));
}
[Test]
public void Can_Mock_UmbracoApiController_Dependencies_With_Injected_UmbracoMapper()
{
var umbracoContext = TestObjects.GetUmbracoContextMock();
var membershipHelper = new MembershipHelper(umbracoContext.HttpContext, Mock.Of<IPublishedMemberCache>(), Mock.Of<MembershipProvider>(), Mock.Of<RoleProvider>(), Mock.Of<IMemberService>(), Mock.Of<IMemberTypeService>(), Mock.Of<IUserService>(), Mock.Of<IPublicAccessService>(), Mock.Of<AppCaches>(), Mock.Of<ILogger>());
var umbracoHelper = new UmbracoHelper(Mock.Of<IPublishedContent>(), Mock.Of<ITagQuery>(), Mock.Of<ICultureDictionaryFactory>(), Mock.Of<IUmbracoComponentRenderer>(), Mock.Of<IPublishedContentQuery>(), membershipHelper);
var umbracoMapper = new UmbracoMapper(new MapDefinitionCollection(new[] { Mock.Of<IMapDefinition>() }));
// ReSharper disable once UnusedVariable
var umbracoApiController = new FakeUmbracoApiController(Mock.Of<IGlobalSettings>(), Mock.Of<IUmbracoContextAccessor>(), Mock.Of<ISqlContext>(), ServiceContext.CreatePartial(), AppCaches.NoCache, Mock.Of<IProfilingLogger>(), Mock.Of<IRuntimeState>(), umbracoHelper, umbracoMapper);
Assert.Pass();
}
}
internal class FakeUmbracoApiController : UmbracoApiController
{
public FakeUmbracoApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper) { }
public FakeUmbracoApiController(IGlobalSettings globalSettings, IUmbracoContextAccessor umbracoContextAccessor, ISqlContext sqlContext, ServiceContext services, AppCaches appCaches, IProfilingLogger logger, IRuntimeState runtimeState, UmbracoHelper umbracoHelper, UmbracoMapper umbracoMapper) : base(globalSettings, umbracoContextAccessor, sqlContext, services, appCaches, logger, runtimeState, umbracoHelper, umbracoMapper) { }
}
}
+21 -18
View File
@@ -3,26 +3,26 @@
module.exports = {
sources: {
//less files used by backoffice and preview
//processed in the less task
// less files used by backoffice and preview
// processed in the less task
less: {
installer: { files: ["./src/less/installer.less"], out: "installer.css" },
nonodes: { files: ["./src/less/pages/nonodes.less"], out: "nonodes.style.min.css"},
preview: { files: ["./src/less/canvas-designer.less"], out: "canvasdesigner.css" },
umbraco: { files: ["./src/less/belle.less"], out: "umbraco.css" },
rteContent: { files: ["./src/less/rte-content.less"], out: "rte-content.css" }
installer: { files: "./src/less/installer.less", watch: "./src/less/**/*.less", out: "installer.css" },
nonodes: { files: "./src/less/pages/nonodes.less", watch: "./src/less/**/*.less", out: "nonodes.style.min.css"},
preview: { files: "./src/less/canvas-designer.less", watch: "./src/less/**/*.less", out: "canvasdesigner.css" },
umbraco: { files: "./src/less/belle.less", watch: "./src/less/**/*.less", out: "umbraco.css" },
rteContent: { files: "./src/less/rte-content.less", watch: "./src/less/**/*.less", out: "rte-content.css" }
},
//js files for backoffie
//processed in the js task
// js files for backoffice
// processed in the js task
js: {
preview: { files: ["./src/preview/**/*.js"], out: "umbraco.preview.js" },
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" },
security: { files: ["./src/common/interceptors/**/*.js"], out: "umbraco.interceptors.js" },
preview: { files: "./src/preview/**/*.js", out: "umbraco.preview.js" },
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" },
security: { files: "./src/common/interceptors/**/*.js", out: "umbraco.interceptors.js" },
//the controllers for views
controllers: {
files: [
@@ -42,13 +42,16 @@ module.exports = {
],
out: "umbraco.directives.js"
}
},
//selectors for copying all views into the build
//processed in the views task
views:{
umbraco: {files: ["./src/views/**/*.html"], folder: ""},
installer: {files: ["./src/installer/steps/*.html"], folder: "install/"}
views: {files: "./src/views/**/*.html", folder: ""},
directives: {files: "./src/common/directives/**/*.html", folder: ""},
components: {files: "./src/common/components/**/*.html", folder: ""},
installer: {files: "./src/installer/steps/*.html", folder: "install/"}
},
//globs for file-watching
-10
View File
@@ -1,10 +0,0 @@
'use strict';
var fs = require('fs');
var onlyScripts = require('./util/scriptFilter');
var tasks = fs.readdirSync('./gulp/tasks/').filter(onlyScripts);
tasks.forEach(function(task) {
require('./tasks/' + task);
});
@@ -1,10 +0,0 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var runSequence = require('run-sequence');
// Build - build the files ready for production
gulp.task('build', function(cb) {
runSequence(["js", "dependencies", "less", "views"], "test:unit", cb);
});
@@ -10,7 +10,7 @@ var imagemin = require('gulp-imagemin');
/**************************
* Task processes and copies all dependencies, either installed by npm or stored locally in the project
**************************/
gulp.task('dependencies', function () {
function dependencies() {
//as we do multiple things in this task, we merge the multiple streams
var stream = new MergeStream();
@@ -244,21 +244,21 @@ gulp.task('dependencies', function () {
nodeModules.forEach(module => {
stream.add(
gulp.src(module.src,
{ base: module.base })
{ base: module.base, allowEmpty: true })
.pipe(gulp.dest(config.root + config.targets.lib + "/" + module.name))
);
});
//copy over libs which are not on npm (/lib)
stream.add(
gulp.src(config.sources.globs.lib)
gulp.src(config.sources.globs.lib, { allowEmpty: true })
.pipe(gulp.dest(config.root + config.targets.lib))
);
//Copies all static assets into /root / assets folder
//css, fonts and image files
var assetsTask = gulp.src(config.sources.globs.assets);
var assetsTask = gulp.src(config.sources.globs.assets, { allowEmpty: true });
if (global.isProd === true) {
assetsTask = assetsTask.pipe(imagemin([
imagemin.gifsicle({interlaced: true}),
@@ -277,21 +277,23 @@ gulp.task('dependencies', function () {
stream.add(assetsTask);
// Copies all the less files related to the preview into their folder
//these are not pre-processed as preview has its own less combiler client side
//these are not pre-processed as preview has its own less compiler client side
stream.add(
gulp.src("src/canvasdesigner/editors/*.less")
gulp.src("src/canvasdesigner/editors/*.less", { allowEmpty: true })
.pipe(gulp.dest(config.root + config.targets.assets + "/less"))
);
// Todo: check if we need these fileSize
// TODO: check if we need these fileSize
stream.add(
gulp.src("src/views/propertyeditors/grid/config/*.*")
gulp.src("src/views/propertyeditors/grid/config/*.*", { allowEmpty: true })
.pipe(gulp.dest(config.root + config.targets.views + "/propertyeditors/grid/config"))
);
stream.add(
gulp.src("src/views/dashboard/default/*.jpg")
gulp.src("src/views/dashboard/default/*.jpg", { allowEmpty: true })
.pipe(gulp.dest(config.root + config.targets.views + "/dashboard/default"))
);
return stream;
});
};
module.exports = { dependencies: dependencies };
@@ -1,13 +0,0 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var runSequence = require('run-sequence');
// Dev - build the files ready for development and start watchers
gulp.task('dev', function (cb) {
global.isProd = false;
runSequence(["dependencies", "js", "less", "views"], "watch", cb);
});
+12 -9
View File
@@ -9,21 +9,24 @@ var MergeStream = require('merge-stream');
var processJs = require('../util/processJs');
/**************************
* Copies all angular JS files into their seperate umbraco.*.js file
* Copies all angular JS files into their separate umbraco.*.js file
**************************/
gulp.task('js', function () {
function js() {
//we run multiple streams, so merge them all together
var stream = new MergeStream();
stream.add(
gulp.src(config.sources.globs.js)
.pipe(gulp.dest(config.root + config.targets.js))
);
gulp.src(config.sources.globs.js).pipe( gulp.dest(config.root + config.targets.js) )
);
_.forEach(config.sources.js, function (group) {
stream.add (processJs(group.files, group.out) );
});
_.forEach(config.sources.js, function (group) {
stream.add(
processJs(group.files, group.out)
);
});
return stream;
});
};
module.exports = { js: js };
+4 -3
View File
@@ -1,14 +1,13 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var _ = require('lodash');
var MergeStream = require('merge-stream');
var processLess = require('../util/processLess');
gulp.task('less', function () {
function less() {
var stream = new MergeStream();
@@ -17,4 +16,6 @@ gulp.task('less', function () {
});
return stream;
});
};
module.exports = { less: less };
@@ -0,0 +1,22 @@
'use strict';
var gulp = require('gulp');
var through2 = require('through2');
function createEmptyStream() {
var pass = through2.obj();
process.nextTick(pass.end.bind(pass));
return pass;
}
/**************************
* Copies all angular JS files into their separate umbraco.*.js file
**************************/
function removeProductionMode() {
global.isProd = false;
return createEmptyStream();
};
module.exports = { removeProductionMode: removeProductionMode };
+8 -13
View File
@@ -1,32 +1,27 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var karmaServer = require('karma').Server;
var runSequence = require('run-sequence');
/**************************
* Build tests
**************************/
// Karma test
gulp.task('runTests', function(cb) {
runSequence("js", "test:unit", cb);
});
function testUnit() {
gulp.task('test:unit', function () {
new karmaServer({
return new karmaServer({
configFile: __dirname + "/../../test/config/karma.conf.js",
keepalive: true
})
.start();
});
};
gulp.task('test:e2e', function() {
new karmaServer({
function testE2e() {
return new karmaServer({
configFile: __dirname + "/../../test/config/e2e.js",
keepalive: true
})
.start();
});
};
module.exports = { testUnit: testUnit, testE2e: testE2e };
@@ -6,7 +6,7 @@ var gulp = require('gulp');
var _ = require('lodash');
var MergeStream = require('merge-stream');
gulp.task('views', function () {
function views() {
var stream = new MergeStream();
@@ -22,4 +22,7 @@ gulp.task('views', function () {
});
return stream;
});
};
module.exports = { views: views };
@@ -1,58 +0,0 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var _ = require('lodash');
var MergeStream = require('merge-stream');
var processJs = require('../util/processJs');
var watch = require('gulp-watch');
gulp.task('watch', function () {
var stream = new MergeStream();
var watchInterval = 500;
//Setup a watcher for all groups of javascript files
_.forEach(config.sources.js, function (group) {
if(group.watch !== false){
stream.add(
watch(group.files, { ignoreInitial: true, interval: watchInterval }, function (file) {
console.info(file.path + " has changed, added to: " + group.out);
processJs(group.files, group.out);
})
);
}
});
stream.add(
//watch all less files and trigger the less task
watch(config.sources.globs.less, { ignoreInitial: true, interval: watchInterval }, function () {
gulp.run(['less']);
})
);
//watch all views - copy single file changes
stream.add(
watch(config.sources.globs.views, { interval: watchInterval })
.pipe(gulp.dest(config.root + config.targets.views))
);
//watch all app js files that will not be merged - copy single file changes
stream.add(
watch(config.sources.globs.js, { interval: watchInterval })
.pipe(gulp.dest(config.root + config.targets.js))
);
return stream;
});
@@ -0,0 +1,49 @@
'use strict';
const config = require('../config');
const {watch, parallel, dest, src} = require('gulp');
var _ = require('lodash');
var MergeStream = require('merge-stream');
var processJs = require('../util/processJs');
var processLess = require('../util/processLess');
//const { less } = require('./less');
//const { views } = require('./views');
function watchTask(cb) {
var watchInterval = 500;
//Setup a watcher for all groups of JS files
_.forEach(config.sources.js, function (group) {
if(group.watch !== false) {
watch(group.files, { ignoreInitial: true, interval: watchInterval }, function JS_Group_Compile() { return processJs(group.files, group.out) });
}
});
//Setup a watcher for all groups of LESS files
_.forEach(config.sources.less, function (group) {
if(group.watch !== false) {
watch(group.watch, { ignoreInitial: true, interval: watchInterval }, function Less_Group_Compile() { processLess(group.files, group.out) });
}
});
//Setup a watcher for all groups of view files
var viewWatcher;
_.forEach(config.sources.views, function (group) {
if(group.watch !== false) {
viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval });
viewWatcher.on('change', function(path, stats) {
console.log("copying " + group.files + " to " + config.root + config.targets.views + group.folder);
src(group.files).pipe( dest(config.root + config.targets.views + group.folder) )
});
}
});
return cb();
};
module.exports = { watchTask: watchTask };
@@ -11,6 +11,8 @@ var embedTemplates = require('gulp-angular-embed-templates');
module.exports = function (files, out) {
console.log("JS: ", files, " -> ", config.root + config.targets.js + out)
var task = gulp.src(files);
// check for js errors
@@ -15,7 +15,9 @@ module.exports = function(files, out) {
autoprefixer,
cssnano({zindex: false})
];
console.log("LESS: ", files, " -> ", config.root + config.targets.js + out)
var task = gulp.src(files)
.pipe(less())
.pipe(cleanCss())
+43 -10
View File
@@ -1,16 +1,49 @@
'use strict';
/*
* gulpfile.js
* ===========
* Rather than manage one giant configuration file responsible
* for creating multiple tasks, each task has been broken out into
* its own file in gulp/tasks. Any file in that folder gets automatically
* required by the loop in ./gulp/index.js (required below).
*
* To add a new task, simply add a new task file to gulp/tasks.
*/
* gulpfile.js
* ===========
* This is now using Gulp 4, each child task is now a child function in its own corresponding file.
*
* To add a new task, simply add a new task file to gulp/tasks folder, add a require statement below to include the one or more methods
* and then add the exports command to add the new item into the task menu.
*/
global.isProd = true;
require('./gulp');
const { src, dest, series, parallel, lastRun } = require('gulp');
const { dependencies } = require('./gulp/tasks/dependencies');
const { js } = require('./gulp/tasks/js');
const { less } = require('./gulp/tasks/less');
const { testE2e, testUnit } = require('./gulp/tasks/test');
const { views } = require('./gulp/tasks/views');
const { watchTask } = require('./gulp/tasks/watchTask');
const { removeProductionMode } = require('./gulp/tasks/removeProductionMode');
// Load local overwrites, can be used to overwrite paths in your local setup.
var fs = require('fs');
var onlyScripts = require('./gulp/util/scriptFilter');
try {
if (fs.existsSync('./gulp/overwrites/')) {
var overwrites = fs.readdirSync('./gulp/overwrites/').filter(onlyScripts);
overwrites.forEach(function(overwrite) {
require('./gulp/overwrites/' + overwrite);
});
}
} catch (err) {
console.error(err)
}
// ***********************************************************
// These Exports are the new way of defining Tasks in Gulp 4.x
// ***********************************************************
exports.build = series(parallel(dependencies, js, less, views), testUnit);
exports.dev = series(parallel(dependencies, js, less, views), watchTask);
exports.fastdev = series(removeProductionMode, parallel(dependencies, js, less, views), watchTask);
exports.watch = series(watchTask);
//
exports.runTests = series(js, testUnit);
exports.testUnit = series(testUnit);
exports.testE2e = series(testE2e);
+2572 -2774
View File
File diff suppressed because it is too large Load Diff
+84 -80
View File
@@ -1,80 +1,84 @@
{
"private": true,
"scripts": {
"test": "karma start test/config/karma.conf.js --singlerun",
"build": "gulp build",
"dev": "gulp dev",
"fastdev": "gulp fastdev"
},
"dependencies": {
"ace-builds": "1.4.2",
"angular": "1.7.5",
"angular-animate": "1.7.5",
"angular-aria": "1.7.5",
"angular-chart.js": "^1.1.1",
"angular-cookies": "1.7.5",
"angular-dynamic-locale": "0.1.37",
"angular-i18n": "1.7.5",
"angular-local-storage": "0.7.1",
"angular-messages": "1.7.5",
"angular-mocks": "1.7.5",
"angular-route": "1.7.5",
"angular-sanitize": "1.7.5",
"angular-touch": "1.7.5",
"angular-ui-sortable": "0.19.0",
"animejs": "2.2.0",
"bootstrap-social": "5.1.1",
"chart.js": "^2.7.3",
"clipboard": "2.0.4",
"diff": "3.5.0",
"flatpickr": "4.5.2",
"font-awesome": "4.7.0",
"jquery": "^3.4.0",
"jquery-ui-dist": "1.12.1",
"jquery-ui-touch-punch": "0.2.3",
"lazyload-js": "1.0.0",
"moment": "2.22.2",
"ng-file-upload": "12.2.13",
"nouislider": "14.0.2",
"npm": "^6.4.1",
"signalr": "2.4.0",
"spectrum-colorpicker": "1.8.0",
"tinymce": "4.9.2",
"typeahead.js": "0.11.1",
"underscore": "1.9.1"
},
"devDependencies": {
"@babel/core": "7.1.6",
"@babel/preset-env": "7.1.6",
"autoprefixer": "9.3.1",
"cssnano": "4.1.7",
"fs": "0.0.2",
"gulp": "^3.9.1",
"gulp-angular-embed-templates": "^2.3.0",
"gulp-babel": "8.0.0",
"gulp-clean-css": "4.0.0",
"gulp-cli": "^2.0.1",
"gulp-concat": "2.6.1",
"gulp-eslint": "^5.0.0",
"gulp-imagemin": "^4.1.0",
"gulp-less": "4.0.1",
"gulp-notify": "^3.0.0",
"gulp-postcss": "8.0.0",
"gulp-rename": "1.4.0",
"gulp-sort": "2.0.0",
"gulp-watch": "5.0.1",
"gulp-wrap": "0.14.0",
"gulp-wrap-js": "0.4.1",
"jasmine-core": "3.3.0",
"karma": "3.1.1",
"karma-jasmine": "2.0.1",
"karma-junit-reporter": "^1.2.0",
"karma-phantomjs-launcher": "1.0.4",
"less": "3.9.0",
"lodash": "4.17.13",
"marked": "^0.6.1",
"merge-stream": "1.0.1",
"run-sequence": "2.2.1",
"caniuse-lite": "^1.0.30000966"
}
}
{
"private": true,
"scripts": {
"test": "gulp runTests",
"unit": "gulp testUnit",
"e2e": "gulp testE2e",
"build": "gulp build",
"dev": "gulp dev",
"fastdev": "gulp fastdev",
"watch": "gulp watch"
},
"dependencies": {
"ace-builds": "1.4.2",
"angular": "1.7.5",
"angular-animate": "1.7.5",
"angular-aria": "1.7.5",
"angular-chart.js": "^1.1.1",
"angular-cookies": "1.7.5",
"angular-dynamic-locale": "0.1.37",
"angular-i18n": "1.7.5",
"angular-local-storage": "0.7.1",
"angular-messages": "1.7.5",
"angular-mocks": "1.7.5",
"angular-route": "1.7.5",
"angular-sanitize": "1.7.5",
"angular-touch": "1.7.5",
"angular-ui-sortable": "0.19.0",
"animejs": "2.2.0",
"bootstrap-social": "5.1.1",
"chart.js": "^2.8.0",
"clipboard": "2.0.4",
"diff": "3.5.0",
"flatpickr": "4.5.2",
"font-awesome": "4.7.0",
"jquery": "^3.4.1",
"jquery-ui-dist": "1.12.1",
"jquery-ui-touch-punch": "0.2.3",
"lazyload-js": "1.0.0",
"moment": "2.22.2",
"ng-file-upload": "12.2.13",
"nouislider": "14.0.2",
"npm": "6.12.0",
"signalr": "2.4.0",
"spectrum-colorpicker": "1.8.0",
"tinymce": "4.9.2",
"typeahead.js": "0.11.1",
"underscore": "1.9.1"
},
"devDependencies": {
"@babel/core": "7.6.4",
"@babel/preset-env": "7.6.3",
"autoprefixer": "9.6.5",
"caniuse-lite": "^1.0.30001002",
"cssnano": "4.1.10",
"fs": "0.0.2",
"gulp": "4.0.2",
"gulp-angular-embed-templates": "^2.3.0",
"gulp-babel": "8.0.0",
"gulp-clean-css": "4.2.0",
"gulp-cli": "^2.2.0",
"gulp-concat": "2.6.1",
"gulp-eslint": "6.0.0",
"gulp-imagemin": "6.1.1",
"gulp-less": "4.0.1",
"gulp-notify": "^3.0.0",
"gulp-postcss": "8.0.0",
"gulp-rename": "1.4.0",
"gulp-sort": "2.0.0",
"gulp-watch": "5.0.1",
"gulp-wrap": "0.15.0",
"gulp-wrap-js": "0.4.1",
"jasmine-core": "3.5.0",
"karma": "4.4.1",
"karma-jasmine": "2.0.1",
"karma-junit-reporter": "2.0.1",
"karma-phantomjs-launcher": "1.0.4",
"karma-spec-reporter": "0.0.32",
"less": "3.10.3",
"lodash": "4.17.15",
"marked": "^0.7.0",
"merge-stream": "2.0.0",
"run-sequence": "2.2.1"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

@@ -26,6 +26,7 @@
@param {string} value Set the value of the radiobutton.
@param {string} name Set the name of the radiobutton.
@param {string} text Set the text for the radiobutton label.
@param {string} labelKey Set a dictinary/localization string for the checkbox label
@param {boolean} disabled Set the radiobutton to be disabled.
@param {boolean} required Set the radiobutton to be required.
@param {callback} onChange Callback when the value of the radiobutton change by interaction.
@@ -39,15 +40,27 @@
var vm = this;
vm.$onInit = onInit;
vm.change = change;
function onInit() {
// If a labelKey is passed let's update the returned text if it's does not contain an opening square bracket [
if (vm.labelKey) {
localizationService.localize(vm.labelKey).then(function (data) {
if(data.indexOf('[') === -1){
vm.text = data;
}
});
}
}
function change() {
if (vm.onChange) {
$timeout(function () {
vm.onChange({ model: vm.model, value: vm.value });
}, 0);
}
}
}
}
var component = {
@@ -60,6 +73,7 @@
value: "@",
name: "@",
text: "@",
labelKey: "@?",
disabled: "<",
required: "<",
onChange: "&?"
@@ -1,5 +1,4 @@
/**
@ngdoc directive
@name umbraco.directives.directive:umbOverlay*
@deprecated
@restrict E
@@ -34,8 +34,9 @@ angular.module("umbraco.directives")
},
link: function (scope, element, attrs, umbTreeCtrl) {
localizationService.localize("general_search").then(function (value) {
scope.searchAltText = value;
localizationService.localizeMany(["general_search", "visuallyHiddenTexts_openContextMenu"]).then(function (value) {
scope.searchAltText = value[0];
scope.optionsText = value[1];
});
// updates the node's DOM/styles
@@ -131,13 +131,24 @@ Use this directive to render a date time picker
return console.warn('Unable to find any flatpickr installation');
}
var fpInstance;
setUpCallbacks();
if (!ctrl.options.locale) {
ctrl.options.locale = userLocale;
}
var fpInstance = new fpLib(element, ctrl.options);
// handle special keydown events
ctrl.options.onKeyDown = function (selectedDates, dateStr, instance, event) {
var code = event.keyCode || event.which;
if (code === 13) {
// close the datepicker on enter (this happens when entering time)
fpInstance.close()
}
};
fpInstance = new fpLib(element, ctrl.options);
if (ctrl.onSetup) {
ctrl.onSetup({
@@ -419,7 +419,7 @@
};
scope.canRemoveGroup = function(group){
return _.find(group.properties, function(property) { return property.locked === true; }) == null;
return group.inherited !== true && _.find(group.properties, function(property) { return property.locked === true; }) == null;
}
scope.removeGroup = function(groupIndex) {
@@ -475,6 +475,23 @@
/* ---------- PROPERTIES ---------- */
scope.addPropertyToActiveGroup = function () {
var group = _.find(scope.model.groups, group => group.tabState === "active");
if (!group && scope.model.groups.length) {
group = scope.model.groups[0];
}
if (!group || !group.name) {
return;
}
var property = _.find(group.properties, property => property.propertyState === "init");
if (!property) {
return;
}
scope.addProperty(property, group);
}
scope.addProperty = function(property, group) {
// set property sort order
@@ -1,161 +1,143 @@
(function() {
'use strict';
(function () {
'use strict';
function ListViewSettingsDirective(dataTypeResource, dataTypeHelper, listViewPrevalueHelper) {
function ListViewSettingsDirective(dataTypeResource, dataTypeHelper, editorService, listViewPrevalueHelper) {
function link(scope) {
function link(scope) {
scope.dataType = {};
scope.editDataTypeSettings = false;
scope.customListViewCreated = false;
scope.dataType = {};
scope.customListViewCreated = false;
const checkForCustomListView = () => scope.dataType.name === "List View - " + scope.modelAlias;
/* ---------- INIT ---------- */
/* ---------- INIT ---------- */
function activate() {
const activate = () => {
if(scope.enableListView) {
if (scope.enableListView) {
dataTypeResource.getByName(scope.listViewName)
.then(function(dataType) {
dataTypeResource.getByName(scope.listViewName)
.then(dataType => {
scope.dataType = dataType;
scope.dataType = dataType;
listViewPrevalueHelper.setPrevalues(dataType.preValues);
scope.customListViewCreated = checkForCustomListView();
});
} else {
scope.dataType = {};
}
}
/* ----------- LIST VIEW SETTINGS --------- */
const showSettingsOverlay = () => {
const overlay = {
view: 'views/components/umb-list-view-settings-overlay.html',
hideDescription: true,
hideIcon: true,
size: 'medium',
dataType: scope.dataType,
title: 'List view settings',
submit: model => {
const preValues = dataTypeHelper.createPreValueProps(model.dataType.preValues);
// store data type
dataTypeResource.save(model.dataType, preValues, false)
.then(dataType => scope.dataType = dataType);
editorService.close();
},
close: () => editorService.close()
};
editorService.open(overlay);
};
/* ---------- CUSTOM LIST VIEW ---------- */
scope.createCustomListViewDataType = () => {
scope.loading = true;
dataTypeResource.createCustomListView(scope.modelAlias).then(dataType => {
// store data type
scope.dataType = dataType;
// set list view name on scope
scope.listViewName = dataType.name;
// change state to custom list view
scope.customListViewCreated = true;
// show settings overlay
showSettingsOverlay();
scope.loading = false;
});
};
scope.removeCustomListDataType = () => {
scope.loading = true;
// delete custom list view data type
dataTypeResource.deleteById(scope.dataType.id).then(dataType => {
// set list view name on scope
scope.listViewName = `List View - ${scope.contentType === 'documentType' ? 'Content' : 'Media'}`;
// get default data type
dataTypeResource.getByName(scope.listViewName)
.then(defaultDataType => {
// store data type
scope.dataType = defaultDataType;
// change state to default list view
scope.customListViewCreated = false;
scope.loading = false;
});
});
};
scope.toggle = () => scope.enableListView = !scope.enableListView;
scope.showSettingsOverlay = () => showSettingsOverlay();
/* ----------- SCOPE WATCHERS ----------- */
const unbindEnableListViewWatcher = scope.$watch('enableListView', newValue => {
if (newValue !== undefined) {
activate();
}
listViewPrevalueHelper.setPrevalues(dataType.preValues);
scope.customListViewCreated = checkForCustomListView();
});
} else {
scope.dataType = {};
// clean up
scope.$on('$destroy', () => unbindEnableListViewWatcher());
}
}
/* ----------- LIST VIEW SETTINGS --------- */
scope.toggleEditListViewDataTypeSettings = function() {
scope.editDataTypeSettings = !scope.editDataTypeSettings;
};
scope.saveListViewDataType = function() {
var preValues = dataTypeHelper.createPreValueProps(scope.dataType.preValues);
dataTypeResource.save(scope.dataType, preValues, false).then(function(dataType) {
// store data type
scope.dataType = dataType;
// hide settings panel
scope.editDataTypeSettings = false;
});
};
/* ---------- CUSTOM LIST VIEW ---------- */
scope.createCustomListViewDataType = function() {
dataTypeResource.createCustomListView(scope.modelAlias).then(function(dataType) {
// store data type
scope.dataType = dataType;
// set list view name on scope
scope.listViewName = dataType.name;
// change state to custom list view
scope.customListViewCreated = true;
// show settings panel
scope.editDataTypeSettings = true;
});
};
scope.removeCustomListDataType = function() {
scope.editDataTypeSettings = false;
// delete custom list view data type
dataTypeResource.deleteById(scope.dataType.id).then(function(dataType) {
// set list view name on scope
if(scope.contentType === "documentType") {
scope.listViewName = "List View - Content";
} else if(scope.contentType === "mediaType") {
scope.listViewName = "List View - Media";
}
// get default data type
dataTypeResource.getByName(scope.listViewName)
.then(function(dataType) {
// store data type
scope.dataType = dataType;
// change state to default list view
scope.customListViewCreated = false;
});
});
};
scope.toggle = function(){
if(scope.enableListView){
scope.enableListView = false;
return;
}
scope.enableListView = true;
};
/* ----------- SCOPE WATCHERS ----------- */
var unbindEnableListViewWatcher = scope.$watch('enableListView', function(newValue){
if(newValue !== undefined) {
activate();
}
});
// clean up
scope.$on('$destroy', function(){
unbindEnableListViewWatcher();
});
/* ----------- METHODS ---------- */
function checkForCustomListView() {
return scope.dataType.name === "List View - " + scope.modelAlias;
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-list-view-settings.html',
scope: {
enableListView: "=",
listViewName: "=",
modelAlias: "=",
contentType: "@"
},
link: link
};
return directive;
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-list-view-settings.html',
scope: {
enableListView: "=",
listViewName: "=",
modelAlias: "=",
contentType: "@"
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbListViewSettings', ListViewSettingsDirective);
angular.module('umbraco.directives').directive('umbListViewSettings', ListViewSettingsDirective);
})();
@@ -17,9 +17,9 @@ angular.module("umbraco.directives")
var observer = new MutationObserver(domChange);
// Options for the observer (which mutations to observe)
var config = { attributes: true, childList: true, subtree: false };
var config = { attributes: true, childList: true, subtree: true };
function domChange(mutationsList, observer){
function domChange(mutationsList, observer) {
for(var mutation of mutationsList) {
//DOM items have been added or removed
@@ -43,4 +43,4 @@ angular.module("umbraco.directives")
}
};
});
});
@@ -251,7 +251,7 @@ When building a custom infinite editor view you can use the same components as a
*
* @param {Object} editor rendering options
* @param {String} editor.view Path to view
* @param {String} editor.size Sets the size of the editor ("small"). If nothing is set it will use full width.
* @param {String} editor.size Sets the size of the editor ("small" || "medium"). If nothing is set it will use full width.
*/
function open(editor) {
@@ -567,6 +567,7 @@ When building a custom infinite editor view you can use the same components as a
* @param {Boolean} editor.multiPicker Pick one or multiple items
* @param {Boolean} editor.onlyImages Only display files that have an image file-extension
* @param {Boolean} editor.disableFolderSelect Disable folder selection
* @param {Boolean} editor.disableFocalPoint Disable focal point editor for selected media
* @param {Array} editor.updatedMediaNodes A list of ids for media items that have been updated through the media picker
* @param {Callback} editor.submit Submits the editor
* @param {Callback} editor.close Closes the editor
@@ -225,8 +225,10 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
retainQueryStrings: function (currRouteParams, nextRouteParams) {
var toRetain = angular.copy(nextRouteParams);
var updated = false;
_.each(retainedQueryStrings, function (r) {
if (currRouteParams[r] && !nextRouteParams[r]) {
// if mculture is set to null in nextRouteParams, the value will be undefined and we will not retain any query string that has a value of "null"
if (currRouteParams[r] && nextRouteParams[r] !== undefined && !nextRouteParams[r]) {
toRetain[r] = currRouteParams[r];
updated = true;
}
@@ -15,7 +15,8 @@
'[tabindex]',
'audio[controls]',
'video[controls]',
'[contenteditable]:not([contenteditable="false"])'
'[contenteditable]:not([contenteditable="false"])',
'iframe[data-mce-style]'
];
var candidateSelector = candidateSelectors.join(',');
@@ -1489,6 +1489,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
onlyImages: true,
showDetails: true,
disableFolderSelect: true,
disableFocalPoint: true,
startNodeId: startNodeId,
startNodeIsVirtual: startNodeIsVirtual,
dataTypeKey: args.model.dataTypeKey,
@@ -338,3 +338,31 @@ input[type="submit"].btn {
text-decoration: none;
}
}
// Icon buttons
// ------------------------------
// 31 July 19, Nathan Woulfe says: Reset styles for cases where button shows an icon only (eg edit/remove property on document type)
// This is lifted from umb-group-builder.less
.btn-icon {
border: none;
font-size: 18px;
position: relative;
cursor: pointer;
color: @ui-icon;
margin: 0;
padding: 5px 10px;
width: auto;
overflow: visible;
background: transparent;
line-height: normal;
outline: 0;
-webkit-appearance: none;
&:hover, &:focus {
color: @ui-icon-hover;
}
}
@@ -1,82 +1,86 @@
.umb-editors {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: hidden;
.absolute();
overflow: hidden;
.umb-editor {
box-shadow: 0px 0 30px 0 rgba(0,0,0,.3);
}
}
.umb-editor {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
.absolute();
background: @brownGrayLight;
z-index: @zIndexEditor;
}
.umb-editor--infiniteMode {
transform: none;
will-change: transform;
transition: transform 400ms ease-in-out;
&.moveRight {
transform: translateX(110%);
&--infiniteMode {
transform: none;
will-change: transform;
transition: transform 400ms ease-in-out;
&.umb-editor--moveRight {
transform: translateX(110%);
}
}
}
.umb-editor--outOfRange {
transform: none;
display: none;
will-change: auto;
transition: display 0s 320ms;
}
.umb-editor--level0 {
transform: none;
}
.umb-editor--level1 {
transform: translateX(60px);
}
.umb-editor--level2 {
transform: translateX(120px);
}
.umb-editor--level3 {
transform: translateX(180px);
}
.umb-editor--n1 {
right:60px;
}
.umb-editor--n2 {
right:120px;
}
.umb-editor--n3 {
right:180px;
}
// hide all infinite editors by default
// will be shown through animation
.umb-editors .umb-editor {
box-shadow: 0px 0 30px 0 rgba(0,0,0,.3);
}
.umb-editor--small {
width: 500px;
will-change: transform;
left: auto;
&--outOfRange {
transform: none;
display: none;
will-change: auto;
transition: display 0s 320ms;
}
.umb-editor-container {
max-width: 500px;
&--level0 {
transform: none;
}
}
// use a loop to build the editor levels
@iterations: 3;
@step: 60px;
.level-loop (@i) when (@i > 0) {
@x: @i * @step;
.umb-editor--level@{i} {
transform: translateX(@x);
}
.umb-editor--n@{i} {
right:@x;
}
.level-loop(@i - 1);
}
.level-loop(@iterations);
// and also use a loop to build editor sizes - easily extended with new sizes by adding to the map
@editorSizes:
small 500px,
medium 800px;
.create-editor-sizes(@iterator:1) when(@iterator <= length(@editorSizes)) {
.umb-editor {
@size: extract(extract(@editorSizes, @iterator), 1);
@value: extract(extract(@editorSizes, @iterator), 2);
&--@{size} {
width: @value;
will-change: transform;
left: auto;
.umb-editor--container {
max-width: @value;
}
}
}
.create-editor-sizes(@iterator + 1);
}
.create-editor-sizes();
.umb-editor__overlay {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
.absolute();
background: rgba(0,0,0,0.4);
z-index: @zIndexEditor;
visibility: hidden;
@@ -85,7 +89,7 @@
}
#contentcolumn > .umb-editor__overlay,
.--notInFront .umb-editor__overlay {
.umb-editor--notInFront .umb-editor__overlay {
visibility: visible;
opacity: 1;
transition: opacity 320ms 20ms, visibility 0s;
@@ -20,11 +20,11 @@
}
.umb-overlay .umb-overlay-header {
border-bottom: 1px solid @purple-l3;
border-bottom: 1px solid @gray-9;
margin-top: 0;
flex-grow: 0;
flex-shrink: 0;
padding: 20px 30px 0;
padding: 20px 20px 0;
}
.umb-overlay__section-header {
@@ -48,11 +48,11 @@
}
.umb-overlay__title {
font-size: @fontSizeLarge;
font-size: 16px;
color: @black;
line-height: 20px;
line-height: 16px;
font-weight: bold;
margin: 7px 0;
margin: 5px 0;
}
.umb-overlay__subtitle {
@@ -66,8 +66,8 @@
flex-shrink: 1;
flex-basis: auto;
position: relative;
padding: 0 30px;
margin-bottom: 10px;
padding: 20px;
background: @white;
max-height: calc(100vh - 170px);
overflow-y: auto;
}
@@ -75,11 +75,11 @@
.umb-overlay-drawer {
flex-grow: 0;
flex-shrink: 0;
flex-basis: 31px;
padding: 10px 20px;
flex-basis: 33px;
padding: 8px 20px;
margin: 0;
background: @gray-10;
border-top: 1px solid @purple-l3;
background: @white;
border-top: 1px solid @gray-9;
}
.umb-overlay-drawer.-auto-height {
@@ -140,7 +140,6 @@
.umb-overlay.umb-overlay-target .umb-overlay-drawer {
border: none;
background: transparent;
padding: 0 30px 20px;
}
/* ---------- OVERLAY RIGHT ---------- */
@@ -158,7 +157,7 @@
}
.umb-overlay.umb-overlay-right .umb-overlay-header {
flex-basis: 100px;
flex-basis: 70px;
box-sizing: border-box;
}
@@ -8,6 +8,14 @@
.umb-breadcrumbs__ancestor {
display: flex;
min-height: 25px;
}
.umb-breadcrumbs__action {
background: transparent;
border: 0 none;
padding: 0;
margin-top: -4px;
}
.umb-breadcrumbs__ancestor-link,
@@ -38,7 +46,6 @@
input.umb-breadcrumbs__add-ancestor {
height: 25px;
margin-top: -2px;
margin-left: 3px;
margin: 0 0 0 3px;
width: 100px;
}
@@ -15,7 +15,7 @@
&__text {
margin: 0 0 0 26px;
position: relative;
top: 0;
top: 1px;
user-select: none;
}
@@ -24,7 +24,7 @@
top: 0;
left: 0;
opacity: 0;
&:hover ~ .umb-form-check__state .umb-form-check__check {
border-color: @inputBorderFocus;
}
@@ -36,7 +36,7 @@
background: @ui-option-type-hover;
}
}
&:checked ~ .umb-form-check__state {
.umb-form-check__check {
// This only happens if the state has a radiobutton modifier
@@ -62,8 +62,7 @@
}
}
}
.tabbing-active &.umb-form-check--radiobutton &__input:focus ~ .umb-form-check__state .umb-form-check__check {
//outline: 2px solid @inputBorderTabFocus;
border: 2px solid @inputBorderTabFocus;
@@ -76,6 +75,11 @@
border-color: white;
}
// add spacing between when flexed/inline, equal to the width of the input
.flex & + & {
margin-left:@checkboxWidth;
}
&__state {
display: flex;
height: 18px;
@@ -367,7 +367,6 @@ input.umb-group-builder__group-title-input:disabled:hover {
overflow: visible;
background: transparent;
line-height: normal;
outline: 0;
-webkit-appearance: none;
&:hover, &:focus {
@@ -1,54 +1,76 @@
.umb-list-view-settings__box {
background: @gray-10;
border: 1px solid @gray-7;
display: flex;
animation: fadeIn 0.5s;
padding: 15px;
position: relative;
background: @gray-10;
display: flex;
flex: 1;
padding: 15px;
position: relative;
border-radius: @baseBorderRadius;
.btn-link {
font-size: 13px;
padding: 0;
}
}
.umb-list-view-settings__trigger {
margin-bottom: 20px;
}
.umb-list-view-settings__box.-open {
border-bottom: transparent;
}
.umb-list-view-settings__content {
display: flex;
margin-bottom: 20px;
}
.umb-list-view-settings__list-view-icon {
font-size: 20px;
color: @gray-7;
margin-right: 10px;
font-size: 20px;
color: @gray-7;
margin-right: 10px;
}
.umb-list-view-settings__name {
margin-right: 5px;
font-size: 14px;
font-weight: bold;
float: left;
font-size: 14px;
font-weight: bold;
}
.umb-list-view-settings__create-new {
font-size: 13px;
color: @ui-action-type;
}
.umb-list-view-settings__create-new:hover {
color: @ui-action-type-hover;
border-color: @ui-action-type-hover;
color: @ui-action-type;
}
.umb-list-view-settings__remove-new {
font-size: 13px;
color: @red;
color: @red;
}
.umb-list-view-settings__settings {
border: 1px dashed @gray-7;
border-top: none;
padding: 20px;
// display `columns displayed` table as a list-view layout
.umb-list-view-settings__overlay {
.btn {
vertical-align: top;
}
.btn-icon {
padding: 0;
}
table {
width: 100%;
}
tbody tr {
background: @gray-10;
border-bottom: 1px solid #fff;
}
th {
text-align: left;
}
td {
padding: 10px 15px 10px 0;
&:first-child {
padding-left: 15px;
}
.ui-sortable-handle {
min-height: 37px;
display: flex;
width:0;
align-items: center;
}
}
}
@@ -234,8 +234,46 @@
margin-left: 10px;
}
.form-horizontal .umb-nested-content--narrow .controls-row
{
.umb-nested-content__placeholder {
height: 22px;
padding: 4px 6px;
border: 1px dashed #d8d7d9;
background: 0 0;
cursor: pointer;
color: #1b264f;
-webkit-animation: fadeIn .5s;
animation: fadeIn .5s;
text-align: center;
&--selected {
border: 1px solid #d8d7d9;
text-align: left;
}
}
.umb-nested-content__placeholder-name{
font-size: 15px;
}
.umb-nested-content__placeholder:hover {
color: #2152a3;
border-color: #2152a3;
text-decoration: none;
}
.umb-nested-content__placeholder-icon-holder {
width: 20px;
text-align: center;
display: inline-block;
}
.umb-nested-content__placeholder-icon {
font-size: 18px;
vertical-align: middle;
}
.form-horizontal .umb-nested-content--narrow .controls-row {
margin-left: 40% !important;
}
@@ -203,9 +203,9 @@
.list-view-layout__name-text {
margin-right: 3px;
}
.list-view-layout__system {
font-size: 10px;
font-size: 10px;
font-weight: normal;
}
@@ -236,6 +236,8 @@
}
.list-view-add-layout {
width:100%;
background:0 0;
margin-top: 10px;
color: @ui-action-discreet-type;
border: 1px dashed @ui-action-discreet-border;
@@ -308,7 +308,14 @@
opacity: @opacity / 100;
}
// Position
.absolute() {
position:absolute;
top:0;
right:0;
bottom:0;
left:0;
}
// BACKGROUNDS
// --------------------------------------------------
+4 -4
View File
@@ -83,10 +83,6 @@
}
}
.umb-rte .mce-fullscreen {
position:absolute;
}
.umb-rte .mce-toolbar .mce-btn-group {
padding: 0;
}
@@ -165,3 +161,7 @@
border: 1px solid #d8d7d9;
max-width: none;
}
.mce-fullscreen {
position: absolute;
}
@@ -118,6 +118,7 @@ ul.sections-tray {
text-decoration: none;
display: block;
position: relative;
outline: none;
&::after {
content: "";
@@ -131,6 +132,13 @@ ul.sections-tray {
top: 0;
left: 0;
}
&:focus .section__name {
.tabbing-active & {
border: 1px solid;
border-color: @gray-9;
}
}
}
}
}
@@ -212,6 +212,22 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
evts.push(eventsService.on("editors.languages.languageDeleted", function (e, args) {
loadLanguages().then(function (languages) {
$scope.languages = languages;
const defaultCulture = $scope.languages[0].culture;
if (args.language.culture === $scope.selectedLanguage.culture) {
$scope.selectedLanguage = defaultCulture;
if ($scope.languages.length > 1) {
$location.search("mculture", defaultCulture);
} else {
$location.search("mculture", null);
}
var currentEditorState = editorState.getCurrent();
if (currentEditorState && currentEditorState.path) {
$scope.treeApi.syncTree({ path: currentEditorState.path, activate: true });
}
}
});
}));
@@ -255,7 +271,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
evts.push(eventsService.on("treeService.removeNode", function (e, args) {
//check to see if the current page has been removed
var currentEditorState = editorState.getCurrent()
var currentEditorState = editorState.getCurrent();
if (currentEditorState && currentEditorState.id.toString() === args.node.id.toString()) {
//current page is loaded, so navigate to root
var section = appState.getSectionState("currentSection");
@@ -279,6 +295,9 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
//select the current language if set in the query string
if (mainCulture && $scope.languages && $scope.languages.length > 1) {
var found = _.find($scope.languages, function (l) {
if (mainCulture === true) {
return false;
}
return l.culture.toLowerCase() === mainCulture.toLowerCase();
});
if (found) {
@@ -348,7 +367,7 @@ function NavigationController($scope, $rootScope, $location, $log, $q, $routePar
return contentResource.allowsCultureVariation().then(function (b) {
if (b === true) {
return languageResource.getAll()
return languageResource.getAll();
} else {
return $q.when([]); //resolve an empty collection
}
@@ -1,7 +1,7 @@
(function () {
"use strict";
function CompositionsController($scope, $location, $filter, overlayService) {
function CompositionsController($scope, $location, $filter, overlayService, localizationService) {
var vm = this;
var oldModel = null;
@@ -68,19 +68,23 @@
or the confirm checkbox has been checked */
if (compositionRemoved) {
vm.allowSubmit = false;
const dialog = {
view: "views/common/infiniteeditors/compositions/overlays/confirmremove.html",
submitButtonLabelKey: "general_ok",
closeButtonLabelKey: "general_cancel",
submit: function (model) {
$scope.model.submit($scope.model);
overlayService.close();
},
close: function () {
overlayService.close();
}
};
overlayService.open(dialog);
localizationService.localize("general_remove").then(function(value) {
const dialog = {
view: "views/common/infiniteeditors/compositions/overlays/confirmremove.html",
title: value,
submitButtonLabelKey: "general_ok",
submitButtonStyle: "danger",
closeButtonLabelKey: "general_cancel",
submit: function (model) {
$scope.model.submit($scope.model);
overlayService.close();
},
close: function () {
overlayService.close();
}
};
overlayService.open(dialog);
});
return;
}
@@ -1,6 +1,9 @@
<div>
<h5 class="red"><i class="icon-alert"></i>Warning</h5>
<p>Removing a composition will delete all the associated property data. Once you save the document type there's no way back, are you sure?</p>
<div class="umb-alert umb-alert--warning mb2">
<localize key="contentTypeEditor_compositionRemoveWarning"></localize>
</div>
<localize key="defaultdialogs_confirmSure"></localize>
</div>
@@ -48,13 +48,14 @@
<li ng-repeat="systemDataType in value | orderBy:'name'"
data-element="editor-{{systemDataType.name}}"
ng-mouseover="vm.showDetailsOverlay(systemDataType)"
ng-click="vm.pickEditor(systemDataType)">
<a class="umb-card-grid-item" href="" title="{{ systemDataType.name }}">
ng-click="vm.pickEditor(systemDataType)"
class="cursor-pointer">
<span class="umb-card-grid-item" title="{{ systemDataType.name }}">
<span>
<i class="{{ systemDataType.icon }}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{ systemDataType.name }}
</span>
</a>
</span>
</li>
</ul>
</div>
@@ -66,16 +67,17 @@
<li ng-repeat="dataType in value | orderBy:'name'"
data-element="editor-{{dataType.name}}"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)">
ng-click="vm.pickDataType(dataType)"
class="cursor-pointer">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
<span class="umb-card-grid-item" title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
</span>
</a>
</span>
</li>
</ul>
</div>
@@ -93,16 +95,17 @@
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="dataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)">
ng-click="vm.pickDataType(dataType)"
class="cursor-pointer">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{dataType.name}}">
<span class="umb-card-grid-item" title="{{dataType.name}}">
<span>
<i class="{{dataType.icon}}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{dataType.name}}
</span>
</a>
</span>
</li>
</ul>
</div>
@@ -116,13 +119,14 @@
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="systemDataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(systemDataType)"
ng-click="vm.pickEditor(systemDataType)">
<a class="umb-card-grid-item" href="" title="{{systemDataType.name}}">
ng-click="vm.pickEditor(systemDataType)"
class="cursor-pointer">
<span class="umb-card-grid-item" title="{{systemDataType.name}}">
<span>
<i class="{{systemDataType.icon}}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{systemDataType.name}}
</span>
</a>
</span>
</li>
</ul>
</div>
@@ -23,6 +23,7 @@
placeholder="@general_url"
class="umb-property-editor umb-textstring"
ng-model="model.target.url"
umb-auto-focus
ng-disabled="model.target.id || model.target.udi" />
</umb-control-group>
@@ -28,6 +28,7 @@ angular.module("umbraco")
var dialogOptions = $scope.model;
$scope.disableFolderSelect = (dialogOptions.disableFolderSelect && dialogOptions.disableFolderSelect !== "0") ? true : false;
$scope.disableFocalPoint = (dialogOptions.disableFocalPoint && dialogOptions.disableFocalPoint !== "0") ? true : false;
$scope.onlyImages = (dialogOptions.onlyImages && dialogOptions.onlyImages !== "0") ? true : false;
$scope.onlyFolders = (dialogOptions.onlyFolders && dialogOptions.onlyFolders !== "0") ? true : false;
$scope.showDetails = (dialogOptions.showDetails && dialogOptions.showDetails !== "0") ? true : false;
@@ -137,7 +138,8 @@ angular.module("umbraco")
$scope.target = node;
if (ensureWithinStartNode(node)) {
selectMedia(node);
$scope.target.url = mediaHelper.resolveFile(node);
$scope.target.url = mediaHelper.resolveFileFromEntity(node);
$scope.target.thumbnail = mediaHelper.resolveFileFromEntity(node, true);
$scope.target.altText = altText;
openDetailsDialog();
}
@@ -333,22 +335,26 @@ angular.module("umbraco")
}
function openDetailsDialog() {
localizationService.localize("defaultdialogs_editSelectedMedia").then(function (data) {
vm.mediaPickerDetailsOverlay = {
show: true,
title: data,
disableFocalPoint: $scope.disableFocalPoint,
submit: function (model) {
$scope.model.selection.push($scope.target);
$scope.model.submit($scope.model);
vm.mediaPickerDetailsOverlay = {
show: true,
submit: function (model) {
vm.mediaPickerDetailsOverlay.show = false;
vm.mediaPickerDetailsOverlay = null;
},
close: function (oldModel) {
vm.mediaPickerDetailsOverlay.show = false;
vm.mediaPickerDetailsOverlay = null;
$scope.model.selection.push($scope.target);
$scope.model.submit($scope.model);
vm.mediaPickerDetailsOverlay.show = false;
vm.mediaPickerDetailsOverlay = null;
},
close: function (oldModel) {
vm.mediaPickerDetailsOverlay.show = false;
vm.mediaPickerDetailsOverlay = null;
}
};
close();
}
};
});
};
var debounceSearchMedia = _.debounce(function () {
@@ -1,198 +1,224 @@
<div ng-controller="Umbraco.Editors.MediaPickerController as vm">
<umb-editor-view >
<umb-editor-view >
<umb-editor-header
name="model.title"
name-locked="true"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<umb-editor-header
name="model.title"
name-locked="true"
hide-alias="true"
hide-icon="true"
hide-description="true">
</umb-editor-header>
<umb-editor-container>
<umb-editor-container>
<form id="fileupload" method="POST" enctype="multipart/form-data" umb-image-upload="options">
<form id="fileupload" method="POST" enctype="multipart/form-data" umb-image-upload="options">
<div on-drag-leave="vm.dragLeave()" on-drag-end="vm.dragLeave()" on-drag-enter="vm.dragEnter()">
<div on-drag-leave="vm.dragLeave()" on-drag-end="vm.dragLeave()" on-drag-enter="vm.dragEnter()">
<div class="umb-control-group umb-mediapicker-upload">
<div class="umb-control-group umb-mediapicker-upload">
<umb-load-indicator
ng-if="vm.loading">
</umb-load-indicator>
<umb-load-indicator
ng-if="vm.loading">
</umb-load-indicator>
<div class="form-search">
<i class="icon-search" aria-hidden="true"></i>
<input class="umb-search-field search-query -full-width-input"
ng-model="vm.searchOptions.filter"
localize="placeholder"
placeholder="@placeholders_search"
ng-change="vm.changeSearch()"
type="text"
no-dirty-check />
<div class="form-search">
<i class="icon-search" aria-hidden="true"></i>
<input class="umb-search-field search-query -full-width-input"
ng-model="vm.searchOptions.filter"
localize="placeholder"
placeholder="@placeholders_search"
ng-change="vm.changeSearch()"
type="text"
umb-auto-focus
no-dirty-check
/>
<div class="form-search__toggle">
<label>
<input type="checkbox" ng-model="showChilds" ng-change="vm.toggle()" />
<localize key="general_includeFromsubFolders">Include subfolders in search</localize>
</label>
<div class="form-search__toggle">
<umb-checkbox
model="showChilds"
on-change="vm.toggle()"
text="Include subfolders in search"
label-key="general_includeFromsubFolders">
</umb-checkbox>
</div>
</div>
<div class="upload-button">
<umb-button
type="button"
label-key="general_upload"
action="vm.upload()"
disabled="lockedFolder"
button-style="action">
</umb-button>
</div>
</div>
<div class="upload-button">
<umb-button
type="button"
label-key="general_upload"
action="vm.upload()"
disabled="lockedFolder"
button-style="action">
</umb-button>
<div class="row umb-control-group" ng-show="!vm.searchOptions.filter">
<ul class="umb-breadcrumbs">
<li ng-hide="startNodeId != -1" class="umb-breadcrumbs__ancestor">
<button type="button" class="umb-breadcrumbs__action" ng-click="vm.gotoFolder()">
<localize key="treeHeaders_media">Media</localize>
</button>
<span class="umb-breadcrumbs__separator" aria-hidden="true">&#47;</span>
</li>
<li ng-repeat="item in path" class="umb-breadcrumbs__ancestor">
<button type="button" class="umb-breadcrumbs__action" ng-click="vm.gotoFolder(item)">{{item.name}}</button>
<span class="umb-breadcrumbs__separator" aria-hidden="true">&#47;</span>
</li>
<li class="umb-breadcrumbs__ancestor" ng-show="!lockedFolder">
<button type="button" class="umb-breadcrumbs__action" ng-hide="model.showFolderInput" ng-click="model.showFolderInput = true">
<i class="icon icon-add small" aria-hidden="true"></i>
<span class="sr-only">
<localize key="visuallyHiddenTexts_createNewFolder">Create new folder</localize>
</span>
</button>
<input type="text"
class="umb-breadcrumbs__add-ancestor"
ng-show="model.showFolderInput"
ng-model="model.newFolderName"
ng-keydown="enterSubmitFolder($event)"
ng-blur="vm.submitFolder()"
focus-when="{{model.showFolderInput}}"
/>
</li>
</ul>
<div class="umb-loader" ng-if="model.creatingFolder"></div>
</div>
<umb-file-dropzone
ng-if="vm.acceptedMediatypes.length > 0 && !vm.loading && !lockedFolder"
accepted-mediatypes="vm.acceptedMediatypes"
parent-id="{{currentFolder.id}}"
files-uploaded="vm.onUploadComplete"
files-queued="vm.onFilesQueue"
accept="{{vm.acceptedFileTypes}}"
max-file-size="{{vm.maxFileSize}}"
hide-dropzone="{{ !activeDrag && (images.length > 0 || vm.searchOptions.filter !== '') }}"
compact="{{ images.length > 0 }}">
</umb-file-dropzone>
<umb-media-grid
ng-if="!vm.loading"
items="images"
on-click="vm.clickHandler"
on-click-name="vm.clickItemName"
on-click-edit="vm.editMediaItem(item)"
allow-on-click-edit="{{allowMediaEdit}}"
item-max-width="150"
item-max-height="150"
item-min-width="100"
item-min-height="100"
disable-folder-select={{disableFolderSelect}}
only-images={{onlyImages}}
only-folders={{onlyFolders}}
include-sub-folders={{showChilds}}
current-folder-id="{{currentFolder.id}}">
</umb-media-grid>
<div class="flex justify-center">
<umb-pagination
ng-if="vm.searchOptions.totalPages > 0 && !vm.loading"
page-number="vm.searchOptions.pageNumber"
total-pages="vm.searchOptions.totalPages"
on-change="vm.changePagination(pageNumber)">
</umb-pagination>
</div>
<umb-empty-state ng-if="vm.searchOptions.filter && images.length === 0 && !vm.loading && !activeDrag" position="center">
<localize key="general_searchNoResult"></localize>
</umb-empty-state>
</div>
<div class="row umb-control-group" ng-show="!vm.searchOptions.filter">
<ul class="umb-breadcrumbs">
<li ng-hide="startNodeId != -1" class="umb-breadcrumbs__ancestor">
<a href ng-click="vm.gotoFolder()" prevent-default><localize key="treeHeaders_media">Media</localize></a>
<span class="umb-breadcrumbs__separator">&#47;</span>
</li>
<li ng-repeat="item in path" class="umb-breadcrumbs__ancestor">
<a href ng-click="vm.gotoFolder(item)" prevent-default>{{item.name}}</a>
<span class="umb-breadcrumbs__separator">&#47;</span>
</li>
<li class="umb-breadcrumbs__ancestor" ng-show="!lockedFolder">
<a role="button" aria-label="Create new folder" ng-hide="model.showFolderInput" ng-click="model.showFolderInput = true">
<i class="icon icon-add small"></i>
</a>
<input type="text" class="umb-breadcrumbs__add-ancestor" ng-show="model.showFolderInput" ng-model="model.newFolderName" ng-keydown="enterSubmitFolder($event)"
ng-blur="vm.submitFolder()" focus-when="{{model.showFolderInput}}" />
</li>
</ul>
<div class="umb-loader" ng-if="model.creatingFolder"></div>
<umb-overlay ng-if="vm.mediaPickerDetailsOverlay.show" model="vm.mediaPickerDetailsOverlay" position="right">
<div class="umb-control-group" ng-if="!target.id">
<h5>
<localize key="@general_url"></localize>
</h5>
<input type="text" localize="placeholder" placeholder="@general_url" class="umb-property-editor umb-textstring" ng-model="target.url" />
</div>
<umb-file-dropzone
ng-if="vm.acceptedMediatypes.length > 0 && !vm.loading && !lockedFolder"
accepted-mediatypes="vm.acceptedMediatypes"
parent-id="{{currentFolder.id}}"
files-uploaded="vm.onUploadComplete"
files-queued="vm.onFilesQueue"
accept="{{vm.acceptedFileTypes}}"
max-file-size="{{vm.maxFileSize}}"
hide-dropzone="{{ !activeDrag && (images.length > 0 || vm.searchOptions.filter !== '') }}"
compact="{{ images.length > 0 }}">
</umb-file-dropzone>
<umb-media-grid
ng-if="!vm.loading"
items="images"
on-click="vm.clickHandler"
on-click-name="vm.clickItemName"
on-click-edit="vm.editMediaItem(item)"
allow-on-click-edit="{{allowMediaEdit}}"
item-max-width="150"
item-max-height="150"
item-min-width="100"
item-min-height="100"
disable-folder-select={{disableFolderSelect}}
only-images={{onlyImages}}
only-folders={{onlyFolders}}
include-sub-folders={{showChilds}}
current-folder-id="{{currentFolder.id}}">
</umb-media-grid>
<div class="flex justify-center">
<umb-pagination
ng-if="vm.searchOptions.totalPages > 0 && !vm.loading"
page-number="vm.searchOptions.pageNumber"
total-pages="vm.searchOptions.totalPages"
on-change="vm.changePagination(pageNumber)">
</umb-pagination>
<div class="umb-control-group">
<h5>
<localize key="@content_altTextOptional"></localize>
</h5>
<input type="text" class="umb-property-editor umb-textstring" ng-model="target.altText" />
</div>
<umb-empty-state ng-if="vm.searchOptions.filter && images.length === 0 && !vm.loading && !activeDrag" position="center">
<localize key="general_searchNoResult"></localize>
</umb-empty-state>
</div>
<umb-overlay ng-if="vm.mediaPickerDetailsOverlay.show" model="vm.mediaPickerDetailsOverlay" position="right">
<div class="umb-control-group">
<div ng-if="target.url">
<umb-image-gravity
src="target.url"
center="target.focalPoint"
on-value-changed="vm.focalPointChanged(left, top)">
</umb-image-gravity>
</div>
<div ng-if="cropSize">
<div ng-if="vm.mediaPickerDetailsOverlay.disableFocalPoint && target.thumbnail">
<h5>
<localize key="general_preview">Preview</localize>
</h5>
<umb-image-thumbnail center="target.focalPoint"
src="target.url"
height="{{cropSize.height}}"
width="{{cropSize.width}}"
max-size="400">
</umb-image-thumbnail>
<img ng-src="{{target.thumbnail}}" alt="{{target.name}}" />
</div>
<div ng-if="!vm.mediaPickerDetailsOverlay.disableFocalPoint">
<h5>
<localize key="@general_focalPoint">Focal point</localize>
</h5>
<div ng-if="target.url">
<umb-image-gravity src="target.url"
center="target.focalPoint"
on-value-changed="vm.focalPointChanged(left, top)">
</umb-image-gravity>
</div>
<div ng-if="cropSize">
<h5>
<localize key="general_preview">Preview</localize>
</h5>
<umb-image-thumbnail center="target.focalPoint"
src="target.url"
height="{{cropSize.height}}"
width="{{cropSize.width}}"
max-size="400">
</umb-image-thumbnail>
</div>
</div>
</div>
</div>
</umb-overlay>
<div class="umb-control-group">
<label>
<localize key="@general_url"></localize>
</label>
<input type="text" localize="placeholder" placeholder="@general_url" class="umb-property-editor umb-textstring" ng-model="target.url"
ng-disabled="target.id" />
</div>
</form>
<div class="umb-control-group">
<label>
<localize key="@content_altTextOptional"></localize>
</label>
<input type="text" class="umb-property-editor umb-textstring" ng-model="target.altText" />
</div>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
</umb-overlay>
<umb-button
action="vm.close()"
button-style="link"
shortcut="esc"
label-key="general_close"
type="button">
</umb-button>
</form>
<umb-button
button-style="success"
label-key="buttons_select"
type="button"
disabled="model.selection.length === 0"
action="vm.submit(model)">
</umb-button>
</umb-editor-container>
</umb-editor-footer-content-right>
</umb-editor-footer>
<umb-editor-footer>
<umb-editor-footer-content-right>
</umb-editor-view>
<umb-button
action="vm.close()"
button-style="link"
shortcut="esc"
label-key="general_close"
type="button">
</umb-button>
<umb-button
button-style="success"
label-key="buttons_select"
type="button"
disabled="model.selection.length === 0"
action="vm.submit(model)">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</div>
</div>
@@ -12,7 +12,9 @@
</li>
<li data-element="section-expand" class="expand" ng-class="{ 'open': showTray === true }" ng-show="needTray">
<a href ng-click="trayClick()"><i></i><i></i><i></i></a>
<a href="#" ng-click="trayClick()" prevent-default>
<span class="section__name"><i></i><i></i><i></i></span>
</a>
<ul id="applications-tray" class="sections-tray shadow-depth-2" ng-if="showTray" on-outside-click="trayClick()">
<li ng-repeat="section in sections | limitTo: overflowingSections" ng-class="{current: section.alias == currentSection}">
@@ -31,6 +31,7 @@
ng-class="{'name-is-empty': $parent.name===null || $parent.name===''}"
ng-disabled="nameDisabled"
umb-auto-focus
focus-on-filled="true"
val-server-field="{{serverValidationNameField}}"
required
aria-required="true"
@@ -35,6 +35,7 @@
ng-model="name"
ng-class="{'name-is-empty': $parent.name===null || $parent.name===''}"
umb-auto-focus
focus-on-filled="true"
val-server-field="Name"
required
autocomplete="off" />
@@ -3,10 +3,11 @@
<div class="umb-editor"
ng-repeat="model in editors"
ng-class="{'umb-editor--small': model.size === 'small',
'umb-editor--medium': model.size === 'medium',
'umb-editor--animating': model.animating,
'--notInFront': model.inFront !== true,
'umb-editor--notInFront': model.inFront !== true,
'umb-editor--infiniteMode': model.infiniteMode,
'moveRight': model.moveRight,
'umb-editor--moveRight': model.moveRight,
'umb-editor--n0': model.styleIndex === 0,
'umb-editor--n1': model.styleIndex === 1,
'umb-editor--n2': model.styleIndex === 2,
@@ -1,13 +1,13 @@
<ul role="tablist" class="umb-tabs-nav">
<li class="umb-tab" role="tab" aria-selected="true" tabindex="0" ng-repeat="tab in vm.tabs | limitTo: vm.maxTabs" data-element="tab-{{tab.alias}}" ng-class="{'umb-tab--active': tab.active, 'umb-tab--error': tabHasError}" val-tab>
<a ng-href="" ng-click="vm.clickTab($event, tab)">{{ tab.label }}</a>
<li ng-click="vm.clickTab($event, tab)" class="umb-tab" role="tab" aria-selected="true" tabindex="0" ng-repeat="tab in vm.tabs | limitTo: vm.maxTabs" data-element="tab-{{tab.alias}}" ng-class="{'umb-tab--active': tab.active, 'umb-tab--error': tabHasError}" val-tab>
<a>{{ tab.label }}</a>
</li>
<li data-element="tab-expand" class="umb-tab umb-tab--expand" ng-class="{ 'open': vm.showTray }" ng-show="vm.needTray">
<a ng-href="" ng-click="vm.toggleTray()"><i></i><i></i><i></i></a>
<li data-element="tab-expand" class="umb-tab umb-tab--expand" tabindex="0" ng-click="vm.toggleTray()" ng-class="{ 'open': vm.showTray }" ng-show="vm.needTray">
<a ng-href=""><i></i><i></i><i></i></a>
<umb-dropdown class="umb-tabs-tray" ng-if="vm.showTray" on-close="vm.hideTray()">
<umb-dropdown-item ng-repeat="tab in vm.tabs | limitTo: vm.overflowingTabs" ng-class="{'umb-tabs-tray-item--active': tab.active}">
<a ng-href="" href="" ng-click="vm.clickTab($event, tab)">{{ tab.label }}</a>
<umb-dropdown-item ng-repeat="tab in vm.tabs | limitTo: vm.overflowingTabs" ng-class="{'umb-tabs-tray-item--active': tab.active}" tabindex="0" ng-click="vm.clickTab($event, tab)">
<a ng-href="">{{ tab.label }}</a>
</umb-dropdown-item>
</umb-dropdown>
</li>
@@ -1,5 +1,5 @@
<li class="umb-tree-item" data-element="tree-item-{{::node.dataElement}}" ng-class="getNodeCssClass(node)" on-right-click="altSelect(node, $event)">
<div class="umb-tree-item__inner" ng-swipe-right="options(node, $event)" ng-dblclick="load(node)" >
<div class="umb-tree-item__inner" ng-swipe-right="options(node, $event)" ng-dblclick="load(node)" tabindex="-1">
<button data-element="tree-item-expand"
class="umb-tree-item__arrow umb-outline btn-reset"
ng-class="{'icon-navigation-right': !node.expanded || node.metaData.isContainer, 'icon-navigation-down': node.expanded && !node.metaData.isContainer}"
@@ -9,12 +9,12 @@
<span class="sr-only">Expand child items for {{node.name}}</span>
</button>
<i class="icon umb-tree-icon sprTree" ng-class="::node.cssClass" title="{{::node.title}}" ng-click="select(node, $event)" ng-style="::node.style"></i>
<i class="icon umb-tree-icon sprTree" ng-class="::node.cssClass" title="{{::node.title}}" ng-click="select(node, $event)" ng-style="::node.style" tabindex="-1"></i>
<span class="umb-tree-item__annotation"></span>
<a class="umb-tree-item__label umb-outline" ng-href="#/{{::node.routePath}}" ng-click="select(node, $event)" title="{{::node.title}}">{{node.name}}</a>
<!-- NOTE: These are the 'option' elipses -->
<button data-element="tree-item-options" class="umb-options btn-reset sr-only sr-only--focusable sr-only--hoverable" ng-click="options(node, $event)" ng-if="::node.menuUrl"><i></i><i></i><i></i></button>
<button data-element="tree-item-options" class="umb-options btn-reset sr-only sr-only--focusable sr-only--hoverable" ng-click="options(node, $event)" ng-if="::node.menuUrl" aria-label="{{optionsText}} {{node.name}}"><i></i><i></i><i></i></button>
<umb-loader ng-show="node.loading" position="bottom" class="umb-tree-item__loader"></umb-loader>
</div>
@@ -37,6 +37,8 @@
<localize key="contentTypeEditor_noGroups"></localize>
</div>
<a ng-if="!sortingMode" hotkey="alt+shift+p" ng-click="addPropertyToActiveGroup()"></a>
<ul class="umb-group-builder__groups" ui-sortable="sortableOptionsGroup" ng-model="model.groups">
<li ng-repeat="tab in model.groups" ng-class="{'umb-group-builder__group-sortable': sortingMode}" data-element="group-{{tab.name}}">
@@ -47,7 +49,7 @@
</a>
<!-- TAB ACTIVE OR INACTIVE STATE -->
<div class="umb-group-builder__group" ng-if="tab.tabState !== 'init'" ng-class="{'-active':tab.tabState=='active', '-inherited': tab.inherited, 'umb-group-builder__group-handle -sortable': sortingMode && !tab.inherited}" ng-click="activateGroup(tab)">
<div class="umb-group-builder__group" ng-if="tab.tabState !== 'init'" ng-class="{'-active':tab.tabState=='active', '-inherited': tab.inherited, 'umb-group-builder__group-handle -sortable': sortingMode && !tab.inherited}" tabindex="0" ng-focus="activateGroup(tab)">
<div class="umb-group-builder__group-title-wrapper">
@@ -119,8 +121,6 @@
data-element="property-add"
class="umb-group-builder__group-add-property"
ng-if="property.propertyState=='init' && !sortingMode"
hotkey="alt+shift+p"
hotkey-when="{{tab.tabState === 'active' && property.propertyState=='init'}}"
ng-click="addProperty(property, tab)"
ng-focus="activateGroup(tab)"
focus-when="{{property.focus}}">
@@ -135,7 +135,7 @@
<ng-form name="propertyTypeForm">
<div class="control-group -no-margin" ng-if="!sortingMode">
<div class="umb-group-builder__property-meta-alias" ng-if="property.inherited || property.locked">{{ property.alias }}</div>
<div class="umb-group-builder__property-meta-alias umb-locked-field__text cursor-not-allowed" style="padding-left: 1px" ng-if="property.inherited || property.locked">{{ property.alias }}</div>
<umb-locked-field
ng-if="!property.inherited && !property.locked"
locked="locked"
@@ -0,0 +1,37 @@
<umb-editor-view>
<umb-editor-header
name="model.title"
name-locked="true"
hide-alias="true"
hide-description="model.hideDescription"
hide-icon="model.hideIcon">
</umb-editor-header>
<umb-editor-container class="umb-list-view-settings__overlay">
<form name="listViewSettingsForm" novalidate val-form-manager>
<umb-box>
<umb-box-content>
<!-- list view settings -->
<umb-property property="preValue" ng-repeat="preValue in model.dataType.preValues">
<umb-property-editor model="preValue" is-pre-value="true"></umb-property-editor>
</umb-property>
</umb-box-content>
</umb-box>
</form>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
type="button"
button-style="link"
label-key="general_close"
action="model.close()">
</umb-button>
<umb-button
type="button"
button-style="success"
label-key="buttons_saveListView"
action="model.submit(model)">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
@@ -9,35 +9,41 @@
</div>
<!-- list view enabled -->
<div ng-if="enableListView">
<div class="umb-list-view-settings__box" ng-class="{'-open': editDataTypeSettings}">
<div class="umb-list-view-settings__content">
<div ng-if="enableListView" class="flex">
<div class="umb-list-view-settings__box">
<div class="flex">
<i class="umb-list-view-settings__list-view-icon icon-list"></i>
<div>
<div>
<div class="umb-list-view-settings__name">{{ dataType.name }} <em ng-if="!customListViewCreated">(<localize key="general_default">default</localize>)</em></div>
<a href="" ng-click="toggleEditListViewDataTypeSettings()"><i class="umb-list-view-settings__settings-icon icon-settings"></i></a>
<div class="umb-list-view-settings__name">
{{ dataType.name }}
<em ng-if="!customListViewCreated">(<localize key="general_default">default</localize>)</em>
</div>
<a href="" class="umb-list-view-settings__create-new" ng-if="!customListViewCreated" ng-click="createCustomListViewDataType()"><localize key="editcontenttype_createListView">Create custom list view</localize></a>
<a href="" class="umb-list-view-settings__remove-new" ng-if="customListViewCreated" ng-click="removeCustomListDataType()"><localize key="editcontenttype_removeListView">Remove custom list view</localize></a>
<button
type="button"
class="btn-link umb-list-view-settings__create-new"
ng-if="!customListViewCreated"
ng-click="createCustomListViewDataType()">
<localize key="editcontenttype_createListView">Create custom list view</localize>
</button>
<button
type="button"
class="btn-link umb-list-view-settings__remove-new"
ng-if="customListViewCreated"
ng-click="removeCustomListDataType()">
<localize key="editcontenttype_removeListView">Remove custom list view</localize>
</button>
</div>
</div>
</div>
<!-- list view settings -->
<div class="umb-list-view-settings__settings form-horizontal" ng-if="editDataTypeSettings" ng-class="{'-open': editDataTypeSettings}">
<umb-property property="preValue" ng-repeat="preValue in dataType.preValues">
<umb-property-editor model="preValue" is-pre-value="true"></umb-property-editor>
</umb-property>
<div class="text-right">
<button type="button" class="btn btn-link" ng-click="toggleEditListViewDataTypeSettings()"><localize key="general_close">Close</localize></button>
<button type="button" class="btn btn-success" ng-click="saveListViewDataType()"><localize key="buttons_saveListView"></localize></button>
<umb-load-indicator ng-show="loading"></umb-load-indicator>
</div>
<div class="umb-group-builder__property-actions">
<div class="umb-group-builder__property-action">
<button
class="btn-icon icon-settings"
ng-click="showSettingsOverlay()"
aria-label="Edit"></button>
</div>
</div>
</div>
</div>
@@ -4,7 +4,6 @@
function ContentSortController($scope, $filter, $routeParams, contentResource, navigationService) {
var vm = this;
var parentId = $scope.currentNode.parentId ? $scope.currentNode.parentId : "-1";
var id = $scope.currentNode.id;
vm.loading = false;
@@ -42,7 +41,7 @@
vm.saveButtonState = "busy";
var args = {
parentId: parentId,
parentId: id,
sortedIds: _.map(vm.children, function(child){ return child.id; })
};
@@ -24,8 +24,6 @@
text="{{ variant.language.name }}"
/>
<div>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - </span>
@@ -48,8 +48,6 @@
<div>
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<span>{{variant.language.name}}</span>
<span class="db umb-list-item__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
@@ -60,6 +58,7 @@
</span>
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
</span>
</div>
</div>
@@ -29,10 +29,9 @@
/>
<div>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<span class="db" ng-if="!saveVariantSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-list-item__description umb-list-item__description--checkbox" variant="variant"></umb-variant-state>
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!saveVariantSelectorForm.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="db" ng-messages="saveVariantSelectorForm.saveVariantSelector.$error" show-validation-on-submit>
@@ -24,10 +24,9 @@
/>
<div>
<strong ng-if="variant.language.isMandatory" class="umb-control-required">*</strong>
<span class="db" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state class="umb-list-item__description umb-list-item__description--checkbox" variant="variant"></umb-variant-state>
<span class="db umb-list-item__description umb-list-item__description--checkbox" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
<umb-variant-state variant="variant"></umb-variant-state>
<span ng-if="variant.language.isMandatory"> - <localize key="languages_mandatoryLanguage"></localize></span>
</span>
<span class="db" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
@@ -79,16 +79,18 @@
</div>
</div>
<umb-control-group label="Name of the Parent Document Type" hide-label="false">
<input type="text" name="collectionName" ng-model="model.collectionName" class="umb-textstring textstring input-block-level" umb-auto-focus required />
<umb-control-group>
<label for="collectionName" class="control-label">Name of the Parent Document Type</label>
<input type="text" name="collectionName" id="collectionName" ng-model="model.collectionName" class="umb-textstring textstring input-block-level" umb-auto-focus required />
<span ng-if="model.disableTemplates === false">
<input id="collectionCreateTemplate" name="collectionCreateTemplate" type="checkbox" ng-model="model.collectionCreateTemplate" style="margin-top: 0;" />
<label for="collectionCreateTemplate" style="margin-bottom: 0; padding-left: 2px;">Create template for the Parent Document Type</label>
</span>
</umb-control-group>
<umb-control-group label="Name of the Item Document Type" hide-label="false">
<input type="text" name="collectionItemName" ng-model="model.collectionItemName" class="umb-textstring textstring input-block-level" required />
<umb-control-group>
<label for="collectionItemName" class="control-label">Name of the Item Document Type</label>
<input type="text" name="collectionItemName" id="collectionItemName" ng-model="model.collectionItemName" class="umb-textstring textstring input-block-level" required />
<span ng-if="model.disableTemplates === false">
<input id="collectionItemCreateTemplate" name="collectionItemCreateTemplate" type="checkbox" ng-model="model.collectionItemCreateTemplate" style="margin-top: 0;" />
<label for="collectionItemCreateTemplate" style="margin-bottom: 0; padding-left: 2px;">Create template for the Item Document Type</label>
@@ -85,7 +85,7 @@
overlayService.open(dialog);
});
event.preventDefault()
event.preventDefault();
event.stopPropagation();
}
@@ -177,7 +177,7 @@
</umb-dropdown-item>
<umb-dropdown-item>
<a ng-href="https://www.google.co.uk/?q=site:our.umbraco.org {{ log.RenderedMessage }}&safe=off#q=site:our.umbraco.org {{ log.RenderedMessage }} {{ log.Properties['SourceContext'].Value }}&safe=off" target="_blank" title="Search Our Umbraco forums using Google">
<a ng-href="https://www.google.co.uk/?q=site:our.umbraco.com {{ log.RenderedMessage }}&safe=off#q=site:our.umbraco.com {{ log.RenderedMessage }} {{ log.Properties['SourceContext'].Value }}&safe=off" target="_blank" title="Search Our Umbraco forums using Google">
<img src="https://www.google.com/favicon.ico" width="16" height="16" /> Search Our Umbraco with Google
</a>
</umb-dropdown-item>
@@ -4,7 +4,6 @@
function MediaSortController($scope, $filter, mediaResource, navigationService) {
var vm = this;
var parentId = $scope.currentNode.parentId ? $scope.currentNode.parentId : "-1";
var id = $scope.currentNode.id;
vm.loading = false;
@@ -42,7 +41,7 @@
vm.saveButtonState = "busy";
var args = {
parentId: parentId,
parentId: id,
sortedIds: _.map(vm.children, function(child){ return child.id; })
};
@@ -28,6 +28,7 @@
view: "views/packages/overlays/delete.html",
package: createdPackage,
submitButtonLabelKey: "contentTypeEditor_yesDelete",
submitButtonStyle:"danger",
submit: function (model) {
performDelete(index, createdPackage);
overlayService.close();
@@ -187,7 +187,7 @@
<div class="umb-package-details__owner-profile-avatar">
<umb-avatar
size="m"
img-src="{{ 'https://our.umbraco.org' + vm.package.ownerInfo.ownerAvatar }}">
img-src="{{ 'https://our.umbraco.com' + vm.package.ownerInfo.ownerAvatar }}">
</umb-avatar>
</div>
@@ -69,9 +69,17 @@
}).then(function (saved) {
// create macro if needed
if($routeParams.create && $routeParams.nomacro !== "true") {
macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function(created) {
macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) {
navigationService.syncTree({
tree: "macros",
path: '-1,new',
forceReload: true,
activate: false
});
completeSave(saved);
}, angular.noop);
} else {
completeSave(saved);
}
@@ -42,6 +42,11 @@ function TreeSourceTypePickerController($scope, contentTypeResource, mediaTypeRe
var editor = {
multiPicker: true,
filterCssClass: "not-allowed not-published",
filter: function (item) {
// filter out folders (containers), element types (for content) and already selected items
return item.nodeType === "container" || item.metaData.isElement || !!_.findWhere(vm.itemTypes, { udi: item.udi });
},
submit: function (model) {
var newItemTypes = _.map(model.selection,
function(selected) {
@@ -8,7 +8,8 @@ angular.module("umbraco")
angularHelper,
$element,
eventsService,
editorService
editorService,
$interpolate
) {
// Grid status variables
@@ -661,7 +662,6 @@ angular.module("umbraco")
return ((spans / $scope.model.config.items.columns) * 100).toFixed(8);
};
$scope.clearPrompt = function (scopedObject, e) {
scopedObject.deletePrompt = false;
e.preventDefault();
@@ -680,6 +680,10 @@ angular.module("umbraco")
$scope.showRowConfigurations = !$scope.showRowConfigurations;
};
$scope.getTemplateName = function (control) {
if (control.editor.nameExp) return control.editor.nameExp(control)
return control.editor.name;
}
// *********************************************
// Initialization
@@ -923,6 +927,11 @@ angular.module("umbraco")
localizationService.localize("grid_" + value.alias, undefined, value.name).then(function (v) {
value.name = v;
});
// setup nametemplate
value.nameExp = !!value.nameTemplate
? $interpolate(value.nameTemplate)
: undefined;
});
$scope.contentReady = true;
@@ -182,7 +182,7 @@
<div class="umb-control-click-overlay" ng-show="control !== active && !sortMode"></div>
<div class="umb-control-collapsed umb-control-handle" ng-show="sortMode">
{{control.editor.name}}
{{ getTemplateName(control) }}
</div>
<div class="umb-control-inner" ng-hide="sortMode">
@@ -190,7 +190,7 @@
<div class="umb-control-bar umb-control-handle">
<div class="umb-control-title" ng-if="control === active">
{{control.editor.name}}
{{ getTemplateName(control) }}
</div>
<div class="umb-tools" ng-if="control === active">
@@ -2,23 +2,24 @@
<div class="control-group">
<select ng-model="selectedField" ng-change="changeField()" val-highlight="{{hasError}}">
<option ng-repeat="field in systemFields" value="_system_{{field.value}}">{{field.name}}</option>
<option ng-repeat="field in systemFields" value="_system_{{field.value}}" ng-bind="field.name"></option>
<option class="select-dash" disabled="disabled">----</option>
<option ng-repeat="alias in propertyAliases" value="{{alias}}">{{alias}}</option>
<option ng-repeat="alias in propertyAliases" value="{{alias}}" ng-bind="alias"></option>
</select>
<button type="button" class="btn" ng-click="addField()">
<localize key="general_add">Add</localize>
</button>
<span class="help-inline">{{errorMsg}}</span>
<span class="help-inline" ng-bind="errorMsg"</span>
</div>
<div class="control-group">
<table ng-show="model.value.length > 0" class="table">
<table ng-show="model.value.length > 0">
<thead>
<tr>
<td style="width:20px;"></td>
<th style="width:220px;">Alias</th>
<td></td>
<th>Alias</th>
<th>Header</th>
<td style="width:100px;"></td>
<th><localize key="template_template">Template</localize></th>
<td></td>
</tr>
</thead>
<tbody ui-sortable="sortableOptions" ng-model="model.value">
@@ -27,26 +28,31 @@
<i class="icon icon-navigation handle"></i>
</td>
<td>
<span class="alias-value" ng-if="!val.isSystem">{{val.alias}}</span>
<span class="alias-value" ng-if="val.isSystem == 1">
{{val.alias}}
</span>
<em ng-show="val.isSystem == 1"><small>(system field)</small></em>
<div class="list-view-layout__name flex-column content-start">
<span class="list-view-layout__name-text" ng-if="!val.isSystem" ng-bind="val.alias"></span>
<span class="list-view-layout__name-text" ng-if="val.isSystem == 1" ng-bind="val.alias"></span>
<span class="list-view-layout__system" ng-show="val.isSystem == 1">(system field)</span>
</div>
</td>
<td>
<ng-form name="headerForm" ng-if="!val.isSystem">
<input type="text" name="header" ng-model="val.header" required />
<span ng-messages="headerForm.header.$error" show-validation-on-submit >
<span ng-messages="headerForm.header.$error" show-validation-on-submit>
<span class="help-inline" ng-message="required">Required</span>
</span>
</ng-form>
<span ng-if="val.isSystem">
<localize key="{{getLocalizedKey(val.alias)}}">{{val.alias}}</localize>
</span>
</td>
<td>
<button type="button" class="btn btn-danger" ng-click="removeField(val)">Remove</button>
<ng-form name="templateForm" ng-if="!val.isSystem">
<input type="text" name="template" ng-model="val.nameTemplate" />
</ng-form>
</td>
<td>
<button type="button" class="btn-icon icon-trash" ng-click="removeField(val)" aria-label="Remove"></button>
</td>
</tr>
</tbody>
@@ -4,13 +4,13 @@
<div class="list-view-layout" ng-repeat="layout in model.value">
<i class="icon-navigation list-view-layout__sort-handle"></i>
<i class="icon-navigation list-view-layout__sort-handle" aria-label="Sort"></i>
<div>
<a ng-if="layout.isSystem !== 1" href="" ng-click="vm.openIconPicker(layout)" class="list-view-layout__icon" umb-auto-focus>
<button ng-if="layout.isSystem !== 1" type="button" ng-click="vm.openIconPicker(layout)" class="list-view-layout__icon" umb-auto-focus>
<i class="{{ layout.icon }}"></i>
</a>
</button>
<div ng-if="layout.isSystem === 1" class="list-view-layout__icon">
<i class="{{ layout.icon }}"></i>
@@ -29,7 +29,7 @@
</div>
<div>
<input ng-if="layout.isSystem === 1" type="checkbox" ng-model="layout.selected" />
<umb-checkbox ng-if="layout.isSystem === 1" model="layout.selected" />
<div class="list-view-layout__remove" ng-if="layout.isSystem !== 1">
<i class="icon-trash" ng-click="vm.showPrompt(layout)"></i>
<umb-confirm-action
@@ -43,7 +43,7 @@
</div>
<a href="" class="list-view-add-layout" ng-click="vm.addLayout()">Add layout</a>
<button type="button" class="list-view-add-layout" ng-click="vm.addLayout()">Add layout</button>
</div>
@@ -1,4 +1,4 @@
function listViewController($scope, $routeParams, $injector, $timeout, currentUserResource, notificationsService, iconHelper, editorState, localizationService, appState, $location, listViewHelper, navigationService, editorService, overlayService, languageResource, mediaHelper) {
function listViewController($scope, $interpolate, $routeParams, $injector, $timeout, currentUserResource, notificationsService, iconHelper, editorState, localizationService, appState, $location, listViewHelper, navigationService, editorService, overlayService, languageResource, mediaHelper) {
//this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
// that isn't created yet, if we continue this will use the parent id in the route params which isn't what
@@ -168,6 +168,11 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
allowBulkDelete: $scope.model.config.bulkActionPermissions.allowBulkDelete,
cultureName: $routeParams.cculture ? $routeParams.cculture : $routeParams.mculture
};
_.each($scope.options.includeProperties, function (property) {
property.nameExp = !!property.nameTemplate
? $interpolate(property.nameTemplate)
: undefined;
});
//watch for culture changes in the query strings and update accordingly
$scope.$watch(function () {
@@ -398,6 +403,7 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
const dialog = {
view: "views/propertyeditors/listview/overlays/delete.html",
deletesVariants: selectionHasVariants(),
isTrashed: $scope.isTrashed,
submitButtonLabelKey: "contentTypeEditor_yesDelete",
submitButtonStyle: "danger",
submit: function (model) {
@@ -699,6 +705,13 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
value = value.substring(0, value.length - 3);
}
if (e.nameExp) {
var newValue = e.nameExp({ value });
if (newValue && (newValue = $.trim(newValue))) {
value = newValue;
}
}
// set what we've got on the result
result[alias] = value;
});
@@ -754,10 +767,11 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
$scope.contentId = id;
$scope.isTrashed = id === "-20" || id === "-21";
$scope.isTrashed = editorState.current ? editorState.current.trashed : id === "-20" || id === "-21";
$scope.options.allowBulkPublish = $scope.options.allowBulkPublish && !$scope.isTrashed;
$scope.options.allowBulkUnpublish = $scope.options.allowBulkUnpublish && !$scope.isTrashed;
$scope.options.allowBulkCopy = $scope.options.allowBulkCopy && !$scope.isTrashed;
$scope.options.bulkActionsAllowed = $scope.options.allowBulkPublish ||
$scope.options.allowBulkUnpublish ||
@@ -1,15 +1,9 @@
<div>
<ng-form name="listViewOrderDirectionForm">
<label class="radio">
<input type="radio" name="orderDirection" value="asc" ng-model="model.value" />
Ascending <code>[a-z]</code>
</label>
<label class="radio">
<input type="radio" name="orderDirection" value="desc" ng-model="model.value" />
Descending <code>[z-a]</code>
</label>
<ng-form name="listViewOrderDirectionForm" class="flex">
<umb-radiobutton name="orderDirection" value="asc" model="model.value" text="Ascending [a-z]" required></umb-radiobutton>
<umb-radiobutton name="orderDirection" value="desc" model="model.value" text="Descending [z-a]" required></umb-radiobutton>
<span ng-messages="listViewOrderDirectionForm.orderDirection.$error" show-validation-on-submit >
<span ng-messages="listViewOrderDirectionForm.orderDirection.$error" show-validation-on-submit>
<span class="help-inline" ng-message="required">Required</span>
</span>
</ng-form>
@@ -1,11 +1,15 @@
<div>
<div ng-if="model.deletesVariants" class="umb-alert umb-alert--warning mb2">
<localize key="defaultdialogs_variantdeletewarning">
This will delete the node and all its languages. If you only want to delete one language go and unpublish it instead.
</localize>
<p class="abstract">
<localize key="defaultdialogs_confirmdelete"></localize>?
</p>
<div class="umb-alert umb-alert--warning" ng-show="model.deletesVariants && !model.isTrashed">
<localize key="defaultdialogs_variantdeletewarning">This will delete the node and all its languages. If you only want to delete one language go and unpublish it instead.</localize>
</div>
<div class="umb-alert umb-alert--warning" ng-show="model.isTrashed">
<localize key="defaultdialogs_recycleBinWarning">When items are deleted from the recycle bin, they will be gone forever</localize>.
</div>
<localize key="defaultdialogs_confirmdelete"></localize>?
</div>
@@ -24,7 +24,7 @@
<div class="umb-list-item" ng-repeat="language in vm.languages track by language.id">
<ng-form name="publishLanguageSelectorForm">
<div class="flex">
<div>
<umb-checkbox name="publishLanguageSelector"
model="language.publish"
@@ -33,7 +33,7 @@
/>
<div>
<span class="db umb-list-item__description">
<span class="db umb-list-item__description umb-list-item__description--checkbox">
<span ng-if="language.isMandatory"><localize key="languages_mandatoryLanguage"></localize></span>
</span>
</div>

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