Merge branch 'v8/8.7' into v8/dev
# Conflicts: # src/SolutionInfo.cs # src/Umbraco.Web.UI.Client/package.json # src/Umbraco.Web.UI.Client/src/common/services/formhelper.service.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/itempicker/itempicker.controller.js # src/Umbraco.Web.UI.Client/src/views/components/users/change-password.html # src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.controller.js # src/Umbraco.Web.UI.Client/src/views/content/overlays/publish.html # src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.controller.js # src/Umbraco.Web.UI.Client/src/views/content/overlays/publishdescendants.html # src/Umbraco.Web.UI.Client/src/views/datatypes/delete.html # src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html # src/Umbraco.Web.UI.Client/src/views/logviewer/search.html # src/Umbraco.Web.UI.Client/src/views/member/member.edit.controller.js # src/Umbraco.Web.UI.Client/src/views/packages/edit.html # src/Umbraco.Web.UI.Client/src/views/propertyeditors/dropdownFlexible/dropdownFlexible.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/layouts.prevalues.html # src/Umbraco.Web.UI.Client/src/views/propertyeditors/nestedcontent/nestedcontent.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/rte/rte.prevalues.html
This commit is contained in:
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
namespace Umbraco.Core.Models.Blocks
|
||||
{
|
||||
/// <summary>
|
||||
/// The base class for any strongly typed model for a Block editor implementation
|
||||
/// </summary>
|
||||
public abstract class BlockEditorModel
|
||||
{
|
||||
protected BlockEditorModel(IEnumerable<IPublishedElement> contentData, IEnumerable<IPublishedElement> settingsData)
|
||||
{
|
||||
ContentData = contentData ?? throw new ArgumentNullException(nameof(contentData));
|
||||
SettingsData = settingsData ?? new List<IPublishedContent>();
|
||||
}
|
||||
|
||||
public BlockEditorModel()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// The content data items of the Block List editor
|
||||
/// </summary>
|
||||
[DataMember(Name = "contentData")]
|
||||
public IEnumerable<IPublishedElement> ContentData { get; set; } = new List<IPublishedContent>();
|
||||
|
||||
/// <summary>
|
||||
/// The settings data items of the Block List editor
|
||||
/// </summary>
|
||||
[DataMember(Name = "settingsData")]
|
||||
public IEnumerable<IPublishedElement> SettingsData { get; set; } = new List<IPublishedContent>();
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,14 @@ namespace Umbraco.Core.Models.Blocks
|
||||
/// </summary>
|
||||
public class BlockPropertyValue
|
||||
{
|
||||
public object Value { get; set; }
|
||||
public PropertyType PropertyType { get; set; }
|
||||
public BlockPropertyValue(object value, PropertyType propertyType)
|
||||
{
|
||||
Value = value;
|
||||
PropertyType = propertyType ?? throw new ArgumentNullException(nameof(propertyType));
|
||||
}
|
||||
|
||||
public object Value { get; }
|
||||
public PropertyType PropertyType { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,10 +7,10 @@ namespace Umbraco.Core.Models.Blocks
|
||||
/// <summary>
|
||||
/// Represents a layout item for the Block List editor
|
||||
/// </summary>
|
||||
[DataContract(Name = "blockListLayout", Namespace = "")]
|
||||
public class BlockListLayoutReference : IBlockReference<IPublishedElement>
|
||||
[DataContract(Name = "block", Namespace = "")]
|
||||
public class BlockListItem : IBlockReference<IPublishedElement>
|
||||
{
|
||||
public BlockListLayoutReference(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings)
|
||||
public BlockListItem(Udi contentUdi, IPublishedElement content, Udi settingsUdi, IPublishedElement settings)
|
||||
{
|
||||
ContentUdi = contentUdi ?? throw new ArgumentNullException(nameof(contentUdi));
|
||||
Content = content ?? throw new ArgumentNullException(nameof(content));
|
||||
@@ -33,19 +33,13 @@ namespace Umbraco.Core.Models.Blocks
|
||||
/// <summary>
|
||||
/// The content data item referenced
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is ignored from serialization since it is just a reference to the actual data element
|
||||
/// </remarks>
|
||||
[IgnoreDataMember]
|
||||
[DataMember(Name = "content")]
|
||||
public IPublishedElement Content { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The settings data item referenced
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is ignored from serialization since it is just a reference to the actual data element
|
||||
/// </remarks>
|
||||
[IgnoreDataMember]
|
||||
[DataMember(Name = "settings")]
|
||||
public IPublishedElement Settings { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Umbraco.Core.Models.PublishedContent;
|
||||
|
||||
@@ -8,26 +11,54 @@ namespace Umbraco.Core.Models.Blocks
|
||||
/// The strongly typed model for the Block List editor
|
||||
/// </summary>
|
||||
[DataContract(Name = "blockList", Namespace = "")]
|
||||
public class BlockListModel : BlockEditorModel
|
||||
public class BlockListModel : IReadOnlyList<BlockListItem>
|
||||
{
|
||||
private readonly IReadOnlyList<BlockListItem> _layout = new List<BlockListItem>();
|
||||
|
||||
public static BlockListModel Empty { get; } = new BlockListModel();
|
||||
|
||||
private BlockListModel()
|
||||
{
|
||||
}
|
||||
|
||||
public BlockListModel(IEnumerable<IPublishedElement> contentData, IEnumerable<IPublishedElement> settingsData, IEnumerable<BlockListLayoutReference> layout)
|
||||
: base(contentData, settingsData)
|
||||
public BlockListModel(IEnumerable<BlockListItem> layout)
|
||||
{
|
||||
Layout = layout;
|
||||
_layout = layout.ToList();
|
||||
}
|
||||
|
||||
public int Count => _layout.Count;
|
||||
|
||||
/// <summary>
|
||||
/// The layout items of the Block List editor
|
||||
/// Get the block by index
|
||||
/// </summary>
|
||||
[DataMember(Name = "layout")]
|
||||
public IEnumerable<BlockListLayoutReference> Layout { get; } = new List<BlockListLayoutReference>();
|
||||
/// <param name="index"></param>
|
||||
/// <returns></returns>
|
||||
public BlockListItem this[int index] => _layout[index];
|
||||
|
||||
/// <summary>
|
||||
/// Get the block by content Guid
|
||||
/// </summary>
|
||||
/// <param name="contentKey"></param>
|
||||
/// <returns></returns>
|
||||
public BlockListItem this[Guid contentKey] => _layout.FirstOrDefault(x => x.Content.Key == contentKey);
|
||||
|
||||
/// <summary>
|
||||
/// Get the block by content element Udi
|
||||
/// </summary>
|
||||
/// <param name="contentUdi"></param>
|
||||
/// <returns></returns>
|
||||
public BlockListItem this[Udi contentUdi]
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!(contentUdi is GuidUdi guidUdi)) return null;
|
||||
return _layout.FirstOrDefault(x => x.Content.Key == guidUdi.Guid);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<BlockListItem> GetEnumerator() => _layout.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Umbraco.Core.Persistence.Repositories.Implement
|
||||
|
||||
if (objectTypes.Any())
|
||||
{
|
||||
sql = sql.Where("umbracoNode.nodeObjectType IN (@objectTypes)", objectTypes);
|
||||
sql = sql.Where("umbracoNode.nodeObjectType IN (@objectTypes)", new { objectTypes = objectTypes });
|
||||
}
|
||||
|
||||
return Database.Fetch<string>(sql);
|
||||
|
||||
@@ -150,8 +150,7 @@
|
||||
<Compile Include="Persistence\Repositories\Implement\InstallationRepository.cs" />
|
||||
<Compile Include="Services\Implement\InstallationService.cs" />
|
||||
<Compile Include="Migrations\Upgrade\V_8_6_0\AddMainDomLock.cs" />
|
||||
<Compile Include="Models\Blocks\BlockEditorModel.cs" />
|
||||
<Compile Include="Models\Blocks\BlockListLayoutReference.cs" />
|
||||
<Compile Include="Models\Blocks\BlockListItem.cs" />
|
||||
<Compile Include="Models\Blocks\BlockListModel.cs" />
|
||||
<Compile Include="Models\UpgradeResult.cs" />
|
||||
<Compile Include="Persistence\Repositories\Implement\UpgradeCheckRepository.cs" />
|
||||
|
||||
@@ -154,15 +154,13 @@ namespace Umbraco.Tests.PropertyEditors
|
||||
var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(0, converted.ContentData.Count());
|
||||
Assert.AreEqual(0, converted.Layout.Count());
|
||||
Assert.AreEqual(0, converted.Count);
|
||||
|
||||
json = string.Empty;
|
||||
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(0, converted.ContentData.Count());
|
||||
Assert.AreEqual(0, converted.Layout.Count());
|
||||
Assert.AreEqual(0, converted.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -177,8 +175,7 @@ namespace Umbraco.Tests.PropertyEditors
|
||||
var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(0, converted.ContentData.Count());
|
||||
Assert.AreEqual(0, converted.Layout.Count());
|
||||
Assert.AreEqual(0, converted.Count);
|
||||
|
||||
json = @"{
|
||||
layout: {},
|
||||
@@ -186,8 +183,7 @@ data: []}";
|
||||
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(0, converted.ContentData.Count());
|
||||
Assert.AreEqual(0, converted.Layout.Count());
|
||||
Assert.AreEqual(0, converted.Count);
|
||||
|
||||
// Even though there is a layout, there is no data, so the conversion will result in zero elements in total
|
||||
json = @"
|
||||
@@ -205,8 +201,7 @@ data: []}";
|
||||
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(0, converted.ContentData.Count());
|
||||
Assert.AreEqual(0, converted.Layout.Count());
|
||||
Assert.AreEqual(0, converted.Count);
|
||||
|
||||
// Even though there is a layout and data, the data is invalid (missing required keys) so the conversion will result in zero elements in total
|
||||
json = @"
|
||||
@@ -228,8 +223,7 @@ data: []}";
|
||||
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(0, converted.ContentData.Count());
|
||||
Assert.AreEqual(0, converted.Layout.Count());
|
||||
Assert.AreEqual(0, converted.Count);
|
||||
|
||||
// Everthing is ok except the udi reference in the layout doesn't match the data so it will be empty
|
||||
json = @"
|
||||
@@ -252,8 +246,7 @@ data: []}";
|
||||
converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(1, converted.ContentData.Count());
|
||||
Assert.AreEqual(0, converted.Layout.Count());
|
||||
Assert.AreEqual(0, converted.Count);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -283,14 +276,12 @@ data: []}";
|
||||
var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(1, converted.ContentData.Count());
|
||||
var item0 = converted.ContentData.ElementAt(0);
|
||||
Assert.AreEqual(1, converted.Count);
|
||||
var item0 = converted[0].Content;
|
||||
Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Key);
|
||||
Assert.AreEqual("Test1", item0.ContentType.Alias);
|
||||
Assert.AreEqual(1, converted.Layout.Count());
|
||||
var layout0 = converted.Layout.ElementAt(0);
|
||||
Assert.IsNull(layout0.Settings);
|
||||
Assert.AreEqual(Udi.Parse("umb://element/1304E1DDAC87439684FE8A399231CB3D"), layout0.ContentUdi);
|
||||
Assert.IsNull(converted[0].Settings);
|
||||
Assert.AreEqual(Udi.Parse("umb://element/1304E1DDAC87439684FE8A399231CB3D"), converted[0].ContentUdi);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -348,17 +339,15 @@ data: []}";
|
||||
var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(3, converted.ContentData.Count());
|
||||
Assert.AreEqual(3, converted.SettingsData.Count());
|
||||
Assert.AreEqual(2, converted.Layout.Count());
|
||||
Assert.AreEqual(2, converted.Count);
|
||||
|
||||
var item0 = converted.Layout.ElementAt(0);
|
||||
var item0 = converted[0];
|
||||
Assert.AreEqual(Guid.Parse("1304E1DD-AC87-4396-84FE-8A399231CB3D"), item0.Content.Key);
|
||||
Assert.AreEqual("Test1", item0.Content.ContentType.Alias);
|
||||
Assert.AreEqual(Guid.Parse("1F613E26CE274898908A561437AF5100"), item0.Settings.Key);
|
||||
Assert.AreEqual("Setting2", item0.Settings.ContentType.Alias);
|
||||
|
||||
var item1 = converted.Layout.ElementAt(1);
|
||||
var item1 = converted[1];
|
||||
Assert.AreEqual(Guid.Parse("0A4A416E-547D-464F-ABCC-6F345C17809A"), item1.Content.Key);
|
||||
Assert.AreEqual("Test2", item1.Content.ContentType.Alias);
|
||||
Assert.AreEqual(Guid.Parse("63027539B0DB45E7B70459762D4E83DD"), item1.Settings.Key);
|
||||
@@ -434,11 +423,9 @@ data: []}";
|
||||
var converted = editor.ConvertIntermediateToObject(publishedElement, propertyType, PropertyCacheLevel.None, json, false) as BlockListModel;
|
||||
|
||||
Assert.IsNotNull(converted);
|
||||
Assert.AreEqual(2, converted.ContentData.Count());
|
||||
Assert.AreEqual(0, converted.SettingsData.Count());
|
||||
Assert.AreEqual(1, converted.Layout.Count());
|
||||
Assert.AreEqual(1, converted.Count);
|
||||
|
||||
var item0 = converted.Layout.ElementAt(0);
|
||||
var item0 = converted[0];
|
||||
Assert.AreEqual(Guid.Parse("0A4A416E-547D-464F-ABCC-6F345C17809A"), item0.Content.Key);
|
||||
Assert.AreEqual("Test2", item0.Content.ContentType.Alias);
|
||||
Assert.IsNull(item0.Settings);
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"lazyload-js": "1.0.0",
|
||||
"moment": "2.22.2",
|
||||
"ng-file-upload": "12.2.13",
|
||||
"nouislider": "14.6.0",
|
||||
"nouislider": "14.6.1",
|
||||
"npm": "^6.14.7",
|
||||
"signalr": "2.4.0",
|
||||
"spectrum-colorpicker": "1.8.0",
|
||||
|
||||
@@ -168,6 +168,7 @@
|
||||
vm.inviteStep = 2;
|
||||
|
||||
}, function (err) {
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
formHelper.handleError(err);
|
||||
vm.invitedUserPasswordModel.buttonState = "error";
|
||||
});
|
||||
|
||||
@@ -588,6 +588,7 @@
|
||||
eventsService.emit("content.unpublished", { content: $scope.content });
|
||||
overlayService.close();
|
||||
}, function (err) {
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
$scope.page.buttonGroupState = 'error';
|
||||
handleHttpException(err);
|
||||
});
|
||||
|
||||
@@ -188,6 +188,7 @@ Use this directive to construct a header inside the main editor window.
|
||||
</ul>
|
||||
|
||||
@param {string} name The content name.
|
||||
@param {boolean=} nameRequired Require name to be defined. (True by default)
|
||||
@param {array=} tabs Array of tabs. See example above.
|
||||
@param {array=} navigation Array of sub views. See example above.
|
||||
@param {boolean=} nameLocked Set to <code>true</code> to lock the name.
|
||||
@@ -358,6 +359,7 @@ Use this directive to construct a header inside the main editor window.
|
||||
scope: {
|
||||
name: "=",
|
||||
nameLocked: "=",
|
||||
nameRequired: "=?",
|
||||
menu: "=",
|
||||
hideActionsMenu: "<?",
|
||||
icon: "=",
|
||||
|
||||
@@ -159,9 +159,6 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
element.removeClass(SHOW_VALIDATION_CLASS_NAME);
|
||||
scope.showValidation = false;
|
||||
notifySubView();
|
||||
//clear form state as at this point we retrieve new data from the server
|
||||
//and all validation will have cleared at this point
|
||||
formCtrl.$setPristine();
|
||||
}));
|
||||
|
||||
var confirmed = false;
|
||||
@@ -238,6 +235,8 @@ function valFormManager(serverValidationManager, $rootScope, $timeout, $location
|
||||
}
|
||||
});
|
||||
|
||||
// TODO: I'm unsure why this exists, i believe this may be a hack for something like tinymce which might automatically
|
||||
// change a form value on load but we need it to be $pristine?
|
||||
$timeout(function () {
|
||||
formCtrl.$setPristine();
|
||||
}, 1000);
|
||||
|
||||
@@ -306,7 +306,15 @@ function valPropertyMsg(serverValidationManager, localizationService, angularHel
|
||||
formCtrl.$setValidity('valPropertyMsg', false);
|
||||
startWatch();
|
||||
|
||||
|
||||
// This check is required in order to be able to reset ourselves and is typically for complex editor
|
||||
// scenarios where the umb-property itself doesn't contain any ng-model controls which means that the
|
||||
// above serverValidityResetter technique will not work to clear valPropertyMsg errors.
|
||||
// In order for this to work we rely on the current form controller's $pristine state. This means that anytime
|
||||
// the form is submitted whether there are validation errors or not the state must be reset... this is automatically
|
||||
// taken care of with the formHelper.resetForm method that should be used in all cases. $pristine is required because it's
|
||||
// a value that is cascaded to all form controls based on the hierarchy of child ng-model controls. This allows us to easily
|
||||
// know if a value has changed. The alternative is what we used to do which was to put a deep $watch on the entire complex value
|
||||
// which is hugely inefficient.
|
||||
if (propertyErrors.length === 1 && hadError && !formCtrl.$pristine) {
|
||||
var propertyValidationPath = umbPropCtrl.getValidationPath();
|
||||
serverValidationManager.removePropertyError(propertyValidationPath, currentCulture, "", currentSegment);
|
||||
|
||||
@@ -54,6 +54,8 @@ function valServerMatch(serverValidationManager) {
|
||||
|
||||
function bindCallback(validationKey, matchVal, matchType) {
|
||||
|
||||
if (!matchVal) return;
|
||||
|
||||
if (Utilities.isString(matchVal)) {
|
||||
matchVal = [matchVal]; // normalize to an array since the value can also natively be an array
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService) {
|
||||
function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService, umbRequestHelper) {
|
||||
|
||||
/**
|
||||
* Simple mapping from property model content entry to editing model,
|
||||
@@ -236,7 +236,7 @@
|
||||
|
||||
|
||||
/**
|
||||
* Formats the content apps and ensures unsupported property's have the notsupported view (returns a promise)
|
||||
* Formats the content apps and ensures unsupported property's have the notsupported view
|
||||
* @param {any} scaffold
|
||||
*/
|
||||
function formatScaffoldData(scaffold) {
|
||||
@@ -255,7 +255,7 @@
|
||||
// could be empty in tests
|
||||
if (!scaffold.apps) {
|
||||
console.warn("No content apps found in scaffold");
|
||||
return $q.resolve(scaffold);
|
||||
return scaffold;
|
||||
}
|
||||
|
||||
// replace view of content app
|
||||
@@ -271,10 +271,19 @@
|
||||
scaffold.apps.splice(infoAppIndex, 1);
|
||||
}
|
||||
|
||||
// add the settings app
|
||||
return localizationService.localize("blockEditor_tabBlockSettings").then(
|
||||
function (settingsName) {
|
||||
return scaffold;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a settings content app, we only want to do this if settings is present on the specific block.
|
||||
* @param {any} contentModel
|
||||
*/
|
||||
function appendSettingsContentApp(contentModel, settingsName) {
|
||||
if (!contentModel.apps) {
|
||||
return
|
||||
}
|
||||
|
||||
// add the settings app
|
||||
var settingsTab = {
|
||||
"name": settingsName,
|
||||
"alias": "settings",
|
||||
@@ -282,11 +291,7 @@
|
||||
"view": "views/common/infiniteeditors/blockeditor/blockeditor.settings.html",
|
||||
"hasError": false
|
||||
};
|
||||
scaffold.apps.push(settingsTab);
|
||||
|
||||
return scaffold;
|
||||
}
|
||||
);
|
||||
contentModel.apps.push(settingsTab);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -309,6 +314,8 @@
|
||||
|
||||
this.__watchers = [];
|
||||
|
||||
this.__labels = {};
|
||||
|
||||
// ensure basic part of data-structure is in place:
|
||||
this.value = propertyModelValue;
|
||||
this.value.layout = this.value.layout || {};
|
||||
@@ -318,13 +325,25 @@
|
||||
this.propertyEditorAlias = propertyEditorAlias;
|
||||
this.blockConfigurations = blockConfigurations;
|
||||
|
||||
this.blockConfigurations.forEach(blockConfiguration => {
|
||||
if (blockConfiguration.view != null && blockConfiguration.view !== "") {
|
||||
blockConfiguration.view = umbRequestHelper.convertVirtualToAbsolutePath(blockConfiguration.view);
|
||||
}
|
||||
if (blockConfiguration.stylesheet != null && blockConfiguration.stylesheet !== "") {
|
||||
blockConfiguration.stylesheet = umbRequestHelper.convertVirtualToAbsolutePath(blockConfiguration.stylesheet);
|
||||
}
|
||||
if (blockConfiguration.thumbnail != null && blockConfiguration.thumbnail !== "") {
|
||||
blockConfiguration.thumbnail = umbRequestHelper.convertVirtualToAbsolutePath(blockConfiguration.thumbnail);
|
||||
}
|
||||
});
|
||||
|
||||
this.scaffolds = [];
|
||||
|
||||
this.isolatedScope = scopeOfExistance.$new(true);
|
||||
this.isolatedScope.blockObjects = {};
|
||||
|
||||
this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this)));
|
||||
this.__watchers.push(propertyEditorScope.$on("postFormSubmitting", this.sync.bind(this)));
|
||||
this.__watchers.push(propertyEditorScope.$on("formSubmittingFinalPhase", this.sync.bind(this)));
|
||||
|
||||
};
|
||||
|
||||
@@ -344,24 +363,25 @@
|
||||
// update our values
|
||||
this.value = propertyModelValue;
|
||||
this.value.layout = this.value.layout || {};
|
||||
this.value.data = this.value.data || [];
|
||||
this.value.contentData = this.value.contentData || [];
|
||||
this.value.settingsData = this.value.settingsData || [];
|
||||
|
||||
// re-create the watchers
|
||||
this.__watchers = [];
|
||||
this.__watchers.push(this.isolatedScope.$on("$destroy", this.destroy.bind(this)));
|
||||
this.__watchers.push(propertyEditorScope.$on("postFormSubmitting", this.sync.bind(this)));
|
||||
this.__watchers.push(propertyEditorScope.$on("formSubmittingFinalPhase", this.sync.bind(this)));
|
||||
},
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name getBlockConfiguration
|
||||
* @methodOf umbraco.services.blockEditorModelObject
|
||||
* @description Get block configuration object for a given contentTypeKey.
|
||||
* @param {string} key contentTypeKey to recive the configuration model for.
|
||||
* @returns {Object | null} Configuration model for the that specific block. Or ´null´ if the contentTypeKey isnt available in the current block configurations.
|
||||
* @description Get block configuration object for a given contentElementTypeKey.
|
||||
* @param {string} key contentElementTypeKey to recive the configuration model for.
|
||||
* @returns {Object | null} Configuration model for the that specific block. Or ´null´ if the contentElementTypeKey isnt available in the current block configurations.
|
||||
*/
|
||||
getBlockConfiguration: function (key) {
|
||||
return this.blockConfigurations.find(bc => bc.contentTypeKey === key) || null;
|
||||
return this.blockConfigurations.find(bc => bc.contentElementTypeKey === key) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -373,12 +393,24 @@
|
||||
* @returns {Promise} A Promise object which resolves when all scaffold models are loaded.
|
||||
*/
|
||||
load: function () {
|
||||
|
||||
var self = this;
|
||||
|
||||
var tasks = [];
|
||||
|
||||
tasks.push(localizationService.localize("blockEditor_tabBlockSettings").then(
|
||||
function (settingsName) {
|
||||
// self.__labels might not exists anymore, this happens if this instance has been destroyed before the load is complete.
|
||||
if(self.__labels) {
|
||||
self.__labels.settingsName = settingsName;
|
||||
}
|
||||
}
|
||||
));
|
||||
|
||||
var scaffoldKeys = [];
|
||||
|
||||
this.blockConfigurations.forEach(blockConfiguration => {
|
||||
scaffoldKeys.push(blockConfiguration.contentTypeKey);
|
||||
scaffoldKeys.push(blockConfiguration.contentElementTypeKey);
|
||||
if (blockConfiguration.settingsElementTypeKey != null) {
|
||||
scaffoldKeys.push(blockConfiguration.settingsElementTypeKey);
|
||||
}
|
||||
@@ -387,19 +419,11 @@
|
||||
// removing duplicates.
|
||||
scaffoldKeys = scaffoldKeys.filter((value, index, self) => self.indexOf(value) === index);
|
||||
|
||||
var self = this;
|
||||
|
||||
scaffoldKeys.forEach(contentTypeKey => {
|
||||
tasks.push(contentResource.getScaffoldByKey(-20, contentTypeKey).then(scaffold => {
|
||||
// self.scaffolds might not exists anymore, this happens if this instance has been destroyed before the load is complete.
|
||||
if (self.scaffolds) {
|
||||
return formatScaffoldData(scaffold).then(s => {
|
||||
self.scaffolds.push(s);
|
||||
return s;
|
||||
});
|
||||
}
|
||||
else {
|
||||
return $q.resolve(scaffold);
|
||||
self.scaffolds.push(formatScaffoldData(scaffold));
|
||||
}
|
||||
}));
|
||||
});
|
||||
@@ -415,7 +439,7 @@
|
||||
* @return {Array} array of strings representing alias.
|
||||
*/
|
||||
getAvailableAliasesForBlockContent: function () {
|
||||
return this.blockConfigurations.map(blockConfiguration => this.getScaffoldFromKey(blockConfiguration.contentTypeKey).contentTypeAlias);
|
||||
return this.blockConfigurations.map(blockConfiguration => this.getScaffoldFromKey(blockConfiguration.contentElementTypeKey).contentTypeAlias);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -431,7 +455,7 @@
|
||||
var blocks = [];
|
||||
|
||||
this.blockConfigurations.forEach(blockConfiguration => {
|
||||
var scaffold = this.getScaffoldFromKey(blockConfiguration.contentTypeKey);
|
||||
var scaffold = this.getScaffoldFromKey(blockConfiguration.contentElementTypeKey);
|
||||
if (scaffold) {
|
||||
blocks.push({
|
||||
blockConfigModel: blockConfiguration,
|
||||
@@ -503,12 +527,12 @@
|
||||
var contentScaffold;
|
||||
|
||||
if (blockConfiguration === null) {
|
||||
console.error("The block entry of " + contentUdi + " is not being initialized because its contentTypeKey is not allowed for this PropertyEditor");
|
||||
console.error("The block of " + contentUdi + " is not being initialized because its contentTypeKey('" + dataModel.contentTypeKey + "') is not allowed for this PropertyEditor");
|
||||
}
|
||||
else {
|
||||
contentScaffold = this.getScaffoldFromKey(blockConfiguration.contentTypeKey);
|
||||
contentScaffold = this.getScaffoldFromKey(blockConfiguration.contentElementTypeKey);
|
||||
if (contentScaffold === null) {
|
||||
console.error("The block entry of " + contentUdi + " is not begin initialized cause its Element Type was not loaded.");
|
||||
console.error("The block of " + contentUdi + " is not begin initialized cause its Element Type was not loaded.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +543,6 @@
|
||||
unsupported: true
|
||||
};
|
||||
contentScaffold = {};
|
||||
|
||||
}
|
||||
|
||||
var blockObject = {};
|
||||
@@ -577,6 +600,9 @@
|
||||
ensureUdiAndKey(blockObject.settings, settingsUdi);
|
||||
|
||||
mapToElementModel(blockObject.settings, settingsData);
|
||||
|
||||
// add settings content-app
|
||||
appendSettingsContentApp(blockObject.content, this.__labels.settingsName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,8 +613,7 @@
|
||||
if (this.config.settingsElementTypeKey !== null) {
|
||||
mapElementValues(settings, this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
blockObject.sync = function () {
|
||||
if (this.content !== null) {
|
||||
@@ -597,7 +622,7 @@
|
||||
if (this.config.settingsElementTypeKey !== null) {
|
||||
mapToPropertyModel(this.settings, this.settingsData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// first time instant update of label.
|
||||
blockObject.label = getBlockLabel(blockObject);
|
||||
@@ -636,7 +661,6 @@
|
||||
}
|
||||
|
||||
return blockObject;
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -691,18 +715,18 @@
|
||||
* @name create
|
||||
* @methodOf umbraco.services.blockEditorModelObject
|
||||
* @description Create a empty layout entry, notice the layout entry is not added to the property editors model layout object, since the layout sturcture depends on the property editor.
|
||||
* @param {string} contentTypeKey the contentTypeKey of the block you wish to create, if contentTypeKey is not avaiable in the block configuration then ´null´ will be returned.
|
||||
* @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or null if contentTypeKey is unavaiaible.
|
||||
* @param {string} contentElementTypeKey the contentElementTypeKey of the block you wish to create, if contentElementTypeKey is not avaiable in the block configuration then ´null´ will be returned.
|
||||
* @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or null if contentElementTypeKey is unavaiaible.
|
||||
*/
|
||||
create: function (contentTypeKey) {
|
||||
create: function (contentElementTypeKey) {
|
||||
|
||||
var blockConfiguration = this.getBlockConfiguration(contentTypeKey);
|
||||
var blockConfiguration = this.getBlockConfiguration(contentElementTypeKey);
|
||||
if (blockConfiguration === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var entry = {
|
||||
contentUdi: createDataEntry(contentTypeKey, this.value.contentData)
|
||||
contentUdi: createDataEntry(contentElementTypeKey, this.value.contentData)
|
||||
}
|
||||
|
||||
if (blockConfiguration.settingsElementTypeKey != null) {
|
||||
@@ -723,14 +747,14 @@
|
||||
|
||||
elementTypeDataModel = Utilities.copy(elementTypeDataModel);
|
||||
|
||||
var contentTypeKey = elementTypeDataModel.contentTypeKey;
|
||||
var contentElementTypeKey = elementTypeDataModel.contentTypeKey;
|
||||
|
||||
var layoutEntry = this.create(contentTypeKey);
|
||||
var layoutEntry = this.create(contentElementTypeKey);
|
||||
if (layoutEntry === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var dataModel = getDataByUdi(layoutEntry.udi, this.value.contentData);
|
||||
var dataModel = getDataByUdi(layoutEntry.contentUdi, this.value.contentData);
|
||||
if (dataModel === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
}
|
||||
);
|
||||
|
||||
var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data, firstLevelClearupMethod), label:displayLabel, icon:displayIcon};
|
||||
var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data, firstLevelClearupMethod), label:displayLabel, icon:displayIcon, date:Date.now()};
|
||||
storage.entries.push(entry);
|
||||
|
||||
if (saveStorage(storage) === true) {
|
||||
@@ -216,8 +216,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi
|
||||
}
|
||||
);
|
||||
|
||||
var entry = {unique:uniqueKey, type:type, aliases:aliases, data:copiedDatas, label:displayLabel, icon:displayIcon};
|
||||
|
||||
var entry = {unique:uniqueKey, type:type, aliases:aliases, data:copiedDatas, label:displayLabel, icon:displayIcon, date:Date.now()};
|
||||
storage.entries.push(entry);
|
||||
|
||||
if (saveStorage(storage) === true) {
|
||||
|
||||
@@ -117,6 +117,9 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, editorSt
|
||||
return $q.resolve(data);
|
||||
|
||||
}, function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: args.scope, hasErrors: true });
|
||||
|
||||
self.handleSaveError({
|
||||
showNotifications: args.showNotifications,
|
||||
softRedirect: args.softRedirect,
|
||||
|
||||
@@ -46,7 +46,12 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
|
||||
args.scope.$broadcast("formSubmitting", { scope: args.scope, action: args.action });
|
||||
|
||||
this.focusOnFirstError(currentForm);
|
||||
args.scope.$broadcast("postFormSubmitting", { scope: args.scope, action: args.action });
|
||||
|
||||
// Some property editors need to perform an action after all property editors have reacted to the formSubmitting.
|
||||
args.scope.$broadcast("formSubmittingFinalPhase", { scope: args.scope, action: args.action });
|
||||
|
||||
// Set the form state to submitted
|
||||
currentForm.$setSubmitted();
|
||||
|
||||
//then check if the form is valid
|
||||
if (!args.skipValidation) {
|
||||
@@ -105,14 +110,28 @@ function formHelper(angularHelper, serverValidationManager, notificationsService
|
||||
* @param {object} args An object containing arguments for form submission
|
||||
*/
|
||||
resetForm: function (args) {
|
||||
|
||||
var currentForm;
|
||||
|
||||
if (!args) {
|
||||
throw "args cannot be null";
|
||||
}
|
||||
if (!args.scope) {
|
||||
throw "args.scope cannot be null";
|
||||
}
|
||||
if (!args.formCtrl) {
|
||||
//try to get the closest form controller
|
||||
currentForm = angularHelper.getRequiredCurrentForm(args.scope);
|
||||
}
|
||||
else {
|
||||
currentForm = args.formCtrl;
|
||||
}
|
||||
|
||||
args.scope.$broadcast("formSubmitted", { scope: args.scope });
|
||||
// Set the form state to pristine
|
||||
currentForm.$setPristine();
|
||||
currentForm.$setUntouched();
|
||||
|
||||
args.scope.$broadcast(args.hasErrors ? "formSubmittedValidationFailed" : "formSubmitted", { scope: args.scope });
|
||||
},
|
||||
|
||||
showNotifications: function (args) {
|
||||
|
||||
@@ -31,6 +31,7 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe
|
||||
return Umbraco.Sys.ServerVariables.application.applicationPath + virtualPath.trimStart("~/");
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name umbraco.services.umbRequestHelper#dictionaryToQueryString
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// -------------------------
|
||||
|
||||
.alert {
|
||||
position: relative;
|
||||
padding: 8px 35px 8px 14px;
|
||||
margin-bottom: @baseLineHeight;
|
||||
background-color: @warningBackground;
|
||||
@@ -98,3 +99,29 @@
|
||||
.alert-block p + p {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
|
||||
// Property error alerts
|
||||
// -------------------------
|
||||
.alert.property-error {
|
||||
|
||||
display: inline-block;
|
||||
font-size: 14px;
|
||||
padding: 6px 16px 6px 12px;
|
||||
margin-bottom: 6px;
|
||||
|
||||
&::after {
|
||||
content:'';
|
||||
position: absolute;
|
||||
bottom:-6px;
|
||||
left: 6px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 6px solid transparent;
|
||||
border-right: 6px solid transparent;
|
||||
border-top: 6px solid;
|
||||
}
|
||||
&.alert-error::after {
|
||||
border-top-color: @errorBackground;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,19 @@ button.umb-variant-switcher__toggle {
|
||||
font-weight: bold;
|
||||
background-color: @errorBackground;
|
||||
color: @errorText;
|
||||
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: umb-variant-switcher__toggle--badge-bounce;
|
||||
animation-timing-function: ease;
|
||||
@keyframes umb-variant-switcher__toggle--badge-bounce {
|
||||
0% { transform: translateY(0); }
|
||||
20% { transform: translateY(-6px); }
|
||||
40% { transform: translateY(0); }
|
||||
55% { transform: translateY(-3px); }
|
||||
70% { transform: translateY(0); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,6 +254,19 @@ button.umb-variant-switcher__toggle {
|
||||
font-weight: bold;
|
||||
background-color: @errorBackground;
|
||||
color: @errorText;
|
||||
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: umb-variant-switcher__name--badge-bounce;
|
||||
animation-timing-function: ease;
|
||||
@keyframes umb-variant-switcher__name--badge-bounce {
|
||||
0% { transform: translateY(0); }
|
||||
20% { transform: translateY(-6px); }
|
||||
40% { transform: translateY(0); }
|
||||
55% { transform: translateY(-3px); }
|
||||
70% { transform: translateY(0); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,39 @@
|
||||
height: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
// Validation
|
||||
.show-validation &.-has-error {
|
||||
color: @red;
|
||||
|
||||
&:hover {
|
||||
color: @red !important;
|
||||
}
|
||||
|
||||
&::before {
|
||||
background-color: @red;
|
||||
}
|
||||
|
||||
&:not(.is-active) {
|
||||
.badge {
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: umb-sub-views-nav-item--badge-bounce;
|
||||
animation-timing-function: ease;
|
||||
@keyframes umb-sub-views-nav-item--badge-bounce {
|
||||
0% { transform: translateY(0); }
|
||||
20% { transform: translateY(-6px); }
|
||||
40% { transform: translateY(0); }
|
||||
55% { transform: translateY(-3px); }
|
||||
70% { transform: translateY(0); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
}
|
||||
.badge.--error-badge {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__action:active,
|
||||
@@ -101,6 +134,10 @@
|
||||
height: 12px;
|
||||
min-width: 12px;
|
||||
}
|
||||
&.--error-badge {
|
||||
display: none;
|
||||
font-weight: 900;
|
||||
}
|
||||
}
|
||||
|
||||
&-text {
|
||||
@@ -182,13 +219,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validation
|
||||
.show-validation .umb-sub-views-nav-item__action.-has-error,
|
||||
.show-validation .umb-sub-views-nav-item > a.-has-error {
|
||||
color: @red;
|
||||
|
||||
&::before {
|
||||
background-color: @red;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,10 +84,8 @@
|
||||
.umb-nested-content__heading {
|
||||
line-height: 20px;
|
||||
position: relative;
|
||||
margin-top:1px;
|
||||
padding: 15px 5px;
|
||||
color:@ui-option-type;
|
||||
border-radius: 3px 3px 0 0;
|
||||
|
||||
&:hover {
|
||||
color:@ui-option-type-hover;
|
||||
|
||||
@@ -272,6 +272,7 @@ label:not([for]) {
|
||||
/* CONTROL VALIDATION */
|
||||
.umb-control-required {
|
||||
color: @controlRequiredColor;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.controls-row {
|
||||
|
||||
@@ -138,7 +138,9 @@
|
||||
// additional targetting of the ng-invalid class.
|
||||
.formFieldState(@textColor: @gray-4, @borderColor: @gray-7, @backgroundColor: @gray-10) {
|
||||
// Set the text color
|
||||
.control-label,
|
||||
> .control-label,
|
||||
> .umb-el-wrap > .control-label,
|
||||
> .umb-el-wrap > .control-header > .control-label,
|
||||
.help-block,
|
||||
.help-inline {
|
||||
color: @textColor;
|
||||
|
||||
@@ -481,7 +481,7 @@
|
||||
|
||||
@formErrorText: @errorBackground;
|
||||
@formErrorBackground: lighten(@errorBackground, 55%);
|
||||
@formErrorBorder: darken(spin(@errorBackground, -10), 3%);
|
||||
@formErrorBorder: @red;
|
||||
|
||||
@formSuccessText: @successBackground;
|
||||
@formSuccessBackground: lighten(@successBackground, 48%);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
angular.module("umbraco")
|
||||
.controller("Umbraco.Editors.BlockEditorController",
|
||||
function ($scope, localizationService, formHelper) {
|
||||
function ($scope, localizationService, formHelper, overlayService) {
|
||||
var vm = this;
|
||||
|
||||
vm.model = $scope.model;
|
||||
@@ -23,17 +23,14 @@ angular.module("umbraco")
|
||||
if (contentApp) {
|
||||
if (vm.model.hideContent) {
|
||||
apps.splice(apps.indexOf(contentApp), 1);
|
||||
} else if (vm.model.openSettings !== true) {
|
||||
contentApp.active = true;
|
||||
}
|
||||
contentApp.active = (vm.model.openSettings !== true);
|
||||
}
|
||||
|
||||
if (vm.model.settings && vm.model.settings.variants) {
|
||||
var settingsApp = apps.find(entry => entry.alias === "settings");
|
||||
if (settingsApp) {
|
||||
if (vm.model.openSettings) {
|
||||
settingsApp.active = true;
|
||||
}
|
||||
settingsApp.active = (vm.model.openSettings === true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +39,7 @@ angular.module("umbraco")
|
||||
|
||||
vm.submitAndClose = function () {
|
||||
if (vm.model && vm.model.submit) {
|
||||
|
||||
// always keep server validations since this will be a nested editor and server validations are global
|
||||
if (formHelper.submitForm({
|
||||
scope: $scope,
|
||||
@@ -49,6 +47,9 @@ angular.module("umbraco")
|
||||
keepServerValidation: true
|
||||
})) {
|
||||
vm.model.submit(vm.model);
|
||||
vm.saveButtonState = "success";
|
||||
} else {
|
||||
vm.saveButtonState = "error";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +68,29 @@ angular.module("umbraco")
|
||||
// * It would have a 'commit' method to commit the removed errors - which we would call in the formHelper.submitForm when it's successful
|
||||
// * It would have a 'rollback' method to reset the removed errors - which we would call here
|
||||
|
||||
|
||||
if (vm.blockForm.$dirty === true) {
|
||||
localizationService.localizeMany(["prompt_discardChanges", "blockEditor_blockHasChanges"]).then(function (localizations) {
|
||||
const confirm = {
|
||||
title: localizations[0],
|
||||
view: "default",
|
||||
content: localizations[1],
|
||||
submitButtonLabelKey: "general_discard",
|
||||
submitButtonStyle: "danger",
|
||||
closeButtonLabelKey: "general_cancel",
|
||||
submit: function () {
|
||||
overlayService.close();
|
||||
vm.model.close(vm.model);
|
||||
},
|
||||
close: function () {
|
||||
overlayService.close();
|
||||
}
|
||||
};
|
||||
overlayService.open(confirm);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
// TODO: check if content/settings has changed and ask user if they are sure.
|
||||
vm.model.close(vm.model);
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<umb-editor-header
|
||||
name="vm.model.title"
|
||||
name-required="false"
|
||||
name-locked="true"
|
||||
navigation="vm.tabs"
|
||||
hide-alias="true"
|
||||
@@ -36,6 +37,7 @@
|
||||
|
||||
<umb-button
|
||||
action="vm.close()"
|
||||
shortcut="esc"
|
||||
button-style="link"
|
||||
label="{{vm.closeLabel}}"
|
||||
type="button">
|
||||
@@ -44,7 +46,7 @@
|
||||
<umb-button
|
||||
action="vm.submitAndClose()"
|
||||
button-style="primary"
|
||||
state="submitButtonState"
|
||||
state="vm.saveButtonState"
|
||||
label="{{vm.submitLabel}}"
|
||||
type="button">
|
||||
</umb-button>
|
||||
|
||||
@@ -69,6 +69,7 @@
|
||||
|
||||
<umb-button
|
||||
action="vm.close()"
|
||||
shortcut="esc"
|
||||
button-style="link"
|
||||
label-key="general_cancel"
|
||||
type="button">
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
};
|
||||
|
||||
if ($scope.model.modify) {
|
||||
angular.extend($scope.model.embed, $scope.model.modify);
|
||||
Utilities.extend($scope.model.embed, $scope.model.modify);
|
||||
|
||||
showPreview();
|
||||
}
|
||||
@@ -122,7 +122,6 @@
|
||||
if ($scope.model.embed.url !== "") {
|
||||
showPreview();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function toggleConstrain() {
|
||||
@@ -142,7 +141,6 @@
|
||||
}
|
||||
|
||||
onInit();
|
||||
|
||||
}
|
||||
|
||||
angular.module("umbraco").controller("Umbraco.Editors.EmbedController", EmbedController);
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<umb-box-content>
|
||||
|
||||
<umb-control-group label="@general_url">
|
||||
<input id="url" class="umb-property-editor input-block-level" type="text" style="margin-bottom: 10px;" ng-model="model.embed.url" ng-keyup="$event.keyCode == 13 ? vm.showPreview() : null" focus-when="{{true}}" required />
|
||||
<input type="text" id="url" class="umb-property-editor input-block-level" ng-model="model.embed.url" ng-keyup="$event.keyCode == 13 ? vm.showPreview() : null" focus-when="{{true}}" required />
|
||||
<umb-button
|
||||
type="button"
|
||||
action="vm.showPreview()"
|
||||
|
||||
@@ -160,7 +160,7 @@ angular.module("umbraco")
|
||||
}, 2000);
|
||||
|
||||
}, function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
formHelper.handleError(err);
|
||||
|
||||
$scope.changePasswordButtonState = "error";
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
/* Grid Setup */
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
grid-auto-rows: minmax(200px, auto);
|
||||
grid-auto-rows: minmax(160px, auto);
|
||||
grid-gap: 20px;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
<div class="__showcase" ng-style="{'background-color':vm.blockConfigModel.backgroundColor, 'background-image': vm.blockConfigModel.thumbnail ? 'url('+vm.blockConfigModel.thumbnail+'?upscale=false&width=400)' : 'transparent'}">
|
||||
<i ng-if="vm.blockConfigModel.thumbnail == null && vm.elementTypeModel.icon" class="__icon {{ vm.elementTypeModel.icon }}" ng-style="{'color':vm.blockConfigModel.iconColor}" aria-hidden="true"></i>
|
||||
<div class="__showcase" ng-style="{'background-color':vm.blockConfigModel.backgroundColor, 'background-image': vm.styleBackgroundImage}">
|
||||
<i ng-if="vm.blockConfigModel.thumbnail == null && vm.elementTypeModel.icon" class="__icon {{ vm.elementTypeModel.icon }}" ng-attr-style="{{'color:'+vm.blockConfigModel.iconColor+' !important'}}" aria-hidden="true"></i>
|
||||
</div>
|
||||
<div class="__info">
|
||||
<div class="__name" ng-bind="vm.elementTypeModel.name"></div>
|
||||
|
||||
@@ -75,14 +75,14 @@ umb-block-card {
|
||||
.__info {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
padding-bottom: 6px;
|
||||
padding-bottom: 11px;// 10 + 1 to compentiate for the -1 substraction in margin-bottom.
|
||||
|
||||
.__name {
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
color: @ui-action-type;
|
||||
margin-left: 16px;
|
||||
margin-top: 8px;
|
||||
margin-top: 10px;
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.__subname {
|
||||
|
||||
@@ -14,9 +14,38 @@
|
||||
}
|
||||
});
|
||||
|
||||
function BlockCardController() {
|
||||
function BlockCardController($scope, umbRequestHelper) {
|
||||
|
||||
var vm = this;
|
||||
vm.styleBackgroundImage = "none";
|
||||
|
||||
var unwatch = $scope.$watch("vm.blockConfigModel.thumbnail", (newValue, oldValue) => {
|
||||
if(newValue !== oldValue) {
|
||||
vm.updateThumbnail();
|
||||
}
|
||||
});
|
||||
|
||||
vm.$onInit = function () {
|
||||
|
||||
vm.updateThumbnail();
|
||||
|
||||
}
|
||||
vm.$onDestroy = function () {
|
||||
unwatch();
|
||||
}
|
||||
|
||||
vm.updateThumbnail = function () {
|
||||
if (vm.blockConfigModel.thumbnail == null || vm.blockConfigModel.thumbnail === "") {
|
||||
vm.styleBackgroundImage = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
var path = umbRequestHelper.convertVirtualToAbsolutePath(vm.blockConfigModel.thumbnail);
|
||||
if (path.toLowerCase().endsWith(".svg") === false) {
|
||||
path += "?upscale=false&width=400";
|
||||
}
|
||||
vm.styleBackgroundImage = 'url(\''+path+'\')';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,6 @@
|
||||
<umb-box data-element="node-info-history">
|
||||
|
||||
<umb-box-header title="{{historyLabel}}">
|
||||
|
||||
<umb-button
|
||||
ng-hide="node.trashed"
|
||||
type="button"
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
umb-auto-focus
|
||||
focus-on-filled="true"
|
||||
val-server-field="Name"
|
||||
required
|
||||
aria-required="true"
|
||||
ng-required="nameRequired != null ? nameRequired : true"
|
||||
aria-required="{{nameRequired != null ? nameRequired : true}}"
|
||||
aria-invalid="{{contentForm.headerNameForm.headerName.$invalid ? true : false}}"
|
||||
autocomplete="off"
|
||||
maxlength="255"/>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<i class="icon {{ vm.item.icon }}" aria-hidden="true"></i>
|
||||
<span class="umb-sub-views-nav-item-text">{{ vm.item.name }}</span>
|
||||
<div ng-show="vm.item.badge" class="badge -type-{{vm.item.badge.type}}">{{vm.item.badge.count}}</div>
|
||||
<div ng-show="!vm.item.badge" class="badge -type-alert --error-badge">!</div>
|
||||
</button>
|
||||
|
||||
<ul class="dropdown-menu umb-sub-views-nav-item__anchor_dropdown" ng-if="vm.item.anchors && vm.item.anchors.length > 1">
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
navigationService.hideMenu();
|
||||
},
|
||||
function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
contentEditingHelper.handleSaveError({
|
||||
err: err
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
var hasSomethingToPublish = false;
|
||||
|
||||
vm.variants.forEach(variant => {
|
||||
// if variant is mandatory and not already published:
|
||||
// if varaint is mandatory and not already published:
|
||||
if (variant.publish === false && notPublishedMandatoryFilter(variant)) {
|
||||
return false;
|
||||
}
|
||||
@@ -38,8 +38,9 @@
|
||||
|
||||
function hasAnyDataFilter(variant) {
|
||||
|
||||
if (variant.name == null || variant.name.length === 0) {
|
||||
return false;
|
||||
// if we have a name, then we have data.
|
||||
if (variant.name != null && variant.name.length > 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(variant.isDirty === true) {
|
||||
@@ -82,7 +83,7 @@
|
||||
}
|
||||
|
||||
function notPublishedMandatoryFilter(variant) {
|
||||
return variant.state !== "Published" && isMandatoryFilter(variant);
|
||||
return variant.state !== "Published" && variant.state !== "PublishedPendingChanges" && variant.isMandatory === true;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,12 +121,15 @@
|
||||
// reset to not be published
|
||||
variant.publish = variant.save = false;
|
||||
|
||||
|
||||
variant.isMandatory = isMandatoryFilter(variant);
|
||||
|
||||
|
||||
// if this is a new node and we have data on this variant.
|
||||
if (vm.isNew === true && hasAnyDataFilter(variant)) {
|
||||
variant.save = true;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
vm.availableVariants = vm.variants.filter(publishableVariantFilter);
|
||||
|
||||
@@ -21,19 +21,16 @@
|
||||
on-change="vm.changeSelection(variant)"
|
||||
server-validation-field="{{variant.htmlId}}">
|
||||
|
||||
<span class="umb-variant-selector-entry__title" ng-if="!(variant.segment && variant.language)">
|
||||
<span ng-bind="variant.displayName"></span>
|
||||
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
|
||||
</span>
|
||||
<span class="umb-variant-selector-entry__title" ng-if="variant.segment && variant.language">
|
||||
<span ng-bind="variant.segment"></span>
|
||||
<span class="__secondarytitle"> — {{variant.language.name}}</span>
|
||||
<strong ng-if="variant.isMandatory" class="umb-control-required">*</strong>
|
||||
<span class="umb-variant-selector-entry__title">
|
||||
<span ng-bind="variant.displayName" ng-if="!(variant.segment && variant.language)"></span>
|
||||
<span ng-bind="variant.segment" ng-if="variant.segment && variant.language"></span>
|
||||
<span class="__secondarytitle" ng-if="variant.segment && variant.language"> — {{variant.language.name}}</span>
|
||||
<strong ng-if="variant.isMandatory && variant.state !== 'Published' && variant.state !== 'PublishedPendingChanges'" class="umb-control-required">*</strong>
|
||||
</span>
|
||||
<span class="umb-variant-selector-entry__description" ng-if="!publishVariantSelectorForm.publishVariantSelector.$invalid && !(variant.notifications && variant.notifications.length > 0)">
|
||||
<umb-variant-state variant="variant"></umb-variant-state>
|
||||
<span ng-if="variant.isMandatory"> - </span>
|
||||
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.publish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
|
||||
<span ng-if="variant.isMandatory" ng-class="{'text-error': (variant.state !== 'Published' && variant.state !== 'PublishedPendingChanges' && variant.publish === false) }"><localize key="languages_mandatoryLanguage"></localize></span>
|
||||
</span>
|
||||
<span class="umb-variant-selector-entry__description" ng-messages="publishVariantSelectorForm.publishVariantSelector.$error" show-validation-on-submit>
|
||||
<span class="text-error" ng-message="valServerField">{{publishVariantSelectorForm.publishVariantSelector.errorMsg}}</span>
|
||||
|
||||
@@ -21,6 +21,11 @@
|
||||
$scope.model.title = value;
|
||||
});
|
||||
}
|
||||
if (!vm.labels.includeUnpublished) {
|
||||
localizationService.localize("content_includeUnpublished").then(function (value) {
|
||||
vm.labels.includeUnpublished = value;
|
||||
});
|
||||
}
|
||||
if (!vm.labels.includeUnpublished) {
|
||||
localizationService.localize("content_includeUnpublished").then(value => {
|
||||
vm.labels.includeUnpublished = value;
|
||||
|
||||
@@ -47,7 +47,6 @@
|
||||
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
@@ -181,12 +181,9 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -47,12 +47,9 @@
|
||||
<umb-variant-notification-list notifications="variant.notifications"></umb-variant-notification-list>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
</div>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -28,10 +28,11 @@ function DataTypeCreateController($scope, $location, navigationService, dataType
|
||||
var currPath = node.path ? node.path : "-1";
|
||||
navigationService.syncTree({ tree: "datatypes", path: currPath + "," + folderId, forceReload: true, activate: true });
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderFor });
|
||||
|
||||
}, function(err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderFor, hasErrors: true });
|
||||
// TODO: Handle errors
|
||||
});
|
||||
};
|
||||
|
||||
@@ -128,6 +128,7 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
|
||||
|
||||
}, function(err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
//NOTE: in the case of data type values we are setting the orig/new props
|
||||
// to be the same thing since that only really matters for content/media.
|
||||
contentEditingHelper.handleSaveError({
|
||||
|
||||
@@ -27,13 +27,14 @@ function DictionaryCreateController($scope, $location, dictionaryResource, navig
|
||||
navigationService.syncTree({ tree: "dictionary", path: currPath + "," + data, forceReload: true, activate: true });
|
||||
|
||||
// reset form state
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createDictionaryForm });
|
||||
|
||||
// navigate to edit view
|
||||
var currentSection = appState.getSectionState("currentSection");
|
||||
$location.path("/" + currentSection + "/dictionary/edit/" + data);
|
||||
|
||||
}, function (err) {
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createDictionaryForm, hasErrors: true });
|
||||
if (err.data && err.data.message) {
|
||||
notificationsService.error(err.data.message);
|
||||
navigationService.hideMenu();
|
||||
|
||||
@@ -86,7 +86,7 @@ function DictionaryEditController($scope, $routeParams, $location, dictionaryRes
|
||||
dictionaryResource.save(vm.content, vm.nameDirty)
|
||||
.then(function (data) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, notifications: data.notifications });
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
|
||||
bindDictionary(data);
|
||||
|
||||
@@ -94,6 +94,8 @@ function DictionaryEditController($scope, $routeParams, $location, dictionaryRes
|
||||
},
|
||||
function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
|
||||
contentEditingHelper.handleSaveError({
|
||||
err: err
|
||||
});
|
||||
|
||||
@@ -47,12 +47,13 @@ function DocumentTypesCreateController($scope, $location, navigationService, con
|
||||
activate: true
|
||||
});
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderForm });
|
||||
|
||||
var section = appState.getSectionState("currentSection");
|
||||
|
||||
}, function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderForm, hasErrors: true });
|
||||
$scope.error = err;
|
||||
|
||||
});
|
||||
@@ -83,9 +84,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con
|
||||
$location.search('create', null);
|
||||
$location.search('notemplate', null);
|
||||
|
||||
formHelper.resetForm({
|
||||
scope: $scope
|
||||
});
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createDoctypeCollectionForm });
|
||||
|
||||
var section = appState.getSectionState("currentSection");
|
||||
|
||||
@@ -94,6 +93,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con
|
||||
|
||||
}, function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createDoctypeCollectionForm, hasErrors: true });
|
||||
$scope.error = err;
|
||||
|
||||
//show any notifications
|
||||
|
||||
@@ -161,7 +161,7 @@
|
||||
|
||||
}, function (err) {
|
||||
vm.page.saveButtonState = "error";
|
||||
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
formHelper.handleError(err);
|
||||
|
||||
});
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<div class="umb-logviewer__main-content">
|
||||
<div ng-show="!vm.canLoadLogs">
|
||||
<umb-box>
|
||||
<umb-box-header title="Unable to view logs"/>
|
||||
<umb-box-header title="Unable to view logs"></umb-box-header>
|
||||
<umb-box-content>
|
||||
<p>Today's log file is too large to be viewed and would cause performance problems.</p>
|
||||
<p>If you need to view the log files, try opening them manually</p>
|
||||
|
||||
@@ -25,7 +25,7 @@ function MacrosCreateController($scope, $location, macroResource, navigationServ
|
||||
navigationService.syncTree({ tree: "macros", path: currPath + "," + data, forceReload: true, activate: true });
|
||||
|
||||
// reset form state
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createMacroForm });
|
||||
|
||||
// navigate to edit view
|
||||
var currentSection = appState.getSectionState("currentSection");
|
||||
@@ -33,6 +33,7 @@ function MacrosCreateController($scope, $location, macroResource, navigationServ
|
||||
|
||||
|
||||
}, function (err) {
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createMacroForm, hasErrors: true });
|
||||
if (err.data && err.data.message) {
|
||||
notificationsService.error(err.data.message);
|
||||
navigationService.hideMenu();
|
||||
|
||||
@@ -33,10 +33,11 @@ function MacrosEditController($scope, $q, $routeParams, macroResource, editorSta
|
||||
vm.page.saveButtonState = "busy";
|
||||
|
||||
macroResource.saveMacro(vm.macro).then(function (data) {
|
||||
formHelper.resetForm({ scope: $scope, notifications: data.notifications });
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
bindMacro(data);
|
||||
vm.page.saveButtonState = "success";
|
||||
}, function (error) {
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
contentEditingHelper.handleSaveError({
|
||||
err: error
|
||||
});
|
||||
|
||||
@@ -204,6 +204,7 @@ function mediaEditController($scope, $routeParams, $location, $http, $q, appStat
|
||||
|
||||
}, function(err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
contentEditingHelper.handleSaveError({
|
||||
err: err,
|
||||
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
|
||||
|
||||
@@ -30,11 +30,12 @@ function MediaTypesCreateController($scope, $location, navigationService, mediaT
|
||||
var currPath = node.path ? node.path : "-1";
|
||||
navigationService.syncTree({ tree: "mediatypes", path: currPath + "," + folderId, forceReload: true, activate: true });
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderForm });
|
||||
|
||||
var section = appState.getSectionState("currentSection");
|
||||
|
||||
}, function (err) {
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderForm, hasErrors: true });
|
||||
$scope.error = err;
|
||||
});
|
||||
};
|
||||
|
||||
@@ -228,7 +228,7 @@ function MemberEditController($scope, $routeParams, $location, $http, $q, appSta
|
||||
}
|
||||
|
||||
}, function(err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
contentEditingHelper.handleSaveError({
|
||||
err: err,
|
||||
rebindCallback: contentEditingHelper.reBindChangedProperties($scope.content, err.data)
|
||||
|
||||
@@ -88,6 +88,7 @@ function MemberGroupsEditController($scope, $routeParams, appState, navigationSe
|
||||
|
||||
}, function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
contentEditingHelper.handleSaveError({
|
||||
err: err
|
||||
});
|
||||
|
||||
@@ -31,10 +31,10 @@ function MemberTypesCreateController($scope, $location, navigationService, membe
|
||||
var currPath = node.path ? node.path : "-1";
|
||||
navigationService.syncTree({ tree: "membertypes", path: currPath + "," + folderId, forceReload: true, activate: true });
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderForm });
|
||||
|
||||
}, function(err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createFolderForm, hasErrors: true });
|
||||
// TODO: Handle errors
|
||||
});
|
||||
};
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
vm.package = updatedPackage;
|
||||
vm.buttonState = "success";
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: editPackageForm });
|
||||
|
||||
if (create) {
|
||||
//if we are creating, then redirect to the correct url and reload
|
||||
@@ -204,6 +204,7 @@
|
||||
}
|
||||
|
||||
}, function (err) {
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: editPackageForm, hasErrors: true });
|
||||
formHelper.handleError(err);
|
||||
vm.buttonState = "error";
|
||||
});
|
||||
|
||||
@@ -46,12 +46,13 @@
|
||||
activate: true
|
||||
});
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: form });
|
||||
|
||||
var section = appState.getSectionState("currentSection");
|
||||
|
||||
}, function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: form, hasErrors: true });
|
||||
vm.createFolderError = err;
|
||||
|
||||
});
|
||||
|
||||
@@ -56,12 +56,13 @@
|
||||
activate: true
|
||||
});
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: form });
|
||||
|
||||
var section = appState.getSectionState("currentSection");
|
||||
|
||||
}, function(err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: form, hasErrors: true });
|
||||
vm.createFolderError = err;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
<umb-block-list-property-editor model="model"/>
|
||||
<umb-block-list-property-editor model="model"></umb-block-list-property-editor>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
// boardcast the formSubmitting event to trigger syncronization or none-live property-editors
|
||||
$scope.$broadcast("formSubmitting", { scope: $scope });
|
||||
// Some property editors need to performe an action after all property editors have reacted to the formSubmitting.
|
||||
$scope.$broadcast("postFormSubmitting", { scope: $scope });
|
||||
$scope.$broadcast("formSubmittingFinalPhase", { scope: $scope });
|
||||
|
||||
block.active = false;
|
||||
} else {
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
ng-click="vm.openBlock(block)"
|
||||
ng-focus="block.focus">
|
||||
<span class="caret"></span>
|
||||
<i class="icon {{block.content.icon}}"></i>
|
||||
<span>{{block.label}}</span>
|
||||
<i class="icon {{block.content.icon}}" aria-hidden="true"></i>
|
||||
<span class="name">{{block.label}}</span>
|
||||
</button>
|
||||
<div class="blockelement-inlineblock-editor__inner" ng-class="{'--singleGroup':block.content.variants[0].tabs.length === 1}" ng-if="block.active === true">
|
||||
<umb-element-editor-content model="block.content"></umb-element-editor-content>
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
background-color: white;
|
||||
|
||||
.caret {
|
||||
vertical-align: middle;
|
||||
transform: rotate(-90deg);
|
||||
transition: transform 80ms ease-out;
|
||||
}
|
||||
@@ -32,7 +33,8 @@
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
span {
|
||||
span.name {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -54,10 +56,55 @@
|
||||
}
|
||||
}
|
||||
|
||||
&.--error {
|
||||
border-color: @formErrorBorder !important;
|
||||
|
||||
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & {
|
||||
> button {
|
||||
color: @formErrorText;
|
||||
span.caret {
|
||||
border-top-color: @formErrorText;
|
||||
}
|
||||
}
|
||||
}
|
||||
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block:not(.--active) > .umb-block-list__block--content > div > & {
|
||||
> button {
|
||||
span.name {
|
||||
&::after {
|
||||
content: "!";
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -15px;
|
||||
min-width: 10px;
|
||||
color: @white;
|
||||
background-color: @ui-active-type;
|
||||
border: 2px solid @white;
|
||||
border-radius: 50%;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
padding: 2px;
|
||||
line-height: 10px;
|
||||
background-color: @formErrorText;
|
||||
font-weight: 900;
|
||||
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: blockelement-inlineblock-editor--badge-bounce;
|
||||
animation-timing-function: ease;
|
||||
@keyframes blockelement-inlineblock-editor--badge-bounce {
|
||||
0% { transform: translateY(0); }
|
||||
20% { transform: translateY(-4px); }
|
||||
40% { transform: translateY(0); }
|
||||
55% { transform: translateY(-2px); }
|
||||
70% { transform: translateY(0); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.blockelement-inlineblock-editor__inner {
|
||||
border-top: 1px solid @gray-8;
|
||||
background-color: @gray-12;
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
ng-focus="block.focus"
|
||||
ng-class="{ '--active': block.active, '--error': parentForm.$invalid && valFormManager.isShowingValidation() }"
|
||||
val-server-property-class="">
|
||||
<i class="icon {{block.content.icon}}"></i>
|
||||
<i class="icon {{block.content.icon}}" aria-hidden="true"></i>
|
||||
<span>{{block.label}}</span>
|
||||
</button>
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
}
|
||||
|
||||
span {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@@ -39,7 +40,42 @@
|
||||
background-color: @ui-active;
|
||||
}
|
||||
|
||||
&.--error {
|
||||
border-color: @formErrorBorder !important;
|
||||
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > .umb-block-list__block--content > div > & {
|
||||
color: @formErrorText;
|
||||
}
|
||||
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block:not(.--active) > .umb-block-list__block--content > div > & {
|
||||
span {
|
||||
&::after {
|
||||
content: "!";
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
right: -15px;
|
||||
min-width: 10px;
|
||||
color: @white;
|
||||
background-color: @ui-active-type;
|
||||
border: 2px solid @white;
|
||||
border-radius: 50%;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
padding: 2px;
|
||||
line-height: 10px;
|
||||
background-color: @formErrorText;
|
||||
font-weight: 900;
|
||||
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: blockelement-inlineblock-editor--badge-bounce;
|
||||
animation-timing-function: ease;
|
||||
@keyframes blockelement-inlineblock-editor--badge-bounce {
|
||||
0% { transform: translateY(0); }
|
||||
20% { transform: translateY(-4px); }
|
||||
40% { transform: translateY(0); }
|
||||
55% { transform: translateY(-2px); }
|
||||
70% { transform: translateY(0); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<div class="blockelement-unsupportedblock-editor blockelement__draggable-element" ng-focus="block.focus">
|
||||
<div class="__header">
|
||||
<i class="icon icon-alert"></i>
|
||||
<i class="icon icon-alert" aria-hidden="true"></i>
|
||||
<span>{{block.label}}</span>
|
||||
</div>
|
||||
<div class="__body">
|
||||
This Block is no longer supported in this context.<br/>
|
||||
You might want to remove this block, or contact your developer to take actions for making this block available again.<br/><br/>
|
||||
<a href="http://our.umbraco.com" target="_blank">Learn about this circumstance</a>
|
||||
<a href="http://our.umbraco.com" target="_blank" rel="noreferrer">Learn about this circumstance</a>
|
||||
<h5>Block data:</h5>
|
||||
<pre ng-bind="block.data | json : 4"></pre>
|
||||
</div>
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
vm.requestRemoveBlockByIndex = function (index) {
|
||||
localizationService.localizeMany(["general_delete", "blockEditor_confirmDeleteBlockMessage", "blockEditor_confirmDeleteBlockNotice"]).then(function (data) {
|
||||
var contentElementType = vm.getElementTypeByKey($scope.model.value[index].contentTypeKey);
|
||||
var contentElementType = vm.getElementTypeByKey($scope.model.value[index].contentElementTypeKey);
|
||||
overlayService.confirmDelete({
|
||||
title: data[0],
|
||||
content: localizationService.tokenReplace(data[1], [contentElementType.name]),
|
||||
@@ -82,7 +82,7 @@
|
||||
vm.getAvailableElementTypes = function () {
|
||||
return vm.elementTypes.filter(function (type) {
|
||||
return !$scope.model.value.find(function (entry) {
|
||||
return type.key === entry.contentTypeKey;
|
||||
return type.key === entry.contentElementTypeKey;
|
||||
});
|
||||
});
|
||||
};
|
||||
@@ -99,7 +99,7 @@
|
||||
|
||||
//we have to add the 'alias' property to the objects, to meet the data requirements of itempicker.
|
||||
var selectedItems = Utilities.copy($scope.model.value).forEach((obj) => {
|
||||
obj.alias = vm.getElementTypeByKey(obj.contentTypeKey).alias;
|
||||
obj.alias = vm.getElementTypeByKey(obj.contentElementTypeKey).alias;
|
||||
return obj;
|
||||
});
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
vm.addBlockFromElementTypeKey = function(key) {
|
||||
|
||||
var entry = {
|
||||
"contentTypeKey": key,
|
||||
"contentElementTypeKey": key,
|
||||
"settingsElementTypeKey": null,
|
||||
"labelTemplate": "",
|
||||
"view": null,
|
||||
@@ -178,7 +178,7 @@
|
||||
|
||||
vm.openBlockOverlay = function (block) {
|
||||
|
||||
localizationService.localize("blockEditor_blockConfigurationOverlayTitle", [vm.getElementTypeByKey(block.contentTypeKey).name]).then(function (data) {
|
||||
localizationService.localize("blockEditor_blockConfigurationOverlayTitle", [vm.getElementTypeByKey(block.contentElementTypeKey).name]).then(function (data) {
|
||||
|
||||
var clonedBlockData = Utilities.copy(block);
|
||||
vm.openBlock = block;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<umb-block-card
|
||||
block-config-model="block"
|
||||
element-type-model="vm.getElementTypeByKey(block.contentTypeKey)"
|
||||
element-type-model="vm.getElementTypeByKey(block.contentElementTypeKey)"
|
||||
ng-repeat="block in model.value"
|
||||
ng-class="{'--isOpen':vm.openBlock === block}"
|
||||
ng-click="vm.openBlockOverlay(block)">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
height: 100%;
|
||||
margin-right: 20px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
return elementTypeResource.getAll().then(function(elementTypes) {
|
||||
vm.elementTypes = elementTypes;
|
||||
|
||||
vm.contentPreview = vm.getElementTypeByKey(vm.block.contentTypeKey);
|
||||
vm.contentPreview = vm.getElementTypeByKey(vm.block.contentElementTypeKey);
|
||||
vm.settingsPreview = vm.getElementTypeByKey(vm.block.settingsElementTypeKey);
|
||||
});
|
||||
}
|
||||
@@ -46,7 +46,7 @@
|
||||
}
|
||||
};
|
||||
editorService.documentTypeEditor(editor);
|
||||
}
|
||||
};
|
||||
|
||||
vm.createElementTypeAndCallback = function(callback) {
|
||||
const editor = {
|
||||
@@ -62,7 +62,7 @@
|
||||
}
|
||||
};
|
||||
editorService.documentTypeEditor(editor);
|
||||
}
|
||||
};
|
||||
|
||||
vm.addSettingsForBlock = function($event, block) {
|
||||
|
||||
@@ -95,11 +95,12 @@
|
||||
};
|
||||
|
||||
overlayService.open(elemTypeSelectorOverlay);
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
vm.applySettingsToBlock = function(block, key) {
|
||||
block.settingsElementTypeKey = key;
|
||||
vm.settingsPreview = vm.getElementTypeByKey(vm.block.settingsElementTypeKey);
|
||||
};
|
||||
|
||||
vm.requestRemoveSettingsForBlock = function(block) {
|
||||
@@ -120,11 +121,11 @@
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
vm.removeSettingsForBlock = function(block) {
|
||||
block.settingsElementTypeKey = null;
|
||||
};
|
||||
|
||||
|
||||
function updateUsedElementTypes(event, args) {
|
||||
var key = args.documentType.key;
|
||||
for (var i = 0; i<vm.elementTypes.length; i++) {
|
||||
@@ -140,13 +141,12 @@
|
||||
vm.settingsPreview = args.documentType;
|
||||
$scope.$evalAsync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
unsubscribe.push(eventsService.on("editors.documentType.saved", updateUsedElementTypes));
|
||||
|
||||
|
||||
vm.addViewForBlock = function(block) {
|
||||
localizationService.localize("blockEditor_headlineSelectView").then(function(localizedTitle) {
|
||||
localizationService.localize("blockEditor_headlineAddCustomView").then(function (localizedTitle) {
|
||||
|
||||
const filePicker = {
|
||||
title: localizedTitle,
|
||||
@@ -159,7 +159,7 @@
|
||||
},
|
||||
select: function (node) {
|
||||
const filepath = decodeURIComponent(node.id.replace(/\+/g, " "));
|
||||
block.view = filepath;
|
||||
block.view = "~/" + filepath;
|
||||
editorService.close();
|
||||
},
|
||||
close: function () {
|
||||
@@ -169,7 +169,8 @@
|
||||
editorService.treePicker(filePicker);
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
vm.requestRemoveViewForBlock = function(block) {
|
||||
localizationService.localizeMany(["general_remove", "defaultdialogs_confirmremoveusageof"]).then(function (data) {
|
||||
overlayService.confirmRemove({
|
||||
@@ -185,12 +186,11 @@
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
vm.removeViewForBlock = function(block) {
|
||||
block.view = null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
vm.addStylesheetForBlock = function(block) {
|
||||
localizationService.localize("blockEditor_headlineAddCustomStylesheet").then(function (localizedTitle) {
|
||||
|
||||
@@ -205,7 +205,7 @@
|
||||
},
|
||||
select: function (node) {
|
||||
const filepath = decodeURIComponent(node.id.replace(/\+/g, " "));
|
||||
block.stylesheet = filepath;
|
||||
block.stylesheet = "~/" + filepath;
|
||||
editorService.close();
|
||||
},
|
||||
close: function () {
|
||||
@@ -215,7 +215,8 @@
|
||||
editorService.treePicker(filePicker);
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
vm.requestRemoveStylesheetForBlock = function(block) {
|
||||
localizationService.localizeMany(["general_remove", "defaultdialogs_confirmremoveusageof"]).then(function (data) {
|
||||
overlayService.confirmRemove({
|
||||
@@ -231,12 +232,11 @@
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
vm.removeStylesheetForBlock = function(block) {
|
||||
block.stylesheet = null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
vm.addThumbnailForBlock = function(block) {
|
||||
|
||||
localizationService.localize("blockEditor_headlineAddThumbnail").then(function (localizedTitle) {
|
||||
@@ -250,8 +250,10 @@
|
||||
filter: function (i) {
|
||||
return !(i.name.indexOf(".jpg") !== -1 || i.name.indexOf(".jpeg") !== -1 || i.name.indexOf(".png") !== -1 || i.name.indexOf(".svg") !== -1 || i.name.indexOf(".webp") !== -1 || i.name.indexOf(".gif") !== -1);
|
||||
},
|
||||
filterCssClass: "not-allowed",
|
||||
select: function (file) {
|
||||
block.thumbnail = file.name;
|
||||
const id = decodeURIComponent(file.id.replace(/\+/g, " "));
|
||||
block.thumbnail = "~/" + id;
|
||||
editorService.close();
|
||||
},
|
||||
close: function () {
|
||||
@@ -261,25 +263,23 @@
|
||||
editorService.treePicker(thumbnailPicker);
|
||||
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
vm.removeThumbnailForBlock = function(entry) {
|
||||
entry.thumbnail = null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
vm.submit = function() {
|
||||
if ($scope.model && $scope.model.submit) {
|
||||
$scope.model.submit($scope.model);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
vm.close = function() {
|
||||
if ($scope.model && $scope.model.close) {
|
||||
$scope.model.close($scope.model);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
unsubscribe.forEach(u => { u(); });
|
||||
|
||||
@@ -106,10 +106,10 @@
|
||||
<div class="umb-el-wrap">
|
||||
<label class="control-label"><localize key="blockEditor_labelContentElementType">Content ElementType</localize></label>
|
||||
<div class="controls">
|
||||
<div class="__settings-input --hasValue" ng-if="vm.block.contentTypeKey !== null" >
|
||||
<div class="__settings-input --hasValue" ng-if="vm.block.contentElementTypeKey !== null" >
|
||||
<umb-node-preview icon="vm.contentPreview.icon" name="vm.contentPreview.name" alias="vm.contentPreview.alias"></umb-node-preview>
|
||||
<div class="__control-actions">
|
||||
<button type="button" class="btn-reset __control-actions-btn --open umb-outline" ng-click="vm.openElementType(vm.block.contentTypeKey)">
|
||||
<button type="button" class="btn-reset __control-actions-btn --open umb-outline" ng-click="vm.openElementType(vm.block.contentElementTypeKey)">
|
||||
<i class="icon icon-edit"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -227,6 +227,7 @@
|
||||
|
||||
<umb-button
|
||||
action="vm.close()"
|
||||
shortcut="esc"
|
||||
button-style="link"
|
||||
label-key="general_close"
|
||||
type="button">
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
class="btn-reset umb-block-list__create-button umb-outline"
|
||||
ng-class="{ '--disabled': vm.availableBlockTypes.length === 0 }"
|
||||
ng-click="vm.showCreateDialog(vm.layout.length, $event)">
|
||||
<localize key="grid_addElement"></localize>
|
||||
<localize key="grid_addElement">Add content</localize>
|
||||
</button>
|
||||
|
||||
<input type="hidden" name="minCount" ng-model="vm.layout" val-server="minCount" />
|
||||
|
||||
@@ -39,6 +39,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
ng-form.ng-invalid-val-server-match-settings > .umb-block-list__block > .umb-block-list__block--actions {
|
||||
opacity: 1;
|
||||
}
|
||||
.umb-block-list__block--actions {
|
||||
position: absolute;
|
||||
z-index:999999999;// We always want to be on top of custom view, but we need to make sure we still are behind relevant Umbraco CMS UI. ToDo: Needs further testing.
|
||||
@@ -58,15 +61,92 @@
|
||||
&:hover {
|
||||
color: @ui-action-discreet-type-hover;
|
||||
}
|
||||
> .__error-badge {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: -2px;
|
||||
min-width: 8px;
|
||||
color: @white;
|
||||
background-color: @ui-active-type;
|
||||
border: 2px solid @white;
|
||||
border-radius: 50%;
|
||||
font-size: 8px;
|
||||
font-weight: bold;
|
||||
padding: 2px;
|
||||
line-height: 8px;
|
||||
background-color: @red;
|
||||
display: none;
|
||||
font-weight: 900;
|
||||
}
|
||||
&.--error > .__error-badge {
|
||||
display: block;
|
||||
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: umb-block-list__action--badge-bounce;
|
||||
animation-timing-function: ease;
|
||||
@keyframes umb-block-list__action--badge-bounce {
|
||||
0% { transform: translateY(0); }
|
||||
20% { transform: translateY(-4px); }
|
||||
40% { transform: translateY(0); }
|
||||
55% { transform: translateY(-2px); }
|
||||
70% { transform: translateY(0); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.umb-block-list__block--content {
|
||||
|
||||
> div {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: @umb-block-list__item_minimum_height;
|
||||
background-color: @white;
|
||||
border-radius: @baseBorderRadius;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
&.--show-validation {
|
||||
ng-form.ng-invalid-val-server-match-content > .umb-block-list__block > & > div {
|
||||
border: 2px solid @formErrorText;
|
||||
border-radius: @baseBorderRadius;
|
||||
&::after {
|
||||
content: "!";
|
||||
position: absolute;
|
||||
top: -12px;
|
||||
right: -12px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 50%;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
background-color: @errorBackground;
|
||||
color: @errorText;
|
||||
border: 2px solid @white;
|
||||
font-weight: 900;
|
||||
|
||||
animation-duration: 1.4s;
|
||||
animation-iteration-count: infinite;
|
||||
animation-name: umb-block-list__block--content--badge-bounce;
|
||||
animation-timing-function: ease;
|
||||
@keyframes umb-block-list__block--content--badge-bounce {
|
||||
0% { transform: translateY(0); }
|
||||
20% { transform: translateY(-6px); }
|
||||
40% { transform: translateY(0); }
|
||||
55% { transform: translateY(-3px); }
|
||||
70% { transform: translateY(0); }
|
||||
100% { transform: translateY(0); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.blockelement__draggable-element {
|
||||
@@ -98,20 +178,19 @@
|
||||
left: 0;
|
||||
height: 2px;
|
||||
animation: umb-block-list__block--create-button_before 400ms ease-in-out alternate infinite;
|
||||
|
||||
@keyframes umb-block-list__block--create-button_before {
|
||||
0% { opacity: 1; }
|
||||
100% { opacity: 0.5; }
|
||||
}
|
||||
}
|
||||
|
||||
> .__plus {
|
||||
position: absolute;
|
||||
pointer-events: none; // lets stop avoiding the mouse values in JS move event.
|
||||
margin-left: -18px - 10px;
|
||||
margin-top: -18px;
|
||||
margin-bottom: -18px;
|
||||
width: 28px;
|
||||
height: 25px;
|
||||
padding-bottom: 3px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
padding: 0;
|
||||
border-radius: 3em;
|
||||
border: 2px solid @blueMid;
|
||||
display: flex;
|
||||
@@ -122,25 +201,29 @@
|
||||
font-weight: 800;
|
||||
background-color: rgba(255, 255, 255, .96);
|
||||
box-shadow: 0 0 0 2px rgba(255, 255, 255, .96);
|
||||
transform: scale(0);
|
||||
transform: scale(0) translate(-80%, -50%);
|
||||
transition: transform 240ms ease-in;
|
||||
animation: umb-block-list__block--create-button_after 800ms ease-in-out infinite;
|
||||
|
||||
@keyframes umb-block-list__block--create-button_after {
|
||||
0% { color: rgba(@blueMid, 0.8); }
|
||||
50% { color: rgba(@blueMid, 1); }
|
||||
100% { color: rgba(@blueMid, 0.8); }
|
||||
}
|
||||
}
|
||||
|
||||
&:focus {
|
||||
> .__plus {
|
||||
border: 2px solid @ui-outline;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover, &:focus {
|
||||
opacity: 1;
|
||||
transition-duration: 120ms;
|
||||
|
||||
> .__plus {
|
||||
transform: scale(1);
|
||||
transform: scale(1) translate(-80%, -50%);
|
||||
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
<umb-block-list-block stylesheet="{{::vm.layout.$block.config.stylesheet}}"
|
||||
class="umb-block-list__block--content"
|
||||
ng-class="{'blockelement__draggable-element': vm.layout.$block.config.stylesheet, '--show-validation': vm.layout.$block.showValidation === true}"
|
||||
view="{{vm.layout.$block.view}}"
|
||||
api="vm.blockEditorApi"
|
||||
block="vm.layout.$block"
|
||||
@@ -19,6 +20,7 @@
|
||||
<span class="sr-only">
|
||||
<localize key="general_settings">Settings</localize>
|
||||
</span>
|
||||
<div class="__error-badge">!</div>
|
||||
</button>
|
||||
<button type="button" class="btn-reset umb-outline action --copy" localize="title" title="actions_copy"
|
||||
ng-click="vm.blockEditorApi.copyBlock(vm.layout.$block);"
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
var modelObject;
|
||||
|
||||
// Property actions:
|
||||
var copyAllBlocksAction;
|
||||
var deleteAllBlocksAction;
|
||||
var copyAllBlocksAction = null;
|
||||
var deleteAllBlocksAction = null;
|
||||
|
||||
var inlineEditing = false;
|
||||
var liveEditing = true;
|
||||
@@ -50,21 +50,21 @@
|
||||
}
|
||||
vm.currentBlockInFocus = block;
|
||||
block.focus = true;
|
||||
}
|
||||
};
|
||||
|
||||
vm.supportCopy = clipboardService.isSupported();
|
||||
|
||||
vm.layout = []; // The layout object specific to this Block Editor, will be a direct reference from Property Model.
|
||||
vm.availableBlockTypes = []; // Available block entries of this property editor.
|
||||
vm.labels = {};
|
||||
|
||||
var labels = {};
|
||||
vm.labels = labels;
|
||||
localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function (data) {
|
||||
labels.grid_addElement = data[0];
|
||||
labels.content_createEmpty = data[1];
|
||||
vm.labels.grid_addElement = data[0];
|
||||
vm.labels.content_createEmpty = data[1];
|
||||
});
|
||||
|
||||
vm.$onInit = function() {
|
||||
if (!vm.umbVariantContent) {
|
||||
if (vm.umbProperty && !vm.umbVariantContent) {// if we dont have vm.umbProperty, it means we are in the DocumentTypeEditor.
|
||||
// not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope
|
||||
// inheritance is (i.e.infinite editing)
|
||||
var found = angularHelper.traverseScopeChain($scope, s => s && s.vm && s.vm.constructor.name === "umbVariantContentController");
|
||||
@@ -101,24 +101,21 @@
|
||||
scopeOfExistence = vm.umbElementEditorContent.getScope();
|
||||
}
|
||||
|
||||
// Create Model Object, to manage our data for this Block Editor.
|
||||
modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks, scopeOfExistence, $scope);
|
||||
modelObject.load().then(onLoaded);
|
||||
|
||||
copyAllBlocksAction = {
|
||||
labelKey: "clipboard_labelForCopyAllEntries",
|
||||
labelTokens: [vm.model.label],
|
||||
icon: "documents",
|
||||
method: requestCopyAllBlocks,
|
||||
isDisabled: true
|
||||
}
|
||||
};
|
||||
|
||||
deleteAllBlocksAction = {
|
||||
labelKey: 'clipboard_labelForRemoveAllEntries',
|
||||
labelTokens: [],
|
||||
icon: 'trash',
|
||||
method: requestDeleteAllBlocks,
|
||||
isDisabled: true
|
||||
}
|
||||
};
|
||||
|
||||
var propertyActions = [
|
||||
copyAllBlocksAction,
|
||||
@@ -128,6 +125,11 @@
|
||||
if (vm.umbProperty) {
|
||||
vm.umbProperty.setPropertyActions(propertyActions);
|
||||
}
|
||||
|
||||
// Create Model Object, to manage our data for this Block Editor.
|
||||
modelObject = blockEditorService.createModelObject(vm.model.value, vm.model.editor, vm.model.config.blocks, scopeOfExistence, $scope);
|
||||
modelObject.load().then(onLoaded);
|
||||
|
||||
};
|
||||
|
||||
// Called when we save the value, the server may return an updated data and our value is re-synced
|
||||
@@ -193,12 +195,37 @@
|
||||
|
||||
function getDefaultViewForBlock(block) {
|
||||
|
||||
var defaultViewFolderPath = "views/propertyeditors/blocklist/blocklistentryeditors/";
|
||||
|
||||
if (block.config.unsupported === true)
|
||||
return "views/propertyeditors/blocklist/blocklistentryeditors/unsupportedblock/unsupportedblock.editor.html";
|
||||
return defaultViewFolderPath + "unsupportedblock/unsupportedblock.editor.html";
|
||||
|
||||
if (inlineEditing === true)
|
||||
return "views/propertyeditors/blocklist/blocklistentryeditors/inlineblock/inlineblock.editor.html";
|
||||
return "views/propertyeditors/blocklist/blocklistentryeditors/labelblock/labelblock.editor.html";
|
||||
return defaultViewFolderPath + "inlineblock/inlineblock.editor.html";
|
||||
return defaultViewFolderPath + "labelblock/labelblock.editor.html";
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the containing content variant languag and current property culture is transfered along
|
||||
* to the scaffolded content object representing this block.
|
||||
* This is required for validation along with ensuring that the umb-property inheritance is constently maintained.
|
||||
* @param {any} content
|
||||
*/
|
||||
function ensureCultureData(content) {
|
||||
|
||||
if (!content) return;
|
||||
|
||||
if (vm.umbVariantContent.editor.content.language) {
|
||||
// set the scaffolded content's language to the language of the current editor
|
||||
content.language = vm.umbVariantContent.editor.content.language;
|
||||
}
|
||||
// currently we only ever deal with invariant content for blocks so there's only one
|
||||
content.variants[0].tabs.forEach(tab => {
|
||||
tab.properties.forEach(prop => {
|
||||
// set the scaffolded property to the culture of the containing property
|
||||
prop.culture = vm.umbProperty.property.culture;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getBlockObject(entry) {
|
||||
@@ -206,34 +233,24 @@
|
||||
|
||||
if (block === null) return null;
|
||||
|
||||
// ensure that the containing content variant language/culture is transfered along
|
||||
// to the scaffolded content object representing this block. This is required for validation
|
||||
// along with ensuring that the umb-property inheritance is constently maintained.
|
||||
if (vm.umbVariantContent.editor.content.language) {
|
||||
block.content.language = vm.umbVariantContent.editor.content.language;
|
||||
// currently we only ever deal with invariant content for blocks so there's only one
|
||||
block.content.variants[0].tabs.forEach(tab => {
|
||||
tab.properties.forEach(prop => {
|
||||
prop.culture = vm.umbVariantContent.editor.content.language.culture;
|
||||
});
|
||||
});
|
||||
}
|
||||
ensureCultureData(block.content);
|
||||
ensureCultureData(block.settings);
|
||||
|
||||
// TODO: Why is there a '/' prefixed? that means this will never work with virtual directories
|
||||
block.view = (block.config.view ? "/" + block.config.view : getDefaultViewForBlock(block));
|
||||
block.view = (block.config.view ? block.config.view : getDefaultViewForBlock(block));
|
||||
block.showValidation = block.config.view ? true : false;
|
||||
|
||||
block.hideContentInOverlay = block.config.forceHideContentEditorInOverlay === true || inlineEditing === true;
|
||||
block.showSettings = block.config.settingsElementTypeKey != null;
|
||||
block.showCopy = vm.supportCopy && block.config.contentTypeKey != null;// if we have content, otherwise it doesn't make sense to copy.
|
||||
block.showCopy = vm.supportCopy && block.config.contentElementTypeKey != null;// if we have content, otherwise it doesn't make sense to copy.
|
||||
|
||||
return block;
|
||||
}
|
||||
|
||||
|
||||
function addNewBlock(index, contentTypeKey) {
|
||||
function addNewBlock(index, contentElementTypeKey) {
|
||||
|
||||
// Create layout entry. (not added to property model jet.)
|
||||
var layoutEntry = modelObject.create(contentTypeKey);
|
||||
var layoutEntry = modelObject.create(contentElementTypeKey);
|
||||
if (layoutEntry === null) {
|
||||
return false;
|
||||
}
|
||||
@@ -271,8 +288,12 @@
|
||||
var removed = vm.layout.splice(layoutIndex, 1);
|
||||
removed.forEach(x => {
|
||||
// remove any server validation errors associated
|
||||
var guid = udiService.getKey(x.contentUdi);
|
||||
var guids = [udiService.getKey(x.contentUdi), (x.settingsUdi ? udiService.getKey(x.settingsUdi) : null)];
|
||||
guids.forEach(guid => {
|
||||
if (guid) {
|
||||
serverValidationManager.removePropertyError(guid, vm.umbProperty.property.culture, vm.umbProperty.property.segment, "", { matchType: "contains" });
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
modelObject.removeDataAndDestroyModel(block);
|
||||
@@ -280,9 +301,9 @@
|
||||
}
|
||||
|
||||
function deleteAllBlocks() {
|
||||
vm.layout.forEach(entry => {
|
||||
deleteBlock(entry.$block);
|
||||
});
|
||||
while(vm.layout.length) {
|
||||
deleteBlock(vm.layout[0].$block);
|
||||
};
|
||||
}
|
||||
|
||||
function activateBlock(blockObject) {
|
||||
@@ -325,7 +346,6 @@
|
||||
openSettings: openSettings === true,
|
||||
liveEditing: liveEditing,
|
||||
title: blockObject.label,
|
||||
index: blockIndex,
|
||||
view: "views/common/infiniteeditors/blockeditor/blockeditor.html",
|
||||
size: blockObject.config.editorSize || "medium",
|
||||
submit: function(blockEditorModel) {
|
||||
@@ -403,7 +423,7 @@
|
||||
submit: function(blockPickerModel, mouseEvent) {
|
||||
var added = false;
|
||||
if (blockPickerModel && blockPickerModel.selectedItem) {
|
||||
added = addNewBlock(createIndex, blockPickerModel.selectedItem.blockConfigModel.contentTypeKey);
|
||||
added = addNewBlock(createIndex, blockPickerModel.selectedItem.blockConfigModel.contentElementTypeKey);
|
||||
}
|
||||
|
||||
if(!(mouseEvent.ctrlKey || mouseEvent.metaKey)) {
|
||||
@@ -439,6 +459,7 @@
|
||||
blockPickerModel.clipboardItems.push(
|
||||
{
|
||||
type: "elementType",
|
||||
date: entry.date,
|
||||
pasteData: entry.data,
|
||||
blockConfigModel: modelObject.getScaffoldFromAlias(entry.alias),
|
||||
elementTypeModel: {
|
||||
@@ -454,6 +475,7 @@
|
||||
blockPickerModel.clipboardItems.push(
|
||||
{
|
||||
type: "elementTypeArray",
|
||||
date: entry.date,
|
||||
pasteData: entry.data,
|
||||
blockConfigModel: {}, // no block configuration for paste items of elementTypeArray.
|
||||
elementTypeModel: {
|
||||
@@ -464,6 +486,10 @@
|
||||
);
|
||||
});
|
||||
|
||||
blockPickerModel.clipboardItems.sort( (a, b) => {
|
||||
return b.date - a.date
|
||||
});
|
||||
|
||||
// open block picker overlay
|
||||
editorService.open(blockPickerModel);
|
||||
|
||||
@@ -570,6 +596,7 @@
|
||||
|
||||
vm.sortableOptions = {
|
||||
axis: "y",
|
||||
containment: "parent",
|
||||
cursor: "grabbing",
|
||||
handle: ".blockelement__draggable-element",
|
||||
cancel: "input,textarea,select,option",
|
||||
@@ -586,30 +613,31 @@
|
||||
function onAmountOfBlocksChanged() {
|
||||
|
||||
// enable/disable property actions
|
||||
if (copyAllBlocksAction) {
|
||||
copyAllBlocksAction.isDisabled = vm.layout.length === 0;
|
||||
}
|
||||
if (deleteAllBlocksAction) {
|
||||
deleteAllBlocksAction.isDisabled = vm.layout.length === 0;
|
||||
}
|
||||
|
||||
// validate limits:
|
||||
if (vm.propertyForm) {
|
||||
if (vm.propertyForm && vm.validationLimit) {
|
||||
|
||||
var isMinRequirementGood = vm.validationLimit.min === null || vm.layout.length >= vm.validationLimit.min;
|
||||
vm.propertyForm.minCount.$setValidity("minCount", isMinRequirementGood);
|
||||
|
||||
var isMaxRequirementGood = vm.validationLimit.max === null || vm.layout.length <= vm.validationLimit.max;
|
||||
vm.propertyForm.maxCount.$setValidity("maxCount", isMaxRequirementGood);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
unsubscribe.push($scope.$watch(() => vm.layout.length, onAmountOfBlocksChanged));
|
||||
|
||||
|
||||
$scope.$on("$destroy", function () {
|
||||
for (const subscription of unsubscribe) {
|
||||
subscription();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
})();
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
}
|
||||
);
|
||||
|
||||
function BlockListBlockController($scope, $compile, $element) {
|
||||
function BlockListBlockController($scope, $compile, $element, umbRequestHelper) {
|
||||
var model = this;
|
||||
|
||||
model.$onInit = function () {
|
||||
@@ -48,8 +48,6 @@
|
||||
$scope.valFormManager = model.valFormManager;
|
||||
|
||||
if (model.stylesheet) {
|
||||
// TODO: Not sure why this needs a prefixed /? this means it will never work in a virtual directory
|
||||
model.stylesheet = "/" + model.stylesheet;
|
||||
var shadowRoot = $element[0].attachShadow({ mode: 'open' });
|
||||
shadowRoot.innerHTML = `
|
||||
<style>
|
||||
|
||||
@@ -18,7 +18,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo
|
||||
|
||||
//ensure when form is saved that we don't store [] or [null] as string values in the database when no items are selected
|
||||
$scope.$on("formSubmitting", function () {
|
||||
if ($scope.model.value !== null && ($scope.model.value.length === 0 || $scope.model.value[0] === null)) {
|
||||
if ($scope.model.value && ($scope.model.value.length === 0 || $scope.model.value[0] === null)) {
|
||||
$scope.model.value = null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<ng-form name="gridItemConfigEditor" val-form-manager>
|
||||
|
||||
<umb-box ng-if="model.config">
|
||||
<umb-box-header title-key="grid_settings" />
|
||||
<umb-box-header title-key="grid_settings"></umb-box-header>
|
||||
<umb-box-content>
|
||||
<div>
|
||||
<umb-property property="configValue"
|
||||
@@ -26,7 +26,7 @@
|
||||
</umb-box>
|
||||
|
||||
<umb-box ng-if="model.styles">
|
||||
<umb-box-header title-key="grid_styles" />
|
||||
<umb-box-header title-key="grid_styles"></umb-box-header>
|
||||
<umb-box-content>
|
||||
<div>
|
||||
<umb-property property="styleValue"
|
||||
|
||||
@@ -19,10 +19,10 @@ angular.module("umbraco")
|
||||
|
||||
$scope.setEmbed = function () {
|
||||
|
||||
var original = Utilities.isObject($scope.control.value) ? $scope.control.value : null;
|
||||
var modify = Utilities.isObject($scope.control.value) ? $scope.control.value : null;
|
||||
|
||||
var embed = {
|
||||
original: original,
|
||||
modify: modify,
|
||||
submit: function (model) {
|
||||
|
||||
var embed = {
|
||||
|
||||
@@ -167,8 +167,7 @@
|
||||
icon: 'trash',
|
||||
method: removeAllEntries,
|
||||
isDisabled: true
|
||||
}
|
||||
|
||||
};
|
||||
// helper to force the current form into the dirty state
|
||||
function setDirty() {
|
||||
if ($scope.$parent.$parent.propertyForm) {
|
||||
@@ -243,6 +242,7 @@
|
||||
_.each(singleEntriesForPaste, function (entry) {
|
||||
dialog.pasteItems.push({
|
||||
type: "elementType",
|
||||
date: entry.date,
|
||||
name: entry.label,
|
||||
data: entry.data,
|
||||
icon: entry.icon
|
||||
@@ -253,12 +253,17 @@
|
||||
_.each(arrayEntriesForPaste, function (entry) {
|
||||
dialog.pasteItems.push({
|
||||
type: "elementTypeArray",
|
||||
date: entry.date,
|
||||
name: entry.label,
|
||||
data: entry.data,
|
||||
icon: entry.icon
|
||||
});
|
||||
});
|
||||
|
||||
vm.overlayMenu.pasteItems.sort( (a, b) => {
|
||||
return b.date - a.date
|
||||
});
|
||||
|
||||
dialog.title = dialog.pasteItems.length > 0 ? labels.grid_addElement : labels.content_createEmpty;
|
||||
|
||||
dialog.clickClearPaste = function ($event) {
|
||||
@@ -294,7 +299,7 @@
|
||||
return (vm.nodes.length > vm.minItems)
|
||||
? true
|
||||
: model.config.contentTypes.length > 1;
|
||||
}
|
||||
};
|
||||
|
||||
function deleteNode(idx) {
|
||||
var removed = vm.nodes.splice(idx, 1);
|
||||
@@ -309,6 +314,7 @@
|
||||
updateModel();
|
||||
validate();
|
||||
};
|
||||
|
||||
vm.requestDeleteNode = function (idx) {
|
||||
if (!vm.canDeleteNode(idx)) {
|
||||
return;
|
||||
@@ -396,10 +402,11 @@
|
||||
|
||||
var scaffold = getScaffold(model.value[idx].ncContentTypeAlias);
|
||||
return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
|
||||
}
|
||||
};
|
||||
|
||||
vm.sortableOptions = {
|
||||
axis: "y",
|
||||
containment: "parent",
|
||||
cursor: "move",
|
||||
handle: '.umb-nested-content__header-bar',
|
||||
distance: 10,
|
||||
|
||||
@@ -36,11 +36,12 @@ function RelationTypeCreateController($scope, $location, relationTypeResource, n
|
||||
var currentPath = node.path ? node.path : "-1";
|
||||
navigationService.syncTree({ tree: "relationTypes", path: currentPath + "," + data, forceReload: true, activate: true });
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createRelationTypeForm });
|
||||
|
||||
var currentSection = appState.getSectionState("currentSection");
|
||||
$location.path("/" + currentSection + "/relationTypes/edit/" + data);
|
||||
}, function (err) {
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: this.createRelationTypeForm, hasErrors: true });
|
||||
if (err.data && err.data.message) {
|
||||
notificationsService.error(err.data.message);
|
||||
navigationService.hideMenu();
|
||||
|
||||
@@ -116,12 +116,13 @@ function RelationTypeEditController($scope, $routeParams, relationTypeResource,
|
||||
vm.page.saveButtonState = "busy";
|
||||
|
||||
relationTypeResource.save(vm.relationType).then(function (data) {
|
||||
formHelper.resetForm({ scope: $scope, notifications: data.notifications });
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
bindRelationType(data);
|
||||
|
||||
vm.page.saveButtonState = "success";
|
||||
|
||||
}, function (error) {
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
contentEditingHelper.handleSaveError({
|
||||
err: error
|
||||
});
|
||||
|
||||
@@ -40,12 +40,13 @@
|
||||
activate: true
|
||||
});
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: form });
|
||||
|
||||
var section = appState.getSectionState("currentSection");
|
||||
|
||||
}, function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: form, hasErrors: true });
|
||||
vm.createFolderError = err;
|
||||
|
||||
});
|
||||
|
||||
@@ -42,10 +42,11 @@
|
||||
activate: true
|
||||
});
|
||||
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: form });
|
||||
|
||||
}, function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, formCtrl: form, hasErrors: true });
|
||||
vm.createFolderError = err;
|
||||
|
||||
});
|
||||
|
||||
@@ -168,7 +168,9 @@
|
||||
extendedSave(saved).then(function (result) {
|
||||
//if all is good, then reset the form
|
||||
formHelper.resetForm({ scope: $scope });
|
||||
}, Utilities.noop);
|
||||
}, function () {
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
});
|
||||
|
||||
vm.user = _.omit(saved, "navigation");
|
||||
//restore
|
||||
@@ -179,7 +181,7 @@
|
||||
vm.page.saveButtonState = "success";
|
||||
|
||||
}, function (err) {
|
||||
|
||||
formHelper.resetForm({ scope: $scope, hasErrors: true });
|
||||
contentEditingHelper.handleSaveError({
|
||||
err: err,
|
||||
showNotifications: true
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
}));
|
||||
|
||||
|
||||
var blockConfigurationMock = { contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", label: "Test label", settingsElementTypeKey: null, view: "testview.html" };
|
||||
var blockConfigurationMock = { contentElementTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", label: "Test label", settingsElementTypeKey: null, view: "/testview.html" };
|
||||
|
||||
var propertyModelMock = {
|
||||
layout: {
|
||||
@@ -60,7 +60,7 @@
|
||||
]
|
||||
};
|
||||
|
||||
var blockWithSettingsConfigurationMock = { contentTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", label: "Test label", settingsElementTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", view: "testview.html" };
|
||||
var blockWithSettingsConfigurationMock = { contentElementTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", label: "Test label", settingsElementTypeKey: "7C5B74D1-E2F9-45A3-AE4B-FC7A829BF8AB", view: "/testview.html" };
|
||||
var propertyModelWithSettingsMock = {
|
||||
layout: {
|
||||
"Umbraco.TestBlockEditor": [
|
||||
@@ -105,7 +105,7 @@
|
||||
it('getBlockConfiguration provide the requested block configurtion', function () {
|
||||
var modelObject = blockEditorService.createModelObject({}, "Umbraco.TestBlockEditor", [blockConfigurationMock], $scope, $scope);
|
||||
|
||||
expect(modelObject.getBlockConfiguration(blockConfigurationMock.contentTypeKey).label).toBe(blockConfigurationMock.label);
|
||||
expect(modelObject.getBlockConfiguration(blockConfigurationMock.contentElementTypeKey).label).toBe(blockConfigurationMock.label);
|
||||
});
|
||||
|
||||
it('load provides data for itemPicker', function (done) {
|
||||
@@ -115,7 +115,7 @@
|
||||
try {
|
||||
var itemPickerOptions = modelObject.getAvailableBlocksForBlockPicker();
|
||||
expect(itemPickerOptions.length).toBe(1);
|
||||
expect(itemPickerOptions[0].blockConfigModel.contentTypeKey).toBe(blockConfigurationMock.contentTypeKey);
|
||||
expect(itemPickerOptions[0].blockConfigModel.contentElementTypeKey).toBe(blockConfigurationMock.contentElementTypeKey);
|
||||
done();
|
||||
} catch (e) {
|
||||
done.fail(e);
|
||||
@@ -139,7 +139,7 @@
|
||||
expect(layout).not.toBeUndefined();
|
||||
expect(layout.length).toBe(1);
|
||||
expect(layout[0]).toBe(propertyModelMock.layout["Umbraco.TestBlockEditor"][0]);
|
||||
expect(layout[0].udi).toBe(propertyModelMock.layout["Umbraco.TestBlockEditor"][0].udi);
|
||||
expect(layout[0].contentUdi).toBe(propertyModelMock.layout["Umbraco.TestBlockEditor"][0].contentUdi);
|
||||
|
||||
done();
|
||||
} catch (e) {
|
||||
|
||||
@@ -6,6 +6,13 @@ describe('umbRequestHelper tests', function () {
|
||||
|
||||
beforeEach(inject(function ($injector) {
|
||||
umbRequestHelper = $injector.get('umbRequestHelper');
|
||||
|
||||
// set the Umbraco.Sys.ServerVariables.application.applicationPath
|
||||
if (!Umbraco) Umbraco = {};
|
||||
if (!Umbraco.Sys) Umbraco.Sys = {};
|
||||
if (!Umbraco.Sys.ServerVariables) Umbraco.Sys.ServerVariables = {};
|
||||
if (!Umbraco.Sys.ServerVariables.application) Umbraco.Sys.ServerVariables.application = {};
|
||||
Umbraco.Sys.ServerVariables.application.applicationPath = "/mysite/";
|
||||
}));
|
||||
|
||||
describe('formatting Urls', function () {
|
||||
@@ -34,4 +41,25 @@ describe('umbRequestHelper tests', function () {
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('Virtual Paths', function () {
|
||||
|
||||
it('can convert virtual path to absolute url', function () {
|
||||
var result = umbRequestHelper.convertVirtualToAbsolutePath("~/App_Plugins/hello/world.css");
|
||||
expect(result).toBe("/mysite/App_Plugins/hello/world.css");
|
||||
});
|
||||
|
||||
it('can convert absolute path to absolute url', function () {
|
||||
var result = umbRequestHelper.convertVirtualToAbsolutePath("/App_Plugins/hello/world.css");
|
||||
expect(result).toBe("/App_Plugins/hello/world.css");
|
||||
});
|
||||
|
||||
it('throws on invalid virtual path', function () {
|
||||
var relativePath = "App_Plugins/hello/world.css";
|
||||
expect(function () {
|
||||
umbRequestHelper.convertVirtualToAbsolutePath(relativePath);
|
||||
}).toThrow("The path " + relativePath + " is not a virtual path");
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -648,6 +648,7 @@
|
||||
<key alias="design">Design</key>
|
||||
<key alias="dictionary">Ordbog</key>
|
||||
<key alias="dimensions">Dimensioner</key>
|
||||
<key alias="discard">Kassér</key>
|
||||
<key alias="down">Ned</key>
|
||||
<key alias="download">Hent</key>
|
||||
<key alias="edit">Rediger</key>
|
||||
@@ -1854,6 +1855,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="tabBlockSettings">Indstillinger</key>
|
||||
<key alias="headlineAdvanced">Avanceret</key>
|
||||
<key alias="forceHideContentEditor">Skjuld indholds editoren</key>
|
||||
<key alias="blockHasChanges">Du har lavet ændringer til dette indhold. Er du sikker på at du vil kassere dem?</key>
|
||||
</area>
|
||||
|
||||
</language>
|
||||
|
||||
@@ -676,6 +676,7 @@
|
||||
<key alias="design">Design</key>
|
||||
<key alias="dictionary">Dictionary</key>
|
||||
<key alias="dimensions">Dimensions</key>
|
||||
<key alias="discard">Discard</key>
|
||||
<key alias="down">Down</key>
|
||||
<key alias="download">Download</key>
|
||||
<key alias="edit">Edit</key>
|
||||
@@ -2479,6 +2480,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="tabBlockSettings">Settings</key>
|
||||
<key alias="headlineAdvanced">Advanced</key>
|
||||
<key alias="forceHideContentEditor">Force hide content editor</key>
|
||||
<key alias="blockHasChanges">You have made changes to this content. Are you sure you want to discard them?</key>
|
||||
</area>
|
||||
<area alias="contentTemplatesDashboard">
|
||||
<key alias="whatHeadline">What are Content Templates?</key>
|
||||
|
||||
@@ -684,6 +684,7 @@
|
||||
<key alias="design">Design</key>
|
||||
<key alias="dictionary">Dictionary</key>
|
||||
<key alias="dimensions">Dimensions</key>
|
||||
<key alias="discard">Discard</key>
|
||||
<key alias="down">Down</key>
|
||||
<key alias="download">Download</key>
|
||||
<key alias="edit">Edit</key>
|
||||
@@ -2500,6 +2501,7 @@ To manage your website, simply open the Umbraco back office and start adding con
|
||||
<key alias="tabBlockSettings">Settings</key>
|
||||
<key alias="headlineAdvanced">Advanced</key>
|
||||
<key alias="forceHideContentEditor">Force hide content editor</key>
|
||||
<key alias="blockHasChanges">You have made changes to this content. Are you sure you want to discard them?</key>
|
||||
</area>
|
||||
<area alias="contentTemplatesDashboard">
|
||||
<key alias="whatHeadline">What are Content Templates?</key>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
@inherits UmbracoViewPage<BlockListModel>
|
||||
@using Umbraco.Core.Models.Blocks
|
||||
@{
|
||||
if (Model?.Layout == null || !Model.Layout.Any()) { return; }
|
||||
if (!Model.Any()) { return; }
|
||||
}
|
||||
<div class="umb-block-list">
|
||||
@foreach (var layout in Model.Layout)
|
||||
@foreach (var block in Model)
|
||||
{
|
||||
if (layout?.Udi == null) { continue; }
|
||||
var data = layout.Data;
|
||||
@Html.Partial("BlockList/Components/" + data.ContentType.Alias, layout)
|
||||
if (block?.ContentUdi == null) { continue; }
|
||||
var data = block.Content;
|
||||
@Html.Partial("BlockList/Components/" + data.ContentType.Alias, block)
|
||||
}
|
||||
</div>
|
||||
|
||||
@@ -36,7 +36,9 @@
|
||||
|
||||
@helper renderRow(dynamic row, bool singleColumn){
|
||||
<div @RenderElementAttributes(row)>
|
||||
@Html.If(singleColumn, "<div class='container'>")
|
||||
@if (singleColumn) {
|
||||
@:<div class="container">
|
||||
}
|
||||
<div class="row clearfix">
|
||||
@foreach ( var area in row.areas ) {
|
||||
<div class="col-md-@area.grid column">
|
||||
@@ -49,7 +51,9 @@
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
@Html.If(singleColumn, "</div>")
|
||||
@if (singleColumn) {
|
||||
@:</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
"element": "[data-element='editor-data-type-picker'] [data-element='datatypeconfig-Textarea'] > a",
|
||||
"element": "[data-element='editor-data-type-picker'] [data-element='datatypeconfig-Textarea']",
|
||||
"title": "Editor settings",
|
||||
"content": "Each property editor can have individual settings. For the textarea editor you can set a character limit but in this case it is not needed.",
|
||||
"event": "click"
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Web
|
||||
|
||||
public static MvcHtmlString GetBlockListHtml(this HtmlHelper html, BlockListModel model, string template = DefaultTemplate)
|
||||
{
|
||||
if (model?.Layout == null || !model.Layout.Any()) return new MvcHtmlString(string.Empty);
|
||||
if (model?.Count == 0) return new MvcHtmlString(string.Empty);
|
||||
|
||||
var view = DefaultFolder + template;
|
||||
return html.Partial(view, model);
|
||||
|
||||
@@ -48,20 +48,11 @@ namespace Umbraco.Web.Models.Mapping
|
||||
}
|
||||
|
||||
public ContentTypeBasic GetContentType(IContentBase source, MapperContext context)
|
||||
{
|
||||
// TODO: We can resolve the UmbracoContext from the IValueResolver options!
|
||||
// OMG
|
||||
if (HttpContext.Current != null && Composing.Current.UmbracoContext != null && Composing.Current.UmbracoContext.Security.CurrentUser != null
|
||||
&& Composing.Current.UmbracoContext.Security.CurrentUser.AllowedSections.Any(x => x.Equals(Constants.Applications.Settings)))
|
||||
{
|
||||
var contentType = _contentTypeBaseServiceProvider.GetContentTypeOf(source);
|
||||
var contentTypeBasic = context.Map<IContentTypeComposition, ContentTypeBasic>(contentType);
|
||||
|
||||
return contentTypeBasic;
|
||||
}
|
||||
//no access
|
||||
return null;
|
||||
}
|
||||
|
||||
public string GetTreeNodeUrl<TController>(IContentBase source)
|
||||
where TController : ContentTreeControllerBase
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
using System.Web.Razor.Parser.SyntaxTree;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Models;
|
||||
@@ -12,7 +10,6 @@ using Umbraco.Core.Models.Blocks;
|
||||
using Umbraco.Core.Models.Editors;
|
||||
using Umbraco.Core.PropertyEditors;
|
||||
using Umbraco.Core.Services;
|
||||
using static Umbraco.Core.Models.Blocks.BlockEditorData;
|
||||
using static Umbraco.Core.Models.Blocks.BlockItemData;
|
||||
|
||||
namespace Umbraco.Web.PropertyEditors
|
||||
@@ -49,7 +46,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
internal class BlockEditorPropertyValueEditor : DataValueEditor, IDataValueReference
|
||||
{
|
||||
private readonly PropertyEditorCollection _propertyEditors;
|
||||
private readonly IDataTypeService _dataTypeService; // TODO: Not used yet but we'll need it to fill in the FromEditor/ToEditor
|
||||
private readonly IDataTypeService _dataTypeService;
|
||||
private readonly ILogger _logger;
|
||||
private readonly BlockEditorValues _blockEditorValues;
|
||||
|
||||
@@ -60,7 +57,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
_dataTypeService = dataTypeService;
|
||||
_logger = logger;
|
||||
_blockEditorValues = new BlockEditorValues(new BlockListEditorDataConverter(), contentTypeService, _logger);
|
||||
Validators.Add(new BlockEditorValidator(_blockEditorValues, propertyEditors, dataTypeService, textService));
|
||||
Validators.Add(new BlockEditorValidator(_blockEditorValues, propertyEditors, dataTypeService, textService, contentTypeService));
|
||||
Validators.Add(new MinMaxValidator(_blockEditorValues, textService));
|
||||
}
|
||||
|
||||
@@ -241,19 +238,33 @@ namespace Umbraco.Web.PropertyEditors
|
||||
public IEnumerable<ValidationResult> Validate(object value, string valueType, object dataTypeConfiguration)
|
||||
{
|
||||
var blockConfig = (BlockListConfiguration)dataTypeConfiguration;
|
||||
if (blockConfig == null) yield break;
|
||||
|
||||
var validationLimit = blockConfig.ValidationLimit;
|
||||
if (validationLimit == null) yield break;
|
||||
|
||||
var blockEditorData = _blockEditorValues.DeserializeAndClean(value);
|
||||
if ((blockEditorData == null && blockConfig?.ValidationLimit?.Min > 0)
|
||||
|| (blockEditorData != null && blockEditorData.Layout.Count() < blockConfig?.ValidationLimit?.Min))
|
||||
|
||||
if ((blockEditorData == null && validationLimit.Min.HasValue && validationLimit.Min > 0)
|
||||
|| (blockEditorData != null && validationLimit.Min.HasValue && blockEditorData.Layout.Count() < validationLimit.Min))
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_textService.Localize("validation/entriesShort", new[] { blockConfig.ValidationLimit.Min.ToString(), (blockConfig.ValidationLimit.Min - blockEditorData.Layout.Count()).ToString() }),
|
||||
_textService.Localize("validation/entriesShort", new[]
|
||||
{
|
||||
validationLimit.Min.ToString(),
|
||||
(validationLimit.Min - blockEditorData.Layout.Count()).ToString()
|
||||
}),
|
||||
new[] { "minCount" });
|
||||
}
|
||||
|
||||
if (blockEditorData != null && blockEditorData.Layout.Count() > blockConfig?.ValidationLimit?.Max)
|
||||
if (blockEditorData != null && validationLimit.Max.HasValue && blockEditorData.Layout.Count() > validationLimit.Max)
|
||||
{
|
||||
yield return new ValidationResult(
|
||||
_textService.Localize("validation/entriesExceed", new[] { blockConfig.ValidationLimit.Max.ToString(), (blockEditorData.Layout.Count() - blockConfig.ValidationLimit.Max).ToString() }),
|
||||
_textService.Localize("validation/entriesExceed", new[]
|
||||
{
|
||||
validationLimit.Max.ToString(),
|
||||
(blockEditorData.Layout.Count() - validationLimit.Max).ToString()
|
||||
}),
|
||||
new[] { "maxCount" });
|
||||
}
|
||||
}
|
||||
@@ -262,10 +273,13 @@ namespace Umbraco.Web.PropertyEditors
|
||||
internal class BlockEditorValidator : ComplexEditorValidator
|
||||
{
|
||||
private readonly BlockEditorValues _blockEditorValues;
|
||||
private readonly IContentTypeService _contentTypeService;
|
||||
|
||||
public BlockEditorValidator(BlockEditorValues blockEditorValues, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService) : base(propertyEditors, dataTypeService, textService)
|
||||
public BlockEditorValidator(BlockEditorValues blockEditorValues, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, ILocalizedTextService textService, IContentTypeService contentTypeService)
|
||||
: base(propertyEditors, dataTypeService, textService)
|
||||
{
|
||||
_blockEditorValues = blockEditorValues;
|
||||
_contentTypeService = contentTypeService;
|
||||
}
|
||||
|
||||
protected override IEnumerable<ElementTypeValidationModel> GetElementTypeValidation(object value)
|
||||
@@ -273,8 +287,28 @@ namespace Umbraco.Web.PropertyEditors
|
||||
var blockEditorData = _blockEditorValues.DeserializeAndClean(value);
|
||||
if (blockEditorData != null)
|
||||
{
|
||||
foreach (var row in blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData))
|
||||
// There is no guarantee that the client will post data for every property defined in the Element Type but we still
|
||||
// need to validate that data for each property especially for things like 'required' data to work.
|
||||
// Lookup all element types for all content/settings and then we can populate any empty properties.
|
||||
var allElements = blockEditorData.BlockValue.ContentData.Concat(blockEditorData.BlockValue.SettingsData).ToList();
|
||||
var allElementTypes = _contentTypeService.GetAll(allElements.Select(x => x.ContentTypeKey).ToArray()).ToDictionary(x => x.Key);
|
||||
|
||||
foreach (var row in allElements)
|
||||
{
|
||||
if (!allElementTypes.TryGetValue(row.ContentTypeKey, out var elementType))
|
||||
throw new InvalidOperationException($"No element type found with key {row.ContentTypeKey}");
|
||||
|
||||
// now ensure missing properties
|
||||
foreach (var elementTypeProp in elementType.CompositionPropertyTypes)
|
||||
{
|
||||
if (!row.PropertyValues.ContainsKey(elementTypeProp.Alias))
|
||||
{
|
||||
// set values to null
|
||||
row.PropertyValues[elementTypeProp.Alias] = new BlockPropertyValue(null, elementTypeProp);
|
||||
row.RawPropertyValues[elementTypeProp.Alias] = null;
|
||||
}
|
||||
}
|
||||
|
||||
var elementValidation = new ElementTypeValidationModel(row.ContentTypeAlias, row.Key);
|
||||
foreach (var prop in row.PropertyValues)
|
||||
{
|
||||
@@ -369,11 +403,7 @@ namespace Umbraco.Web.PropertyEditors
|
||||
else
|
||||
{
|
||||
// set the value to include the resolved property type
|
||||
propValues[prop.Key] = new BlockPropertyValue
|
||||
{
|
||||
PropertyType = propType,
|
||||
Value = prop.Value
|
||||
};
|
||||
propValues[prop.Key] = new BlockPropertyValue(prop.Value, propType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user