diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js new file mode 100644 index 0000000000..d7e44df113 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbdatetimepicker.directive.js @@ -0,0 +1,179 @@ +/** +@ngdoc directive +@name umbraco.directives.directive:umbDateTimePicker +@restrict E +@scope + +@description +Added in Umbraco version 7.6 +This directive is a wrapper of the bootstrap datetime picker version 3.1.3. Use it to render a date time picker. +For extra details about options and events take a look here: http://eonasdan.github.io/bootstrap-datetimepicker/ + +Use this directive to render a date time picker + +

Markup example

+
+	
+ + + + +
+
+ +

Controller example

+
+	(function () {
+		"use strict";
+
+		function Controller() {
+
+            var vm = this;
+
+            vm.date = "";
+
+            vm.config = {
+                pickDate: true,
+                pickTime: true,
+                useSeconds: true,
+                format: "YYYY-MM-DD HH:mm:ss",
+                icons: {
+                    time: "icon-time",
+                    date: "icon-calendar",
+                    up: "icon-chevron-up",
+                    down: "icon-chevron-down"
+                }
+            };
+
+            vm.datePickerChange = datePickerChange;
+            vm.datePickerError = datePickerError;
+
+            function datePickerChange(event) {
+                // handle change
+                if(event.date && event.date.isValid()) {
+                    var date = event.date.format(vm.datePickerConfig.format);
+                }
+            }
+
+            function datePickerError(event) {
+                // handle error
+            }
+
+        }
+
+		angular.module("umbraco").controller("My.Controller", Controller);
+
+	})();
+
+ +@param {object} options (binding): Config object for the date picker. +@param {callback} onHide (callback): Hide callback. +@param {callback} onShow (callback): Show callback. +@param {callback} onChange (callback): Change callback. +@param {callback} onError (callback): Error callback. +@param {callback} onUpdate (callback): Update callback. +**/ + +(function () { + 'use strict'; + + function DateTimePickerDirective(assetsService) { + + function link(scope, element, attrs, ctrl) { + + function onInit() { + // load css file for the date picker + assetsService.loadCss('lib/datetimepicker/bootstrap-datetimepicker.min.css'); + + // load the js file for the date picker + assetsService.loadJs('lib/datetimepicker/bootstrap-datetimepicker.js').then(function () { + // init date picker + initDatePicker(); + }); + } + + function onHide(event) { + if (scope.onHide) { + scope.$apply(function(){ + // callback + scope.onHide({event: event}); + }); + } + } + + function onShow() { + if (scope.onShow) { + scope.$apply(function(){ + // callback + scope.onShow(); + }); + } + } + + function onChange(event) { + if (scope.onChange && event.date && event.date.isValid()) { + scope.$apply(function(){ + // callback + scope.onChange({event: event}); + }); + } + } + + function onError(event) { + if (scope.onError) { + scope.$apply(function(){ + // callback + scope.onError({event:event}); + }); + } + } + + function onUpdate(event) { + if (scope.onUpdate) { + scope.$apply(function(){ + // callback + scope.onUpdate({event: event}); + }); + } + } + + function initDatePicker() { + // Open the datepicker and add a changeDate eventlistener + element + .datetimepicker(scope.options) + .on("dp.hide", onHide) + .on("dp.show", onShow) + .on("dp.change", onChange) + .on("dp.error", onError) + .on("dp.update", onUpdate); + } + + onInit(); + + } + + var directive = { + restrict: 'E', + replace: true, + templateUrl: 'views/components/umb-date-time-picker.html', + scope: { + options: "=", + onHide: "&", + onShow: "&", + onChange: "&", + onError: "&", + onUpdate: "&" + }, + link: link + }; + + return directive; + + } + + angular.module('umbraco.directives').directive('umbDateTimePicker', DateTimePickerDirective); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-querybuilder.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-querybuilder.less index fb6279608e..cf72c04b1b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-querybuilder.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-querybuilder.less @@ -23,4 +23,19 @@ .umb-querybuilder .row > div { padding: 20px 0; border-bottom: 1px solid @grayLighter; +} + +.umb-querybuilder .datepicker input { + width: 90px; +} + +.umb-querybuilder .query-items { + display: flex; + flex-wrap: wrap; + align-items: center; +} + +.umb-querybuilder .query-items > * { + flex: 0 1 auto; + margin: 5px; } \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js index bc5bc10e13..d04b7f35a3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.controller.js @@ -9,6 +9,12 @@ vm.contentTypes = []; vm.conditions = []; + vm.datePickerConfig = { + pickDate: true, + pickTime: false, + format: "YYYY-MM-DD" + }; + vm.query = { contentType: { name: "Everything" @@ -41,6 +47,7 @@ vm.setFilterProperty = setFilterProperty; vm.setFilterTerm = setFilterTerm; vm.changeConstraintValue = changeConstraintValue; + vm.datePickerChange = datePickerChange; function onInit() { @@ -58,7 +65,7 @@ .then(function (conditions) { vm.conditions = conditions; }); - + throttledFunc(); } @@ -134,18 +141,28 @@ function setFilterProperty(filter, property) { filter.property = property; - throttledFunc(); + filter.term = {}; + filter.constraintValue = ""; } function setFilterTerm(filter, term) { filter.term = term; - throttledFunc(); + if(filter.constraintValue) { + throttledFunc(); + } } function changeConstraintValue() { throttledFunc(); } + function datePickerChange(event, filter) { + if(event.date && event.date.isValid()) { + filter.constraintValue = event.date.format(vm.datePickerConfig.format); + throttledFunc(); + } + } + var throttledFunc = _.throttle(function () { templateQueryResource.postTemplateQuery(vm.query) @@ -154,7 +171,6 @@ }); }, 200); - onInit(); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html index 40fb909390..36f729adb0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/overlays/querybuilder/querybuilder.html @@ -2,9 +2,10 @@
-
-
- I want +
+
+ + I want
@@ -30,7 +31,7 @@
-
+
Where And @@ -66,8 +67,22 @@
- + + + + + + + + + + + + + + @@ -79,9 +94,9 @@
-
+
- Order by + Order by diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index c274892c65..558f94d387 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -26,7 +26,10 @@ ng-model="name" ng-class="{'name-is-empty': $parent.name===null || $parent.name===''}" umb-auto-focus + val-server-field="Name" required /> +
Required name
+
{{ name }}
diff --git a/src/Umbraco.Web.UI.Client/src/views/components/umb-date-time-picker.html b/src/Umbraco.Web.UI.Client/src/views/components/umb-date-time-picker.html new file mode 100644 index 0000000000..6b37381113 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/umb-date-time-picker.html @@ -0,0 +1,6 @@ +
+ + + + +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js index d8f8557fa5..02d5a62432 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/datatypes/datatype.edit.controller.js @@ -178,8 +178,6 @@ function DataTypeEditController($scope, $routeParams, $location, appState, navig //share state editorState.set($scope.content); - - dataTypeHelper.rebindChangedProperties($scope.content, data); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index a2cc29a0d4..f661b82e7b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -123,6 +123,7 @@ // save state of master template to use for comparison when syncing the tree on save oldMasterTemplateAlias = angular.copy(template.masterTemplateAlias); + // ace configuration vm.aceOption = { mode: "razor", theme: "chrome", @@ -133,15 +134,23 @@ onLoad: function(_editor) { vm.editor = _editor; - //initial cursor placement - vm.editor.navigateFileEnd(); - persistCurrentLocation(); + // initial cursor placement + // Keep cursor in name field if we are create a new template + // else set the cursor at the bottom of the code editor + if(!$routeParams.create) { + $timeout(function(){ + vm.editor.navigateFileEnd(); + vm.editor.focus(); + persistCurrentLocation(); + }); + } //change on blur, focus vm.editor.on("blur", persistCurrentLocation); vm.editor.on("focus", persistCurrentLocation); } } + }; vm.openPageFieldOverlay = openPageFieldOverlay; @@ -193,8 +202,11 @@ }, close: function(oldModel) { + // close the dialog vm.insertOverlay.show = false; vm.insertOverlay = null; + // focus editor + vm.editor.focus(); } }; @@ -216,6 +228,13 @@ vm.macroPickerOverlay.show = false; vm.macroPickerOverlay = null; + }, + close: function(oldModel) { + // close the dialog + vm.macroPickerOverlay.show = false; + vm.macroPickerOverlay = null; + // focus editor + vm.editor.focus(); } }; } @@ -233,8 +252,11 @@ vm.pageFieldOverlay = null; }, close: function (model) { + // close the dialog vm.pageFieldOverlay.show = false; vm.pageFieldOverlay = null; + // focus editor + vm.editor.focus(); } }; } @@ -258,8 +280,11 @@ vm.dictionaryItemOverlay = null; }, close: function (model) { + // close dialog vm.dictionaryItemOverlay.show = false; vm.dictionaryItemOverlay = null; + // focus editor + vm.editor.focus(); } }; } @@ -282,8 +307,11 @@ vm.partialItemOverlay = null; }, close: function (model) { + // close dialog vm.partialItemOverlay.show = false; vm.partialItemOverlay = null; + // focus editor + vm.editor.focus(); } }; } @@ -312,8 +340,11 @@ }, close: function (model) { + // close dialog vm.queryBuilderOverlay.show = false; vm.queryBuilderOverlay = null; + // focus editor + vm.editor.focus(); } }; } @@ -345,10 +376,11 @@ }, close: function(model) { - + // close dialog vm.sectionsOverlay.show = false; vm.sectionsOverlay = null; - + // focus editor + vm.editor.focus(); } } } @@ -386,8 +418,11 @@ vm.masterTemplateOverlay = null; }, close: function(oldModel) { + // close dialog vm.masterTemplateOverlay.show = false; vm.masterTemplateOverlay = null; + // focus editor + vm.editor.focus(); } }; @@ -460,6 +495,7 @@ vm.editor.clearSelection(); vm.editor.navigateFileStart(); + vm.editor.focus(); // set form state to $dirty setFormState("dirty"); diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js index a5009f5eb8..8208999536 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js @@ -47,6 +47,7 @@ getCursorPosition: function() {}, getValue: function() {}, setValue: function() {}, + focus: function() {}, clearSelection: function() {}, navigateFileStart: function() {} }; diff --git a/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs index f4350bc596..0910ec936e 100644 --- a/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeNotificationsController.cs @@ -1,3 +1,4 @@ +using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; namespace Umbraco.Web.Editors @@ -8,6 +9,7 @@ namespace Umbraco.Web.Editors /// currently in the request. /// [AppendCurrentEventMessages] + [PrefixlessBodyModelValidator] public abstract class BackOfficeNotificationsController : UmbracoAuthorizedJsonController { protected BackOfficeNotificationsController() diff --git a/src/Umbraco.Web/Editors/ContentTypeController.cs b/src/Umbraco.Web/Editors/ContentTypeController.cs index 45a2821130..d6e6cbccf4 100644 --- a/src/Umbraco.Web/Editors/ContentTypeController.cs +++ b/src/Umbraco.Web/Editors/ContentTypeController.cs @@ -106,16 +106,7 @@ namespace Umbraco.Web.Editors /// Returns the avilable compositions for this content type /// This has been wrapped in a dto instead of simple parameters to support having multiple parameters in post request body /// - /// - /// - /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out - /// along with any content types that have matching property types that are included in the filtered content types - /// - /// - /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out. - /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot - /// be looked up via the db, they need to be passed in. - /// + /// /// [HttpPost] public HttpResponseMessage GetAvailableCompositeContentTypes(GetAvailableCompositionsFilter filter) diff --git a/src/Umbraco.Web/Editors/DataTypeController.cs b/src/Umbraco.Web/Editors/DataTypeController.cs index 931d00968b..b3e306a48c 100644 --- a/src/Umbraco.Web/Editors/DataTypeController.cs +++ b/src/Umbraco.Web/Editors/DataTypeController.cs @@ -33,7 +33,7 @@ namespace Umbraco.Web.Editors [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.DataTypes, Constants.Trees.DocumentTypes, Constants.Trees.MediaTypes, Constants.Trees.MemberTypes)] [EnableOverrideAuthorization] - public class DataTypeController : UmbracoAuthorizedJsonController + public class DataTypeController : BackOfficeNotificationsController { /// /// Gets data type by name diff --git a/src/Umbraco.Web/Editors/TemplateController.cs b/src/Umbraco.Web/Editors/TemplateController.cs index 54c43b4f29..256f0df45b 100644 --- a/src/Umbraco.Web/Editors/TemplateController.cs +++ b/src/Umbraco.Web/Editors/TemplateController.cs @@ -17,7 +17,7 @@ namespace Umbraco.Web.Editors { [PluginController("UmbracoApi")] [UmbracoTreeAuthorize(Constants.Trees.Templates)] - public class TemplateController : UmbracoAuthorizedJsonController + public class TemplateController : BackOfficeNotificationsController { /// /// Gets data type by alias diff --git a/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs b/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs index d9bc169c8f..61d61f2108 100644 --- a/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs +++ b/src/Umbraco.Web/Models/ContentEditing/GetAvailableCompositionsFilter.cs @@ -3,7 +3,18 @@ namespace Umbraco.Web.Models.ContentEditing public class GetAvailableCompositionsFilter { public int ContentTypeId { get; set; } + + /// + /// This is normally an empty list but if additional property type aliases are passed in, any content types that have these aliases will be filtered out. + /// This is required because in the case of creating/modifying a content type because new property types being added to it are not yet persisted so cannot + /// be looked up via the db, they need to be passed in. + /// public string[] FilterPropertyTypes { get; set; } + + /// + /// This is normally an empty list but if additional content type aliases are passed in, any content types containing those aliases will be filtered out + /// along with any content types that have matching property types that are included in the filtered content types + /// public string[] FilterContentTypes { get; set; } } } \ No newline at end of file diff --git a/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs index 235eb3d360..8e813bd3d3 100644 --- a/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs +++ b/src/Umbraco.Web/Models/ContentEditing/TemplateDisplay.cs @@ -10,7 +10,7 @@ using Umbraco.Core.Models.Validation; namespace Umbraco.Web.Models.ContentEditing { [DataContract(Name = "template", Namespace = "")] - public class TemplateDisplay + public class TemplateDisplay : INotificationModel { [DataMember(Name = "id")] @@ -35,5 +35,11 @@ namespace Umbraco.Web.Models.ContentEditing [DataMember(Name = "masterTemplateAlias")] public string MasterTemplateAlias { get; set; } + + /// + /// This is used to add custom localized messages/strings to the response for the app to use for localized UI purposes. + /// + [DataMember(Name = "notifications")] + public List Notifications { get; private set; } } } diff --git a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs index a1047b8f19..36593736d3 100644 --- a/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs +++ b/src/Umbraco.Web/Models/TemplateQuery/QueryCondition.cs @@ -24,15 +24,34 @@ private static string BuildConditionString(this QueryCondition condition, string prefix, int token = -1) { + + + var operand = string.Empty; var value = string.Empty; var constraintValue = string.Empty; + //if a token is used, use a token placeholder, otherwise, use the actual value if(token >= 0){ constraintValue = string.Format("@{0}", token); }else { - constraintValue = condition.Property.Type == "string" ? string.Format("\"{0}\"", condition.ConstraintValue) : condition.ConstraintValue; + + //modify the format of the constraint value + switch (condition.Property.Type) + { + case "string": + constraintValue = string.Format("\"{0}\"", condition.ConstraintValue); + break; + case "datetime": + constraintValue = string.Format("DateTime.Parse(\"{0}\")", condition.ConstraintValue); + break; + default: + constraintValue = condition.ConstraintValue; + break; + } + + // constraintValue = condition.Property.Type == "string" ? string.Format("\"{0}\"", condition.ConstraintValue) : condition.ConstraintValue; } switch (condition.Term.Operathor) @@ -56,20 +75,23 @@ operand = " <= "; break; case Operathor.Contains: - value = string.Format("{0}{1}.Contains({2})", prefix, condition.Property.Name, constraintValue); + value = string.Format("{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; case Operathor.NotContains: - value = string.Format("!{0}{1}.Contains({2})", prefix, condition.Property.Name, constraintValue); + value = string.Format("!{0}{1}.Contains({2})", prefix, condition.Property.Alias, constraintValue); break; default : operand = " == "; break; } + if (string.IsNullOrEmpty(value) == false) return value; - return string.Format("{0}{1}{2}{3}", prefix, condition.Property.Name, operand, constraintValue); + + + return string.Format("{0}{1}{2}{3}", prefix, condition.Property.Alias, operand, constraintValue); } } diff --git a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs index b54d6102c0..de1383706e 100644 --- a/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs +++ b/src/Umbraco.Web/WebApi/JsonCamelCaseFormatter.cs @@ -8,7 +8,7 @@ using Newtonsoft.Json.Serialization; namespace Umbraco.Web.WebApi { /// - /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. + /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter with a camelCase formatter /// public class JsonCamelCaseFormatter : Attribute, IControllerConfiguration { diff --git a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs index e018881f8a..2593acfbe0 100644 --- a/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs +++ b/src/Umbraco.Web/WebApi/PrefixlessBodyModelValidatorAttribute.cs @@ -6,7 +6,7 @@ using System.Web.Http.Validation; namespace Umbraco.Web.WebApi { /// - /// Applying this attribute to any webapi controller will ensure that it only contains one json formatter compatible with the angular json vulnerability prevention. + /// Applying this attribute to any webapi controller will ensure that the is of type /// internal class PrefixlessBodyModelValidatorAttribute : Attribute, IControllerConfiguration {