diff --git a/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.js b/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.js index 80f54a83b..5e06bb675 100644 --- a/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.js +++ b/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.js @@ -1,28 +1,30 @@ angular .module("LayoutEditor") - .directive("orcLayoutForm", function ($compile, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "y"; - $scope.edit = function () { - $scope.$root.editElement($scope.element).then(function (args) { - if (args.cancel) - return; - $scope.element.data = decodeURIComponent(args.element.data); - $scope.element.name = args.elementEditorModel.name; - $scope.element.formBindingContentType = args.elementEditorModel.formBindingContentType; - $scope.$apply(); - }); - }; - }, - templateUrl: environment.templateUrl("Form"), - replace: true - }; - }); + .directive("orcLayoutForm", ["$compile", "scopeConfigurator", "environment", + function ($compile, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "y"; + $scope.edit = function () { + $scope.$root.editElement($scope.element).then(function (args) { + if (args.cancel) + return; + $scope.element.data = decodeURIComponent(args.element.data); + $scope.element.name = args.elementEditorModel.name; + $scope.element.formBindingContentType = args.elementEditorModel.formBindingContentType; + $scope.$apply(); + }); + }; + }, + templateUrl: environment.templateUrl("Form"), + replace: true + }; + } + ]); var LayoutEditor; (function ($, LayoutEditor) { diff --git a/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.js.bundle b/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.js.bundle index 80ce1e564..4e288f06b 100644 --- a/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.js.bundle +++ b/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.js.bundle @@ -4,7 +4,7 @@ true - false + true diff --git a/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.min.js b/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.min.js index 24a36ca23..837b77fdf 100644 --- a/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.min.js +++ b/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Forms.min.js @@ -1 +1 @@ -angular.module("LayoutEditor").directive("orcLayoutForm",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="y";n.edit=function(){n.$root.editElement(n.element).then(function(t){t.cancel||(n.element.data=decodeURIComponent(t.element.data),n.element.name=t.elementEditorModel.name,n.element.formBindingContentType=t.elementEditorModel.formBindingContentType,n.$apply())})}},templateUrl:i.templateUrl("Form"),replace:!0}});var LayoutEditor;(function(n,t){t.Form=function(i,r,u,f,e,o,s,h,c,l,a,v){var y,p;t.Element.call(this,"Form",i,r,u,f,e);t.Container.call(this,["Grid","Content"],v);y=this;this.isContainable=!0;this.dropTargetClass="layout-common-holder";this.contentType=h;this.contentTypeLabel=c;this.contentTypeClass=l;this.name=o||"Untitled";this.formBindingContentType=s;this.hasEditor=a;this.toObject=function(){var n=this.elementToObject();return n.name=this.name,n.formBindingContentType=this.formBindingContentType,n.children=this.childrenToObject(),n};p=this.getEditorObject;this.getEditorObject=function(){var t=p();return n.extend(t,{FormName:this.name,FormBindingContentType:this.formBindingContentType})};this.setChildren=function(n){this.children=n;_(this.children).each(function(n){n.parent=y;y.linkChild(n)})};this.linkChild=function(t){var i=t.getEditorObject;t.getEditorObject=function(){var t=i();return n.extend(t,{FormBindingContentType:y.formBindingContentType})}};this.setChildren(v)};t.Form.from=function(n){return new t.Form(n.data,n.htmlId,n.htmlClass,n.htmlStyle,n.isTemplated,n.name,n.formBindingContentType,n.contentType,n.contentTypeLabel,n.contentTypeClass,n.hasEditor,t.childrenFrom(n.children))};t.registerFactory("Form",function(n){return t.Form.from(n)})})(jQuery,LayoutEditor||(LayoutEditor={})); \ No newline at end of file +angular.module("LayoutEditor").directive("orcLayoutForm",["$compile","scopeConfigurator","environment",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="y";n.edit=function(){n.$root.editElement(n.element).then(function(t){t.cancel||(n.element.data=decodeURIComponent(t.element.data),n.element.name=t.elementEditorModel.name,n.element.formBindingContentType=t.elementEditorModel.formBindingContentType,n.$apply())})}},templateUrl:i.templateUrl("Form"),replace:!0}}]);var LayoutEditor;(function(n,t){t.Form=function(i,r,u,f,e,o,s,h,c,l,a,v){var y,p;t.Element.call(this,"Form",i,r,u,f,e);t.Container.call(this,["Grid","Content"],v);y=this;this.isContainable=!0;this.dropTargetClass="layout-common-holder";this.contentType=h;this.contentTypeLabel=c;this.contentTypeClass=l;this.name=o||"Untitled";this.formBindingContentType=s;this.hasEditor=a;this.toObject=function(){var n=this.elementToObject();return n.name=this.name,n.formBindingContentType=this.formBindingContentType,n.children=this.childrenToObject(),n};p=this.getEditorObject;this.getEditorObject=function(){var t=p();return n.extend(t,{FormName:this.name,FormBindingContentType:this.formBindingContentType})};this.setChildren=function(n){this.children=n;_(this.children).each(function(n){n.parent=y;y.linkChild(n)})};this.linkChild=function(t){var i=t.getEditorObject;t.getEditorObject=function(){var t=i();return n.extend(t,{FormBindingContentType:y.formBindingContentType})}};this.setChildren(v)};t.Form.from=function(n){return new t.Form(n.data,n.htmlId,n.htmlClass,n.htmlStyle,n.isTemplated,n.name,n.formBindingContentType,n.contentType,n.contentTypeLabel,n.contentTypeClass,n.hasEditor,t.childrenFrom(n.children))};t.registerFactory("Form",function(n){return t.Form.from(n)})})(jQuery,LayoutEditor||(LayoutEditor={})); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Layouts/Directives/Form.js b/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Layouts/Directives/Form.js index 12870d7a2..6c84433ce 100644 --- a/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Layouts/Directives/Form.js +++ b/src/Orchard.Web/Modules/Orchard.DynamicForms/Scripts/Layouts/Directives/Form.js @@ -1,25 +1,27 @@ angular .module("LayoutEditor") - .directive("orcLayoutForm", function ($compile, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "y"; - $scope.edit = function () { - $scope.$root.editElement($scope.element).then(function (args) { - if (args.cancel) - return; - $scope.element.data = decodeURIComponent(args.element.data); - $scope.element.name = args.elementEditorModel.name; - $scope.element.formBindingContentType = args.elementEditorModel.formBindingContentType; - $scope.$apply(); - }); - }; - }, - templateUrl: environment.templateUrl("Form"), - replace: true - }; - }); \ No newline at end of file + .directive("orcLayoutForm", ["$compile", "scopeConfigurator", "environment", + function ($compile, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "y"; + $scope.edit = function () { + $scope.$root.editElement($scope.element).then(function (args) { + if (args.cancel) + return; + $scope.element.data = decodeURIComponent(args.element.data); + $scope.element.name = args.elementEditorModel.name; + $scope.element.formBindingContentType = args.elementEditorModel.formBindingContentType; + $scope.$apply(); + }); + }; + }, + templateUrl: environment.templateUrl("Form"), + replace: true + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/ResourceManifest.cs b/src/Orchard.Web/Modules/Orchard.Layouts/ResourceManifest.cs index 4188906b9..3c5704d2c 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/ResourceManifest.cs +++ b/src/Orchard.Web/Modules/Orchard.Layouts/ResourceManifest.cs @@ -9,9 +9,8 @@ namespace Orchard.Layouts { manifest.DefineScript("AngularSanitize").SetUrl("Lib/angular-sanitize.js").SetDependencies("Angular"); manifest.DefineScript("AngularResource").SetUrl("Lib/angular-resource.js").SetDependencies("Angular"); manifest.DefineScript("Layouts.Models").SetUrl("Models.min.js", "Models.js").SetDependencies("jQuery", "Underscore"); - - // For some reason the minified version doesn't work and causes AngularJS to complain about some directive not being found, so we're only registering the debug version of LayoutEditor.js. - manifest.DefineScript("Layouts.LayoutEditor").SetUrl(/*"LayoutEditor.min.js",*/ "LayoutEditor.js").SetDependencies("Layouts.Models", "AngularResource", "AngularSanitize", "Angular", "jQuery", "Underscore"); + + manifest.DefineScript("Layouts.LayoutEditor").SetUrl("LayoutEditor.min.js", "LayoutEditor.js").SetDependencies("Layouts.Models", "AngularResource", "AngularSanitize", "Angular", "jQuery", "Underscore"); } } } diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.js index 3d1e82837..5a260a36b 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.js @@ -21,889 +21,913 @@ var LayoutEditor; angular .module("LayoutEditor") - .factory("clipboard", function() { - return { - setData: LayoutEditor.Clipboard.setData, - getData: LayoutEditor.Clipboard.getData, - disable: LayoutEditor.Clipboard.disable - }; - }); + .factory("clipboard", [ + function() { + return { + setData: LayoutEditor.Clipboard.setData, + getData: LayoutEditor.Clipboard.getData, + disable: LayoutEditor.Clipboard.disable + }; + } + ]); })(LayoutEditor || (LayoutEditor = {})); angular .module("LayoutEditor") - .factory("scopeConfigurator", function ($timeout, clipboard) { - return { + .factory("scopeConfigurator", ["$timeout", "clipboard", + function ($timeout, clipboard) { + return { - configureForElement: function ($scope, $element) { + configureForElement: function ($scope, $element) { - $element.find(".layout-panel").click(function (e) { - e.stopPropagation(); - }); + $element.find(".layout-panel").click(function (e) { + e.stopPropagation(); + }); - $element.parent().keydown(function (e) { - var handled = false; - var resetFocus = false; - var element = $scope.element; + $element.parent().keydown(function (e) { + var handled = false; + var resetFocus = false; + var element = $scope.element; - if (element.editor.isDragging || element.editor.inlineEditingIsActive) - return; + if (element.editor.isDragging || element.editor.inlineEditingIsActive) + return; - // If the "real" clipboard works, then the pseudo-clipboard will have been disabled. - if (!clipboard.disabled) { - var focusedElement = element.editor.focusedElement; - if (!!focusedElement) { - // Pseudo clipboard handling for browsers not allowing real clipboard operations. - if (e.ctrlKey) { - switch (e.which) { - case 67: // C - focusedElement.copy(clipboard); - break; - case 88: // X - focusedElement.cut(clipboard); - break; - case 86: // V - focusedElement.paste(clipboard); - break; + // If the "real" clipboard works, then the pseudo-clipboard will have been disabled. + if (!clipboard.disabled) { + var focusedElement = element.editor.focusedElement; + if (!!focusedElement) { + // Pseudo clipboard handling for browsers not allowing real clipboard operations. + if (e.ctrlKey) { + switch (e.which) { + case 67: // C + focusedElement.copy(clipboard); + break; + case 88: // X + focusedElement.cut(clipboard); + break; + case 86: // V + focusedElement.paste(clipboard); + break; + } } } } - } - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 46) { // Del - $scope.delete(element); - handled = true; - } else if (!e.ctrlKey && !e.shiftKey && !e.altKey && (e.which == 32 || e.which == 27)) { // Space or Esc - $element.find(".layout-panel-action-properties").first().click(); - handled = true; - } - - if (element.type == "Content") { // This is a content element. - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 13) { // Enter - $element.find(".layout-panel-action-edit").first().click(); + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 46) { // Del + $scope.delete(element); handled = true; - } - } - - if (!!element.children) { // This is a container. - if (!e.ctrlKey && !e.shiftKey && e.altKey && e.which == 40) { // Alt+Down - if (element.children.length > 0) - element.children[0].setIsFocused(); + } else if (!e.ctrlKey && !e.shiftKey && !e.altKey && (e.which == 32 || e.which == 27)) { // Space or Esc + $element.find(".layout-panel-action-properties").first().click(); handled = true; } - if (element.type == "Column") { // This is a column. - var connectAdjacent = !e.ctrlKey; - if (e.which == 37) { // Left - if (e.altKey) - element.expandLeft(connectAdjacent); - if (e.shiftKey) - element.contractRight(connectAdjacent); - handled = true; - } else if (e.which == 39) { // Right - if (e.altKey) - element.contractLeft(connectAdjacent); - if (e.shiftKey) - element.expandRight(connectAdjacent); + if (element.type == "Content") { // This is a content element. + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 13) { // Enter + $element.find(".layout-panel-action-edit").first().click(); handled = true; } } - } - if (!!element.parent) { // This is a child. - if (e.altKey && e.which == 38) { // Alt+Up - element.parent.setIsFocused(); - handled = true; - } - - if (element.parent.type == "Row") { // Parent is a horizontal container. - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 37) { // Left - element.parent.moveFocusPrevChild(element); + if (!!element.children) { // This is a container. + if (!e.ctrlKey && !e.shiftKey && e.altKey && e.which == 40) { // Alt+Down + if (element.children.length > 0) + element.children[0].setIsFocused(); handled = true; } - else if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 39) { // Right - element.parent.moveFocusNextChild(element); - handled = true; - } - else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 37) { // Ctrl+Left - element.moveUp(); - resetFocus = true; - handled = true; - } - else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 39) { // Ctrl+Right - element.moveDown(); - handled = true; - } - } - else { // Parent is a vertical container. - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 38) { // Up - element.parent.moveFocusPrevChild(element); - handled = true; - } - else if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 40) { // Down - element.parent.moveFocusNextChild(element); - handled = true; - } - else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 38) { // Ctrl+Up - element.moveUp(); - resetFocus = true; - handled = true; - } - else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 40) { // Ctrl+Down - element.moveDown(); - handled = true; - } - } - } - if (handled) { - e.preventDefault(); - } - - e.stopPropagation(); - - $scope.$apply(); // Event is not triggered by Angular directive but raw event handler on element. - - // HACK: Workaround because of how Angular treats the DOM when elements are shifted around - input focus is sometimes lost. - if (resetFocus) { - window.setTimeout(function () { - $scope.$apply(function () { - element.editor.focusedElement.setIsFocused(); - }); - }, 100); - } - }); - - $scope.element.setIsFocusedEventHandlers.push(function () { - $element.parent().focus(); - }); - - $scope.delete = function (element) { - element.delete(); - } - }, - - configureForContainer: function ($scope, $element) { - var element = $scope.element; - - //$scope.isReceiving = false; // True when container is receiving an external element via drag/drop. - $scope.getShowChildrenPlaceholder = function () { - return $scope.element.children.length === 0 && !$scope.element.getIsDropTarget(); - }; - - $scope.sortableOptions = { - cursor: "move", - delay: 150, - disabled: element.getIsSealed(), - distance: 5, - //handle: element.children.length < 2 ? ".imaginary-class" : false, // For some reason doesn't get re-evaluated after adding more children. - start: function (e, ui) { - $scope.$apply(function () { - element.setIsDropTarget(true); - element.editor.isDragging = true; - }); - // Make the drop target placeholder as high as the item being dragged. - ui.placeholder.height(ui.item.height() - 4); - ui.placeholder.css("min-height", 0); - }, - stop: function (e, ui) { - $scope.$apply(function () { - element.editor.isDragging = false; - element.setIsDropTarget(false); - }); - }, - over: function (e, ui) { - if (!!ui.sender && !!ui.sender[0].isToolbox) { - if (!!ui.sender[0].dropTargetTimeout) { - $timeout.cancel(ui.sender[0].dropTargetTimeout); - ui.sender[0].dropTargetTimeout = null; - } - $timeout(function () { - if (element.type == "Row") { - // If there was a previous drop target and it was a row, roll back any pending column adds to it. - var previousDropTarget = element.editor.dropTargetElement; - if (!!previousDropTarget && previousDropTarget.type == "Row") - previousDropTarget.rollbackAddColumn(); + if (element.type == "Column") { // This is a column. + var connectAdjacent = !e.ctrlKey; + if (e.which == 37) { // Left + if (e.altKey) + element.expandLeft(connectAdjacent); + if (e.shiftKey) + element.contractRight(connectAdjacent); + handled = true; + } else if (e.which == 39) { // Right + if (e.altKey) + element.contractLeft(connectAdjacent); + if (e.shiftKey) + element.expandRight(connectAdjacent); + handled = true; } - element.setIsDropTarget(false); - }); - ui.sender[0].dropTargetTimeout = $timeout(function () { - if (element.type == "Row") { - var receivedColumn = ui.item.sortable.model; - var receivedColumnWidth = Math.floor(12 / (element.children.length + 1)); - receivedColumn.width = receivedColumnWidth; - receivedColumn.offset = 0; - element.beginAddColumn(receivedColumnWidth); - // Make the drop target placeholder the correct width and as high as the highest existing column in the row. - var maxHeight = _.max(_($element.find("> .layout-children > .layout-column:not(.ui-sortable-placeholder)")).map(function (e) { - return $(e).height(); - })); - for (i = 1; i <= 12; i++) - ui.placeholder.removeClass("col-xs-" + i); - ui.placeholder.addClass("col-xs-" + receivedColumn.width); - if (maxHeight > 0) { - ui.placeholder.height(maxHeight); - ui.placeholder.css("min-height", 0); - } - else { - ui.placeholder.height(0); - ui.placeholder.css("min-height", ""); - } - } - element.setIsDropTarget(true); - }, 150); - } - }, - receive: function (e, ui) { - if (!!ui.sender && !!ui.sender[0].isToolbox) { - $scope.$apply(function () { - var receivedElement = ui.item.sortable.model; - if (!!receivedElement) { - if (element.type == "Row") - element.commitAddColumn(); - // Should ideally call LayoutEditor.Container.addChild() instead, but since this handler - // is run *before* the ui-sortable directive's handler, if we try to add the child to the - // array that handler will get an exception when trying to do the same. - // Because of this, we need to invoke "setParent" so that specific container types can perform element speficic initialization. - receivedElement.setEditor(element.editor); - receivedElement.setParent(element); - if (receivedElement.type == "Content" && !!receivedElement.hasEditor) { - $scope.$root.editElement(receivedElement).then(function (args) { - if (!args.cancel) { - receivedElement.data = decodeURIComponent(args.element.data); - receivedElement.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20"))); - } - $timeout(function () { - if (!!args.cancel) - receivedElement.delete(); - else - receivedElement.setIsFocused(); - //$scope.isReceiving = false; - element.setIsDropTarget(false); - - }); - return; - }); - } - } - $timeout(function () { - //$scope.isReceiving = false; - element.setIsDropTarget(false); - if (!!receivedElement) - receivedElement.setIsFocused(); - }); - }); - } - } - }; - - $scope.click = function (child, e) { - if (!child.editor.isDragging) - child.setIsFocused(); - e.stopPropagation(); - }; - - $scope.getClasses = function (child) { - var result = ["layout-element"]; - - if (!!child.children) { - result.push("layout-container"); - if (child.getIsSealed()) - result.push("layout-container-sealed"); - } - - result.push("layout-" + child.type.toLowerCase()); - - if (!!child.dropTargetClass) - result.push(child.dropTargetClass); - - // TODO: Move these to either the Column directive or the Column model class. - if (child.type == "Row") { - result.push("row"); - if (!child.canAddColumn()) - result.push("layout-row-full"); - } - if (child.type == "Column") { - result.push("col-xs-" + child.width); - result.push("col-xs-offset-" + child.offset); - } - if (child.type == "Content") - result.push("layout-content-" + child.contentTypeClass); - - if (child.getIsActive()) - result.push("layout-element-active"); - if (child.getIsFocused()) - result.push("layout-element-focused"); - if (child.getIsSelected()) - result.push("layout-element-selected"); - if (child.getIsDropTarget()) - result.push("layout-element-droptarget"); - if (child.isTemplated) - result.push("layout-element-templated"); - - return result; - }; - } - } - }); -angular - .module("LayoutEditor") - .directive("orcLayoutEditor", function (environment) { - return { - restrict: "E", - scope: {}, - controller: function ($scope, $element, $attrs, $compile, clipboard) { - if (!!$attrs.model) - $scope.element = eval($attrs.model); - else - throw new Error("The 'model' attribute must evaluate to a LayoutEditor.Editor object."); - - $scope.click = function (canvas, e) { - if (!canvas.editor.isDragging) - canvas.setIsFocused(); - e.stopPropagation(); - }; - - $scope.getClasses = function (canvas) { - var result = ["layout-element", "layout-container", "layout-canvas"]; - - if (canvas.getIsActive()) - result.push("layout-element-active"); - if (canvas.getIsFocused()) - result.push("layout-element-focused"); - if (canvas.getIsSelected()) - result.push("layout-element-selected"); - if (canvas.getIsDropTarget()) - result.push("layout-element-droptarget"); - if (canvas.isTemplated) - result.push("layout-element-templated"); - - return result; - }; - - // An unfortunate side-effect of the next hack on line 54 is that the created elements aren't added to the DOM yet, so we can't use it to get to the parent ".layout-desiger" element. - // Work around: access that element directly (which efectively turns multiple layout editors on a single page impossible). - // //var layoutDesignerHost = $element.closest(".layout-designer").data("layout-designer-host"); - var layoutDesignerHost = $(".layout-designer").data("layout-designer-host"); - - $scope.$root.layoutDesignerHost = layoutDesignerHost; - - layoutDesignerHost.element.on("replacecanvas", function (e, args) { - var editor = $scope.element; - var canvasData = { - data: args.canvas.data, - htmlId: args.canvas.htmlId, - htmlClass: args.canvas.htmlClass, - htmlStyle: args.canvas.htmlStyle, - isTemplated: args.canvas.isTemplated, - children: args.canvas.children - }; - - // HACK: Instead of simply updating the $scope.element with a new instance, we need to replace the entire orc-layout-editor markup - // in order for angular to rebind starting with the Canvas element. Otherwise, for some reason, it will rebind starting with the first child of Canvas. - // You can see this happening when setting a breakpoint in ScopeConfigurator where containers are initialized with drag & drop: on page load, the first element - // is a Canvas (good), but after having selected another template, the first element is (typically) a Grid (bad). - // Simply recompiling the orc-layout-editor directive will cause the entire thing to be generated, which works just fine as well (even though not is nice as simply leveraging model binding). - layoutDesignerHost.editor = window.layoutEditor = new LayoutEditor.Editor(editor.config, canvasData); - var template = ""; - var html = $compile(template)($scope); - $(".layout-editor-holder").html(html); - }); - - $scope.$root.editElement = function (element) { - var host = $scope.$root.layoutDesignerHost; - return host.editElement(element); - }; - - $scope.$root.addElement = function (contentType) { - var host = $scope.$root.layoutDesignerHost; - return host.addElement(contentType); - }; - - $scope.toggleInlineEditing = function () { - if (!$scope.element.inlineEditingIsActive) { - $scope.element.inlineEditingIsActive = true; - $element.find(".layout-toolbar-container").show(); - var selector = "#layout-editor-" + $scope.$id + " .layout-content-h-t-m-l .layout-content-markup[data-templated=false]"; - var firstContentEditorId = $(selector).first().attr("id"); - tinymce.init({ - selector: selector, - theme: "modern", - schema: "html5", - plugins: [ - "advlist autolink lists link image charmap print preview hr anchor pagebreak", - "searchreplace wordcount visualblocks visualchars code fullscreen", - "insertdatetime media nonbreaking table contextmenu directionality", - "emoticons template paste textcolor colorpicker textpattern", - "fullscreen autoresize" - ], - toolbar: "undo redo cut copy paste | bold italic | bullist numlist outdent indent formatselect | alignleft aligncenter alignright alignjustify ltr rtl | link unlink charmap | code fullscreen close", - convert_urls: false, - valid_elements: "*[*]", - // Shouldn't be needed due to the valid_elements setting, but TinyMCE would strip script.src without it. - extended_valid_elements: "script[type|defer|src|language]", - statusbar: false, - skin: "orchardlightgray", - inline: true, - fixed_toolbar_container: "#layout-editor-" + $scope.$id + " .layout-toolbar-container", - init_instance_callback: function (editor) { - if (editor.id == firstContentEditorId) - tinymce.execCommand("mceFocus", false, editor.id); } - }); - } - else { - tinymce.remove("#layout-editor-" + $scope.$id + " .layout-content-markup"); - $element.find(".layout-toolbar-container").hide(); - $scope.element.inlineEditingIsActive = false; - } - }; + } - $(document).on("cut copy paste", function (e) { - // The real clipboard is supported, so disable the peudo clipboard. - clipboard.disable(); - var focusedElement = $scope.element.focusedElement; - if (!!focusedElement) { - $scope.$apply(function () { - switch (e.type) { - case "copy": - focusedElement.copy(e.originalEvent.clipboardData); - break; - case "cut": - focusedElement.cut(e.originalEvent.clipboardData); - break; - case "paste": - focusedElement.paste(e.originalEvent.clipboardData); - break; + if (!!element.parent) { // This is a child. + if (e.altKey && e.which == 38) { // Alt+Up + element.parent.setIsFocused(); + handled = true; } - }); + + if (element.parent.type == "Row") { // Parent is a horizontal container. + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 37) { // Left + element.parent.moveFocusPrevChild(element); + handled = true; + } + else if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 39) { // Right + element.parent.moveFocusNextChild(element); + handled = true; + } + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 37) { // Ctrl+Left + element.moveUp(); + resetFocus = true; + handled = true; + } + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 39) { // Ctrl+Right + element.moveDown(); + handled = true; + } + } + else { // Parent is a vertical container. + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 38) { // Up + element.parent.moveFocusPrevChild(element); + handled = true; + } + else if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 40) { // Down + element.parent.moveFocusNextChild(element); + handled = true; + } + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 38) { // Ctrl+Up + element.moveUp(); + resetFocus = true; + handled = true; + } + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 40) { // Ctrl+Down + element.moveDown(); + handled = true; + } + } + } + + if (handled) { + e.preventDefault(); + } + + e.stopPropagation(); + + $scope.$apply(); // Event is not triggered by Angular directive but raw event handler on element. // HACK: Workaround because of how Angular treats the DOM when elements are shifted around - input focus is sometimes lost. - window.setTimeout(function () { - $scope.$apply(function () { - if (!!$scope.element.focusedElement) - $scope.element.focusedElement.setIsFocused(); - }); - }, 100); - - e.preventDefault(); - } - }); - }, - templateUrl: environment.templateUrl("Editor"), - replace: true, - link: function (scope, element) { - // No clicks should propagate from the TinyMCE toolbars. - element.find(".layout-toolbar-container").click(function (e) { - e.stopPropagation(); - }); - // Intercept mousedown on editor while in inline editing mode to - // prevent current editor from losing focus. - element.mousedown(function (e) { - if (scope.element.inlineEditingIsActive) { - e.preventDefault(); - e.stopPropagation(); - } - }) - // Unfocus and unselect everything on click outside of canvas. - $(window).click(function (e) { - // Except when in inline editing mode. - if (!scope.element.inlineEditingIsActive) { - scope.$apply(function () { - scope.element.activeElement = null; - scope.element.focusedElement = null; - }); - } - }); - } - }; - }); -angular - .module("LayoutEditor") - .directive("orcLayoutCanvas", function (scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element, $attrs) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "y"; - }, - templateUrl: environment.templateUrl("Canvas"), - replace: true - }; - }); -angular - .module("LayoutEditor") - .directive("orcLayoutChild", function ($compile) { - return { - restrict: "E", - scope: { element: "=" }, - link: function (scope, element) { - var template = ""; - var html = $compile(template)(scope); - $(element).replaceWith(html); - } - }; - }); -angular - .module("LayoutEditor") - .directive("orcLayoutColumn", function ($compile, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "y"; - }, - templateUrl: environment.templateUrl("Column"), - replace: true, - link: function (scope, element, attrs) { - element.find(".layout-column-resize-bar").draggable({ - axis: "x", - helper: "clone", - revert: true, - start: function (e, ui) { - scope.$apply(function () { - scope.element.editor.isResizing = true; - }); - }, - drag: function (e, ui) { - var columnElement = element.parent(); - var columnSize = columnElement.width() / scope.element.width; - var connectAdjacent = !e.ctrlKey; - if ($(e.target).hasClass("layout-column-resize-bar-left")) { - var delta = ui.offset.left - columnElement.offset().left; - if (delta < -columnSize && scope.element.canExpandLeft(connectAdjacent)) { - scope.$apply(function () { - scope.element.expandLeft(connectAdjacent); + if (resetFocus) { + window.setTimeout(function () { + $scope.$apply(function () { + element.editor.focusedElement.setIsFocused(); }); - } - else if (delta > columnSize && scope.element.canContractLeft(connectAdjacent)) { - scope.$apply(function () { - scope.element.contractLeft(connectAdjacent); - }); - } + }, 100); } - else if ($(e.target).hasClass("layout-column-resize-bar-right")) { - var delta = ui.offset.left - columnElement.width() - columnElement.offset().left; - if (delta > columnSize && scope.element.canExpandRight(connectAdjacent)) { - scope.$apply(function () { - scope.element.expandRight(connectAdjacent); - }); - } - else if (delta < -columnSize && scope.element.canContractRight(connectAdjacent)) { - scope.$apply(function () { - scope.element.contractRight(connectAdjacent); - }); - } - } - - }, - stop: function (e, ui) { - scope.$apply(function () { - scope.element.editor.isResizing = false; - }); - } - }); - } - }; - }); -angular - .module("LayoutEditor") - .directive("orcLayoutContent", function ($sce, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - $scope.edit = function () { - $scope.$root.editElement($scope.element).then(function (args) { - $scope.$apply(function () { - if (args.cancel) - return; - - $scope.element.data = decodeURIComponent(args.element.data); - $scope.element.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20"))); - }); - }); - }; - $scope.updateContent = function (e) { - $scope.element.setHtml(e.target.innerHTML); - }; - - // Overwrite the setHtml function so that we can use the $sce service to trust the html (and not have the html binding strip certain tags). - $scope.element.setHtml = function (html) { - $scope.element.html = html; - $scope.element.htmlUnsafe = $sce.trustAsHtml(html); - }; - - $scope.element.setHtml(decodeURIComponent($scope.element.html.replace(/\+/g, "%20"))); - }, - templateUrl: environment.templateUrl("Content"), - replace: true, - link: function (scope, element) { - // Mouse down events must not be intercepted by drag and drop while inline editing is active, - // otherwise clicks in inline editors will have no effect. - element.find(".layout-content-markup").mousedown(function (e) { - if (scope.element.editor.inlineEditingIsActive) { - e.stopPropagation(); - } - }); - } - }; - }); -angular - .module("LayoutEditor") - .directive("orcLayoutGrid", function ($compile, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "y"; - }, - templateUrl: environment.templateUrl("Grid"), - replace: true - }; - }); -angular - .module("LayoutEditor") - .directive("orcLayoutRow", function ($compile, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "x"; - $scope.sortableOptions["ui-floating"] = true; - }, - templateUrl: environment.templateUrl("Row"), - replace: true - }; - }); -angular - .module("LayoutEditor") - .directive("orcLayoutPopup", function () { - return { - restrict: "A", - link: function (scope, element, attrs) { - var popup = $(element); - var trigger = popup.closest(".layout-popup-trigger"); - var parentElement = popup.closest(".layout-element"); - trigger.click(function () { - popup.toggle(); - if (popup.is(":visible")) { - popup.position({ - my: attrs.orcLayoutPopupMy || "left top", - at: attrs.orcLayoutPopupAt || "left bottom+4px", - of: trigger - }); - popup.find("input").first().focus(); - } - }); - popup.click(function (e) { - e.stopPropagation(); - }); - parentElement.click(function (e) { - popup.hide(); - }); - popup.keydown(function (e) { - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 27) // Esc - popup.hide(); - e.stopPropagation(); - }); - } - }; - }); -angular - .module("LayoutEditor") - .directive("orcLayoutToolbox", function ($compile, environment) { - return { - restrict: "E", - controller: function ($scope, $element) { - - $scope.resetElements = function () { - - $scope.gridElements = [ - LayoutEditor.Grid.from({ - toolboxIcon: "\uf00a", - toolboxLabel: "Grid", - toolboxDescription: "Empty grid.", - children: [] - }) - ]; - - $scope.rowElements = [ - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (1 column)", - toolboxDescription: "Row with 1 column.", - children: LayoutEditor.Column.times(1) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (2 columns)", - toolboxDescription: "Row with 2 columns.", - children: LayoutEditor.Column.times(2) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (3 columns)", - toolboxDescription: "Row with 3 columns.", - children: LayoutEditor.Column.times(3) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (4 columns)", - toolboxDescription: "Row with 4 columns.", - children: LayoutEditor.Column.times(4) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (6 columns)", - toolboxDescription: "Row with 6 columns.", - children: LayoutEditor.Column.times(6) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (12 columns)", - toolboxDescription: "Row with 12 columns.", - children: LayoutEditor.Column.times(12) - }), LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (empty)", - toolboxDescription: "Empty row.", - children: [] - }) - ]; - - $scope.columnElements = [ - LayoutEditor.Column.from({ - toolboxIcon: "\uf0db", - toolboxLabel: "Column", - toolboxDescription: "Empty column.", - width: 1, - offset: 0, - children: [] - }) - ]; - - $scope.contentElementCategories = _($scope.element.config.categories).map(function (category) { - return { - name: category.name, - elements: _(category.contentTypes).map(function (contentType) { - var type = contentType.type; - var factory = LayoutEditor.factories[type] || LayoutEditor.factories["Content"]; - var item = { - isTemplated: false, - contentType: contentType.id, - contentTypeLabel: contentType.label, - contentTypeClass: contentType.typeClass, - data: null, - hasEditor: contentType.hasEditor, - html: contentType.html - }; - var element = factory(item); - element.toolboxIcon = contentType.icon || "\uf1c9"; - element.toolboxLabel = contentType.label; - element.toolboxDescription = contentType.description; - return element; - }) - }; }); - }; + $scope.element.setIsFocusedEventHandlers.push(function () { + $element.parent().focus(); + }); - $scope.resetElements(); - - $scope.getSortableOptions = function (type) { - var editorId = $element.closest(".layout-editor").attr("id"); - var parentClasses; - var placeholderClasses; - var floating = false; - - switch (type) { - case "Grid": - parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder"]; - placeholderClasses = "layout-element layout-container layout-grid ui-sortable-placeholder"; - break; - case "Row": - parentClasses = [".layout-grid"]; - placeholderClasses = "layout-element layout-container layout-row row ui-sortable-placeholder"; - break; - case "Column": - parentClasses = [".layout-row:not(.layout-row-full)"]; - placeholderClasses = "layout-element layout-container layout-column ui-sortable-placeholder"; - floating = true; // To ensure a smooth horizontal-list reordering. https://github.com/angular-ui/ui-sortable#floating - break; - case "Content": - parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder"]; - placeholderClasses = "layout-element layout-content ui-sortable-placeholder"; - break; + $scope.delete = function (element) { + element.delete(); } + }, - return { + configureForContainer: function ($scope, $element) { + var element = $scope.element; + + //$scope.isReceiving = false; // True when container is receiving an external element via drag/drop. + $scope.getShowChildrenPlaceholder = function () { + return $scope.element.children.length === 0 && !$scope.element.getIsDropTarget(); + }; + + $scope.sortableOptions = { cursor: "move", - connectWith: _(parentClasses).map(function (e) { return "#" + editorId + " " + e + ":not(.layout-container-sealed) > .layout-element-wrapper > .layout-children"; }).join(", "), - placeholder: placeholderClasses, - "ui-floating": floating, - create: function (e, ui) { - e.target.isToolbox = true; // Will indicate to connected sortables that dropped items were sent from toolbox. - }, + delay: 150, + disabled: element.getIsSealed(), + distance: 5, + //handle: element.children.length < 2 ? ".imaginary-class" : false, // For some reason doesn't get re-evaluated after adding more children. start: function (e, ui) { $scope.$apply(function () { - $scope.element.isDragging = true; + element.setIsDropTarget(true); + element.editor.isDragging = true; }); + // Make the drop target placeholder as high as the item being dragged. + ui.placeholder.height(ui.item.height() - 4); + ui.placeholder.css("min-height", 0); }, stop: function (e, ui) { $scope.$apply(function () { - $scope.element.isDragging = false; - $scope.resetElements(); + element.editor.isDragging = false; + element.setIsDropTarget(false); }); }, over: function (e, ui) { - $scope.$apply(function () { - $scope.element.canvas.setIsDropTarget(false); - }); + if (!!ui.sender && !!ui.sender[0].isToolbox) { + if (!!ui.sender[0].dropTargetTimeout) { + $timeout.cancel(ui.sender[0].dropTargetTimeout); + ui.sender[0].dropTargetTimeout = null; + } + $timeout(function () { + if (element.type == "Row") { + // If there was a previous drop target and it was a row, roll back any pending column adds to it. + var previousDropTarget = element.editor.dropTargetElement; + if (!!previousDropTarget && previousDropTarget.type == "Row") + previousDropTarget.rollbackAddColumn(); + } + element.setIsDropTarget(false); + }); + ui.sender[0].dropTargetTimeout = $timeout(function () { + if (element.type == "Row") { + var receivedColumn = ui.item.sortable.model; + var receivedColumnWidth = Math.floor(12 / (element.children.length + 1)); + receivedColumn.width = receivedColumnWidth; + receivedColumn.offset = 0; + element.beginAddColumn(receivedColumnWidth); + // Make the drop target placeholder the correct width and as high as the highest existing column in the row. + var maxHeight = _.max(_($element.find("> .layout-children > .layout-column:not(.ui-sortable-placeholder)")).map(function (e) { + return $(e).height(); + })); + for (i = 1; i <= 12; i++) + ui.placeholder.removeClass("col-xs-" + i); + ui.placeholder.addClass("col-xs-" + receivedColumn.width); + if (maxHeight > 0) { + ui.placeholder.height(maxHeight); + ui.placeholder.css("min-height", 0); + } + else { + ui.placeholder.height(0); + ui.placeholder.css("min-height", ""); + } + } + element.setIsDropTarget(true); + }, 150); + } }, - } - }; + receive: function (e, ui) { + if (!!ui.sender && !!ui.sender[0].isToolbox) { + $scope.$apply(function () { + var receivedElement = ui.item.sortable.model; + if (!!receivedElement) { + if (element.type == "Row") + element.commitAddColumn(); + // Should ideally call LayoutEditor.Container.addChild() instead, but since this handler + // is run *before* the ui-sortable directive's handler, if we try to add the child to the + // array that handler will get an exception when trying to do the same. + // Because of this, we need to invoke "setParent" so that specific container types can perform element speficic initialization. + receivedElement.setEditor(element.editor); + receivedElement.setParent(element); + if (receivedElement.type == "Content" && !!receivedElement.hasEditor) { + $scope.$root.editElement(receivedElement).then(function (args) { + if (!args.cancel) { + receivedElement.data = decodeURIComponent(args.element.data); + receivedElement.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20"))); + } + $timeout(function () { + if (!!args.cancel) + receivedElement.delete(); + else + receivedElement.setIsFocused(); + //$scope.isReceiving = false; + element.setIsDropTarget(false); - var layoutIsCollapsedCookieName = "layoutToolboxCategory_Layout_IsCollapsed"; - $scope.layoutIsCollapsed = $.cookie(layoutIsCollapsedCookieName) === "true"; + }); + return; + }); + } + } + $timeout(function () { + //$scope.isReceiving = false; + element.setIsDropTarget(false); + if (!!receivedElement) + receivedElement.setIsFocused(); + }); + }); + } + } + }; - $scope.toggleLayoutIsCollapsed = function (e) { - $scope.layoutIsCollapsed = !$scope.layoutIsCollapsed; - $.cookie(layoutIsCollapsedCookieName, $scope.layoutIsCollapsed, { expires: 365 }); // Remember collapsed state for a year. - e.preventDefault(); - e.stopPropagation(); - }; - }, - templateUrl: environment.templateUrl("Toolbox"), - replace: true - }; - }); + $scope.click = function (child, e) { + if (!child.editor.isDragging) + child.setIsFocused(); + e.stopPropagation(); + }; + + $scope.getClasses = function (child) { + var result = ["layout-element"]; + + if (!!child.children) { + result.push("layout-container"); + if (child.getIsSealed()) + result.push("layout-container-sealed"); + } + + result.push("layout-" + child.type.toLowerCase()); + + if (!!child.dropTargetClass) + result.push(child.dropTargetClass); + + // TODO: Move these to either the Column directive or the Column model class. + if (child.type == "Row") { + result.push("row"); + if (!child.canAddColumn()) + result.push("layout-row-full"); + } + if (child.type == "Column") { + result.push("col-xs-" + child.width); + result.push("col-xs-offset-" + child.offset); + } + if (child.type == "Content") + result.push("layout-content-" + child.contentTypeClass); + + if (child.getIsActive()) + result.push("layout-element-active"); + if (child.getIsFocused()) + result.push("layout-element-focused"); + if (child.getIsSelected()) + result.push("layout-element-selected"); + if (child.getIsDropTarget()) + result.push("layout-element-droptarget"); + if (child.isTemplated) + result.push("layout-element-templated"); + + return result; + }; + } + }; + } + ]); angular .module("LayoutEditor") - .directive("orcLayoutToolboxGroup", function ($compile, environment) { - return { - restrict: "E", - scope: { category: "=" }, - controller: function ($scope, $element) { - var isCollapsedCookieName = "layoutToolboxCategory_" + $scope.category.name + "_IsCollapsed"; - $scope.isCollapsed = $.cookie(isCollapsedCookieName) === "true"; - $scope.toggleIsCollapsed = function (e) { - $scope.isCollapsed = !$scope.isCollapsed; - $.cookie(isCollapsedCookieName, $scope.isCollapsed, { expires: 365 }); // Remember collapsed state for a year. - e.preventDefault(); - e.stopPropagation(); - }; - }, - templateUrl: environment.templateUrl("ToolboxGroup"), - replace: true - }; - }); + .directive("orcLayoutEditor", ["environment", + function (environment) { + return { + restrict: "E", + scope: {}, + controller: function ($scope, $element, $attrs, $compile, clipboard) { + if (!!$attrs.model) + $scope.element = eval($attrs.model); + else + throw new Error("The 'model' attribute must evaluate to a LayoutEditor.Editor object."); + + $scope.click = function (canvas, e) { + if (!canvas.editor.isDragging) + canvas.setIsFocused(); + e.stopPropagation(); + }; + + $scope.getClasses = function (canvas) { + var result = ["layout-element", "layout-container", "layout-canvas"]; + + if (canvas.getIsActive()) + result.push("layout-element-active"); + if (canvas.getIsFocused()) + result.push("layout-element-focused"); + if (canvas.getIsSelected()) + result.push("layout-element-selected"); + if (canvas.getIsDropTarget()) + result.push("layout-element-droptarget"); + if (canvas.isTemplated) + result.push("layout-element-templated"); + + return result; + }; + + // An unfortunate side-effect of the next hack on line 54 is that the created elements aren't added to the DOM yet, so we can't use it to get to the parent ".layout-desiger" element. + // Work around: access that element directly (which efectively turns multiple layout editors on a single page impossible). + // //var layoutDesignerHost = $element.closest(".layout-designer").data("layout-designer-host"); + var layoutDesignerHost = $(".layout-designer").data("layout-designer-host"); + + $scope.$root.layoutDesignerHost = layoutDesignerHost; + + layoutDesignerHost.element.on("replacecanvas", function (e, args) { + var editor = $scope.element; + var canvasData = { + data: args.canvas.data, + htmlId: args.canvas.htmlId, + htmlClass: args.canvas.htmlClass, + htmlStyle: args.canvas.htmlStyle, + isTemplated: args.canvas.isTemplated, + children: args.canvas.children + }; + + // HACK: Instead of simply updating the $scope.element with a new instance, we need to replace the entire orc-layout-editor markup + // in order for angular to rebind starting with the Canvas element. Otherwise, for some reason, it will rebind starting with the first child of Canvas. + // You can see this happening when setting a breakpoint in ScopeConfigurator where containers are initialized with drag & drop: on page load, the first element + // is a Canvas (good), but after having selected another template, the first element is (typically) a Grid (bad). + // Simply recompiling the orc-layout-editor directive will cause the entire thing to be generated, which works just fine as well (even though not is nice as simply leveraging model binding). + layoutDesignerHost.editor = window.layoutEditor = new LayoutEditor.Editor(editor.config, canvasData); + var template = ""; + var html = $compile(template)($scope); + $(".layout-editor-holder").html(html); + }); + + $scope.$root.editElement = function (element) { + var host = $scope.$root.layoutDesignerHost; + return host.editElement(element); + }; + + $scope.$root.addElement = function (contentType) { + var host = $scope.$root.layoutDesignerHost; + return host.addElement(contentType); + }; + + $scope.toggleInlineEditing = function () { + if (!$scope.element.inlineEditingIsActive) { + $scope.element.inlineEditingIsActive = true; + $element.find(".layout-toolbar-container").show(); + var selector = "#layout-editor-" + $scope.$id + " .layout-content-h-t-m-l .layout-content-markup[data-templated=false]"; + var firstContentEditorId = $(selector).first().attr("id"); + tinymce.init({ + selector: selector, + theme: "modern", + schema: "html5", + plugins: [ + "advlist autolink lists link image charmap print preview hr anchor pagebreak", + "searchreplace wordcount visualblocks visualchars code fullscreen", + "insertdatetime media nonbreaking table contextmenu directionality", + "emoticons template paste textcolor colorpicker textpattern", + "fullscreen autoresize" + ], + toolbar: "undo redo cut copy paste | bold italic | bullist numlist outdent indent formatselect | alignleft aligncenter alignright alignjustify ltr rtl | link unlink charmap | code fullscreen close", + convert_urls: false, + valid_elements: "*[*]", + // Shouldn't be needed due to the valid_elements setting, but TinyMCE would strip script.src without it. + extended_valid_elements: "script[type|defer|src|language]", + statusbar: false, + skin: "orchardlightgray", + inline: true, + fixed_toolbar_container: "#layout-editor-" + $scope.$id + " .layout-toolbar-container", + init_instance_callback: function (editor) { + if (editor.id == firstContentEditorId) + tinymce.execCommand("mceFocus", false, editor.id); + } + }); + } + else { + tinymce.remove("#layout-editor-" + $scope.$id + " .layout-content-markup"); + $element.find(".layout-toolbar-container").hide(); + $scope.element.inlineEditingIsActive = false; + } + }; + + $(document).on("cut copy paste", function (e) { + // The real clipboard is supported, so disable the peudo clipboard. + clipboard.disable(); + var focusedElement = $scope.element.focusedElement; + if (!!focusedElement) { + $scope.$apply(function () { + switch (e.type) { + case "copy": + focusedElement.copy(e.originalEvent.clipboardData); + break; + case "cut": + focusedElement.cut(e.originalEvent.clipboardData); + break; + case "paste": + focusedElement.paste(e.originalEvent.clipboardData); + break; + } + }); + + // HACK: Workaround because of how Angular treats the DOM when elements are shifted around - input focus is sometimes lost. + window.setTimeout(function () { + $scope.$apply(function () { + if (!!$scope.element.focusedElement) + $scope.element.focusedElement.setIsFocused(); + }); + }, 100); + + e.preventDefault(); + } + }); + }, + templateUrl: environment.templateUrl("Editor"), + replace: true, + link: function (scope, element) { + // No clicks should propagate from the TinyMCE toolbars. + element.find(".layout-toolbar-container").click(function (e) { + e.stopPropagation(); + }); + // Intercept mousedown on editor while in inline editing mode to + // prevent current editor from losing focus. + element.mousedown(function (e) { + if (scope.element.inlineEditingIsActive) { + e.preventDefault(); + e.stopPropagation(); + } + }) + // Unfocus and unselect everything on click outside of canvas. + $(window).click(function (e) { + // Except when in inline editing mode. + if (!scope.element.inlineEditingIsActive) { + scope.$apply(function () { + scope.element.activeElement = null; + scope.element.focusedElement = null; + }); + } + }); + } + }; + } + ]); +angular + .module("LayoutEditor") + .directive("orcLayoutCanvas", ["scopeConfigurator", "environment", + function (scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element, $attrs) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "y"; + }, + templateUrl: environment.templateUrl("Canvas"), + replace: true + }; + } + ]); +angular + .module("LayoutEditor") + .directive("orcLayoutChild", ["$compile", + function ($compile) { + return { + restrict: "E", + scope: { element: "=" }, + link: function (scope, element) { + var template = ""; + var html = $compile(template)(scope); + $(element).replaceWith(html); + } + }; + } + ]); +angular + .module("LayoutEditor") + .directive("orcLayoutColumn", ["$compile", "scopeConfigurator", "environment", + function ($compile, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "y"; + }, + templateUrl: environment.templateUrl("Column"), + replace: true, + link: function (scope, element, attrs) { + element.find(".layout-column-resize-bar").draggable({ + axis: "x", + helper: "clone", + revert: true, + start: function (e, ui) { + scope.$apply(function () { + scope.element.editor.isResizing = true; + }); + }, + drag: function (e, ui) { + var columnElement = element.parent(); + var columnSize = columnElement.width() / scope.element.width; + var connectAdjacent = !e.ctrlKey; + if ($(e.target).hasClass("layout-column-resize-bar-left")) { + var delta = ui.offset.left - columnElement.offset().left; + if (delta < -columnSize && scope.element.canExpandLeft(connectAdjacent)) { + scope.$apply(function () { + scope.element.expandLeft(connectAdjacent); + }); + } + else if (delta > columnSize && scope.element.canContractLeft(connectAdjacent)) { + scope.$apply(function () { + scope.element.contractLeft(connectAdjacent); + }); + } + } + else if ($(e.target).hasClass("layout-column-resize-bar-right")) { + var delta = ui.offset.left - columnElement.width() - columnElement.offset().left; + if (delta > columnSize && scope.element.canExpandRight(connectAdjacent)) { + scope.$apply(function () { + scope.element.expandRight(connectAdjacent); + }); + } + else if (delta < -columnSize && scope.element.canContractRight(connectAdjacent)) { + scope.$apply(function () { + scope.element.contractRight(connectAdjacent); + }); + } + } + + }, + stop: function (e, ui) { + scope.$apply(function () { + scope.element.editor.isResizing = false; + }); + } + }); + } + }; + } + ]); +angular + .module("LayoutEditor") + .directive("orcLayoutContent", ["$sce", "scopeConfigurator", "environment", + function ($sce, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + $scope.edit = function () { + $scope.$root.editElement($scope.element).then(function (args) { + $scope.$apply(function () { + if (args.cancel) + return; + + $scope.element.data = decodeURIComponent(args.element.data); + $scope.element.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20"))); + }); + }); + }; + $scope.updateContent = function (e) { + $scope.element.setHtml(e.target.innerHTML); + }; + + // Overwrite the setHtml function so that we can use the $sce service to trust the html (and not have the html binding strip certain tags). + $scope.element.setHtml = function (html) { + $scope.element.html = html; + $scope.element.htmlUnsafe = $sce.trustAsHtml(html); + }; + + $scope.element.setHtml(decodeURIComponent($scope.element.html.replace(/\+/g, "%20"))); + }, + templateUrl: environment.templateUrl("Content"), + replace: true, + link: function (scope, element) { + // Mouse down events must not be intercepted by drag and drop while inline editing is active, + // otherwise clicks in inline editors will have no effect. + element.find(".layout-content-markup").mousedown(function (e) { + if (scope.element.editor.inlineEditingIsActive) { + e.stopPropagation(); + } + }); + } + }; + } + ]); +angular + .module("LayoutEditor") + .directive("orcLayoutGrid", ["$compile", "scopeConfigurator", "environment", + function ($compile, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "y"; + }, + templateUrl: environment.templateUrl("Grid"), + replace: true + }; + } + ]); +angular + .module("LayoutEditor") + .directive("orcLayoutRow", ["$compile", "scopeConfigurator", "environment", + function ($compile, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "x"; + $scope.sortableOptions["ui-floating"] = true; + }, + templateUrl: environment.templateUrl("Row"), + replace: true + }; + } + ]); +angular + .module("LayoutEditor") + .directive("orcLayoutPopup", [ + function () { + return { + restrict: "A", + link: function (scope, element, attrs) { + var popup = $(element); + var trigger = popup.closest(".layout-popup-trigger"); + var parentElement = popup.closest(".layout-element"); + trigger.click(function () { + popup.toggle(); + if (popup.is(":visible")) { + popup.position({ + my: attrs.orcLayoutPopupMy || "left top", + at: attrs.orcLayoutPopupAt || "left bottom+4px", + of: trigger + }); + popup.find("input").first().focus(); + } + }); + popup.click(function (e) { + e.stopPropagation(); + }); + parentElement.click(function (e) { + popup.hide(); + }); + popup.keydown(function (e) { + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 27) // Esc + popup.hide(); + e.stopPropagation(); + }); + } + }; + } + ]); +angular + .module("LayoutEditor") + .directive("orcLayoutToolbox", ["$compile", "environment", + function ($compile, environment) { + return { + restrict: "E", + controller: function ($scope, $element) { + + $scope.resetElements = function () { + + $scope.gridElements = [ + LayoutEditor.Grid.from({ + toolboxIcon: "\uf00a", + toolboxLabel: "Grid", + toolboxDescription: "Empty grid.", + children: [] + }) + ]; + + $scope.rowElements = [ + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (1 column)", + toolboxDescription: "Row with 1 column.", + children: LayoutEditor.Column.times(1) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (2 columns)", + toolboxDescription: "Row with 2 columns.", + children: LayoutEditor.Column.times(2) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (3 columns)", + toolboxDescription: "Row with 3 columns.", + children: LayoutEditor.Column.times(3) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (4 columns)", + toolboxDescription: "Row with 4 columns.", + children: LayoutEditor.Column.times(4) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (6 columns)", + toolboxDescription: "Row with 6 columns.", + children: LayoutEditor.Column.times(6) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (12 columns)", + toolboxDescription: "Row with 12 columns.", + children: LayoutEditor.Column.times(12) + }), LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (empty)", + toolboxDescription: "Empty row.", + children: [] + }) + ]; + + $scope.columnElements = [ + LayoutEditor.Column.from({ + toolboxIcon: "\uf0db", + toolboxLabel: "Column", + toolboxDescription: "Empty column.", + width: 1, + offset: 0, + children: [] + }) + ]; + + $scope.contentElementCategories = _($scope.element.config.categories).map(function (category) { + return { + name: category.name, + elements: _(category.contentTypes).map(function (contentType) { + var type = contentType.type; + var factory = LayoutEditor.factories[type] || LayoutEditor.factories["Content"]; + var item = { + isTemplated: false, + contentType: contentType.id, + contentTypeLabel: contentType.label, + contentTypeClass: contentType.typeClass, + data: null, + hasEditor: contentType.hasEditor, + html: contentType.html + }; + var element = factory(item); + element.toolboxIcon = contentType.icon || "\uf1c9"; + element.toolboxLabel = contentType.label; + element.toolboxDescription = contentType.description; + return element; + }) + }; + }); + + }; + + $scope.resetElements(); + + $scope.getSortableOptions = function (type) { + var editorId = $element.closest(".layout-editor").attr("id"); + var parentClasses; + var placeholderClasses; + var floating = false; + + switch (type) { + case "Grid": + parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder"]; + placeholderClasses = "layout-element layout-container layout-grid ui-sortable-placeholder"; + break; + case "Row": + parentClasses = [".layout-grid"]; + placeholderClasses = "layout-element layout-container layout-row row ui-sortable-placeholder"; + break; + case "Column": + parentClasses = [".layout-row:not(.layout-row-full)"]; + placeholderClasses = "layout-element layout-container layout-column ui-sortable-placeholder"; + floating = true; // To ensure a smooth horizontal-list reordering. https://github.com/angular-ui/ui-sortable#floating + break; + case "Content": + parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder"]; + placeholderClasses = "layout-element layout-content ui-sortable-placeholder"; + break; + } + + return { + cursor: "move", + connectWith: _(parentClasses).map(function (e) { return "#" + editorId + " " + e + ":not(.layout-container-sealed) > .layout-element-wrapper > .layout-children"; }).join(", "), + placeholder: placeholderClasses, + "ui-floating": floating, + create: function (e, ui) { + e.target.isToolbox = true; // Will indicate to connected sortables that dropped items were sent from toolbox. + }, + start: function (e, ui) { + $scope.$apply(function () { + $scope.element.isDragging = true; + }); + }, + stop: function (e, ui) { + $scope.$apply(function () { + $scope.element.isDragging = false; + $scope.resetElements(); + }); + }, + over: function (e, ui) { + $scope.$apply(function () { + $scope.element.canvas.setIsDropTarget(false); + }); + }, + } + }; + + var layoutIsCollapsedCookieName = "layoutToolboxCategory_Layout_IsCollapsed"; + $scope.layoutIsCollapsed = $.cookie(layoutIsCollapsedCookieName) === "true"; + + $scope.toggleLayoutIsCollapsed = function (e) { + $scope.layoutIsCollapsed = !$scope.layoutIsCollapsed; + $.cookie(layoutIsCollapsedCookieName, $scope.layoutIsCollapsed, { expires: 365 }); // Remember collapsed state for a year. + e.preventDefault(); + e.stopPropagation(); + }; + }, + templateUrl: environment.templateUrl("Toolbox"), + replace: true + }; + } + ]); +angular + .module("LayoutEditor") + .directive("orcLayoutToolboxGroup", ["$compile", "environment", + function ($compile, environment) { + return { + restrict: "E", + scope: { category: "=" }, + controller: function ($scope, $element) { + var isCollapsedCookieName = "layoutToolboxCategory_" + $scope.category.name + "_IsCollapsed"; + $scope.isCollapsed = $.cookie(isCollapsedCookieName) === "true"; + $scope.toggleIsCollapsed = function (e) { + $scope.isCollapsed = !$scope.isCollapsed; + $.cookie(isCollapsedCookieName, $scope.isCollapsed, { expires: 365 }); // Remember collapsed state for a year. + e.preventDefault(); + e.stopPropagation(); + }; + }, + templateUrl: environment.templateUrl("ToolboxGroup"), + replace: true + }; + } + ]); diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.min.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.min.js index ff8c51e02..af9c861e6 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.min.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor.min.js @@ -1 +1 @@ -angular.module("LayoutEditor",["ngSanitize","ngResource","ui.sortable"]);var LayoutEditor;(function(n){var t=function(){var n=this;this.clipboardData={};this.setData=function(t,i){n.clipboardData[t]=i};this.getData=function(t){return n.clipboardData[t]};this.disable=function(){this.disabled=!0}};n.Clipboard=new t;angular.module("LayoutEditor").factory("clipboard",function(){return{setData:n.Clipboard.setData,getData:n.Clipboard.getData,disable:n.Clipboard.disable}})})(LayoutEditor||(LayoutEditor={}));angular.module("LayoutEditor").factory("scopeConfigurator",function(n,t){return{configureForElement:function(n,i){i.find(".layout-panel").click(function(n){n.stopPropagation()});i.parent().keydown(function(r){var f=!1,s=!1,u=n.element,e,o;if(!u.editor.isDragging&&!u.editor.inlineEditingIsActive){if(!t.disabled&&(e=u.editor.focusedElement,!!e&&r.ctrlKey))switch(r.which){case 67:e.copy(t);break;case 88:e.cut(t);break;case 86:e.paste(t)}r.ctrlKey||r.shiftKey||r.altKey||r.which!=46?r.ctrlKey||r.shiftKey||r.altKey||r.which!=32&&r.which!=27||(i.find(".layout-panel-action-properties").first().click(),f=!0):(n.delete(u),f=!0);u.type=="Content"&&(r.ctrlKey||r.shiftKey||r.altKey||r.which!=13||(i.find(".layout-panel-action-edit").first().click(),f=!0));!u.children||(r.ctrlKey||r.shiftKey||!r.altKey||r.which!=40||(u.children.length>0&&u.children[0].setIsFocused(),f=!0),u.type=="Column"&&(o=!r.ctrlKey,r.which==37?(r.altKey&&u.expandLeft(o),r.shiftKey&&u.contractRight(o),f=!0):r.which==39&&(r.altKey&&u.contractLeft(o),r.shiftKey&&u.expandRight(o),f=!0)));!u.parent||(r.altKey&&r.which==38&&(u.parent.setIsFocused(),f=!0),u.parent.type=="Row"?r.ctrlKey||r.shiftKey||r.altKey||r.which!=37?r.ctrlKey||r.shiftKey||r.altKey||r.which!=39?!r.ctrlKey||r.shiftKey||r.altKey||r.which!=37?!r.ctrlKey||r.shiftKey||r.altKey||r.which!=39||(u.moveDown(),f=!0):(u.moveUp(),s=!0,f=!0):(u.parent.moveFocusNextChild(u),f=!0):(u.parent.moveFocusPrevChild(u),f=!0):r.ctrlKey||r.shiftKey||r.altKey||r.which!=38?r.ctrlKey||r.shiftKey||r.altKey||r.which!=40?!r.ctrlKey||r.shiftKey||r.altKey||r.which!=38?!r.ctrlKey||r.shiftKey||r.altKey||r.which!=40||(u.moveDown(),f=!0):(u.moveUp(),s=!0,f=!0):(u.parent.moveFocusNextChild(u),f=!0):(u.parent.moveFocusPrevChild(u),f=!0));f&&r.preventDefault();r.stopPropagation();n.$apply();s&&window.setTimeout(function(){n.$apply(function(){u.editor.focusedElement.setIsFocused()})},100)}});n.element.setIsFocusedEventHandlers.push(function(){i.parent().focus()});n.delete=function(n){n.delete()}},configureForContainer:function(t,r){var u=t.element;t.getShowChildrenPlaceholder=function(){return t.element.children.length===0&&!t.element.getIsDropTarget()};t.sortableOptions={cursor:"move",delay:150,disabled:u.getIsSealed(),distance:5,start:function(n,i){t.$apply(function(){u.setIsDropTarget(!0);u.editor.isDragging=!0});i.placeholder.height(i.item.height()-4);i.placeholder.css("min-height",0)},stop:function(){t.$apply(function(){u.editor.isDragging=!1;u.setIsDropTarget(!1)})},over:function(t,f){!f.sender||!f.sender[0].isToolbox||(!f.sender[0].dropTargetTimeout||(n.cancel(f.sender[0].dropTargetTimeout),f.sender[0].dropTargetTimeout=null),n(function(){if(u.type=="Row"){var n=u.editor.dropTargetElement;!n||n.type!="Row"||n.rollbackAddColumn()}u.setIsDropTarget(!1)}),f.sender[0].dropTargetTimeout=n(function(){var n,t,e;if(u.type=="Row"){for(n=f.item.sortable.model,t=Math.floor(12/(u.children.length+1)),n.width=t,n.offset=0,u.beginAddColumn(t),e=_.max(_(r.find("> .layout-children > .layout-column:not(.ui-sortable-placeholder)")).map(function(n){return $(n).height()})),i=1;i<=12;i++)f.placeholder.removeClass("col-xs-"+i);f.placeholder.addClass("col-xs-"+n.width);e>0?(f.placeholder.height(e),f.placeholder.css("min-height",0)):(f.placeholder.height(0),f.placeholder.css("min-height",""))}u.setIsDropTarget(!0)},150))},receive:function(i,r){!r.sender||!r.sender[0].isToolbox||t.$apply(function(){var i=r.item.sortable.model;!i||(u.type=="Row"&&u.commitAddColumn(),i.setEditor(u.editor),i.setParent(u),i.type!="Content"||!i.hasEditor||t.$root.editElement(i).then(function(t){t.cancel||(i.data=decodeURIComponent(t.element.data),i.setHtml(decodeURIComponent(t.element.html.replace(/\+/g,"%20"))));n(function(){t.cancel?i.delete():i.setIsFocused();u.setIsDropTarget(!1)});return}));n(function(){u.setIsDropTarget(!1);!i||i.setIsFocused()})})}};t.click=function(n,t){n.editor.isDragging||n.setIsFocused();t.stopPropagation()};t.getClasses=function(n){var t=["layout-element"];return!n.children||(t.push("layout-container"),n.getIsSealed()&&t.push("layout-container-sealed")),t.push("layout-"+n.type.toLowerCase()),!n.dropTargetClass||t.push(n.dropTargetClass),n.type=="Row"&&(t.push("row"),n.canAddColumn()||t.push("layout-row-full")),n.type=="Column"&&(t.push("col-xs-"+n.width),t.push("col-xs-offset-"+n.offset)),n.type=="Content"&&t.push("layout-content-"+n.contentTypeClass),n.getIsActive()&&t.push("layout-element-active"),n.getIsFocused()&&t.push("layout-element-focused"),n.getIsSelected()&&t.push("layout-element-selected"),n.getIsDropTarget()&&t.push("layout-element-droptarget"),n.isTemplated&&t.push("layout-element-templated"),t}}}});angular.module("LayoutEditor").directive("orcLayoutEditor",function(n){return{restrict:"E",scope:{},controller:function($scope,$element,$attrs,$compile,clipboard){if(!$attrs.model)throw new Error("The 'model' attribute must evaluate to a LayoutEditor.Editor object.");else $scope.element=eval($attrs.model);$scope.click=function(n,t){n.editor.isDragging||n.setIsFocused();t.stopPropagation()};$scope.getClasses=function(n){var t=["layout-element","layout-container","layout-canvas"];return n.getIsActive()&&t.push("layout-element-active"),n.getIsFocused()&&t.push("layout-element-focused"),n.getIsSelected()&&t.push("layout-element-selected"),n.getIsDropTarget()&&t.push("layout-element-droptarget"),n.isTemplated&&t.push("layout-element-templated"),t};var layoutDesignerHost=$(".layout-designer").data("layout-designer-host");$scope.$root.layoutDesignerHost=layoutDesignerHost;layoutDesignerHost.element.on("replacecanvas",function(n,t){var u=$scope.element,f={data:t.canvas.data,htmlId:t.canvas.htmlId,htmlClass:t.canvas.htmlClass,htmlStyle:t.canvas.htmlStyle,isTemplated:t.canvas.isTemplated,children:t.canvas.children},i,r;layoutDesignerHost.editor=window.layoutEditor=new LayoutEditor.Editor(u.config,f);i="";r=$compile(i)($scope);$(".layout-editor-holder").html(r)});$scope.$root.editElement=function(n){var t=$scope.$root.layoutDesignerHost;return t.editElement(n)};$scope.$root.addElement=function(n){var t=$scope.$root.layoutDesignerHost;return t.addElement(n)};$scope.toggleInlineEditing=function(){if($scope.element.inlineEditingIsActive)tinymce.remove("#layout-editor-"+$scope.$id+" .layout-content-markup"),$element.find(".layout-toolbar-container").hide(),$scope.element.inlineEditingIsActive=!1;else{$scope.element.inlineEditingIsActive=!0;$element.find(".layout-toolbar-container").show();var n="#layout-editor-"+$scope.$id+" .layout-content-h-t-m-l .layout-content-markup[data-templated=false]",t=$(n).first().attr("id");tinymce.init({selector:n,theme:"modern",schema:"html5",plugins:["advlist autolink lists link image charmap print preview hr anchor pagebreak","searchreplace wordcount visualblocks visualchars code fullscreen","insertdatetime media nonbreaking table contextmenu directionality","emoticons template paste textcolor colorpicker textpattern","fullscreen autoresize"],toolbar:"undo redo cut copy paste | bold italic | bullist numlist outdent indent formatselect | alignleft aligncenter alignright alignjustify ltr rtl | link unlink charmap | code fullscreen close",convert_urls:!1,valid_elements:"*[*]",extended_valid_elements:"script[type|defer|src|language]",statusbar:!1,skin:"orchardlightgray",inline:!0,fixed_toolbar_container:"#layout-editor-"+$scope.$id+" .layout-toolbar-container",init_instance_callback:function(n){n.id==t&&tinymce.execCommand("mceFocus",!1,n.id)}})}};$(document).on("cut copy paste",function(n){clipboard.disable();var t=$scope.element.focusedElement;!t||($scope.$apply(function(){switch(n.type){case"copy":t.copy(n.originalEvent.clipboardData);break;case"cut":t.cut(n.originalEvent.clipboardData);break;case"paste":t.paste(n.originalEvent.clipboardData)}}),window.setTimeout(function(){$scope.$apply(function(){!$scope.element.focusedElement||$scope.element.focusedElement.setIsFocused()})},100),n.preventDefault())})},templateUrl:n.templateUrl("Editor"),replace:!0,link:function(n,t){t.find(".layout-toolbar-container").click(function(n){n.stopPropagation()});t.mousedown(function(t){n.element.inlineEditingIsActive&&(t.preventDefault(),t.stopPropagation())});$(window).click(function(){n.element.inlineEditingIsActive||n.$apply(function(){n.element.activeElement=null;n.element.focusedElement=null})})}}});angular.module("LayoutEditor").directive("orcLayoutCanvas",function(n,t){return{restrict:"E",scope:{element:"="},controller:function(t,i){n.configureForElement(t,i);n.configureForContainer(t,i);t.sortableOptions.axis="y"},templateUrl:t.templateUrl("Canvas"),replace:!0}});angular.module("LayoutEditor").directive("orcLayoutChild",function(n){return{restrict:"E",scope:{element:"="},link:function(t,i){var r="",u=n(r)(t);$(i).replaceWith(u)}}});angular.module("LayoutEditor").directive("orcLayoutColumn",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="y"},templateUrl:i.templateUrl("Column"),replace:!0,link:function(n,t){t.find(".layout-column-resize-bar").draggable({axis:"x",helper:"clone",revert:!0,start:function(){n.$apply(function(){n.element.editor.isResizing=!0})},drag:function(i,r){var e=t.parent(),o=e.width()/n.element.width,u=!i.ctrlKey,f;$(i.target).hasClass("layout-column-resize-bar-left")?(f=r.offset.left-e.offset().left,f<-o&&n.element.canExpandLeft(u)?n.$apply(function(){n.element.expandLeft(u)}):f>o&&n.element.canContractLeft(u)&&n.$apply(function(){n.element.contractLeft(u)})):$(i.target).hasClass("layout-column-resize-bar-right")&&(f=r.offset.left-e.width()-e.offset().left,f>o&&n.element.canExpandRight(u)?n.$apply(function(){n.element.expandRight(u)}):f<-o&&n.element.canContractRight(u)&&n.$apply(function(){n.element.contractRight(u)}))},stop:function(){n.$apply(function(){n.element.editor.isResizing=!1})}})}}});angular.module("LayoutEditor").directive("orcLayoutContent",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(i,r){t.configureForElement(i,r);i.edit=function(){i.$root.editElement(i.element).then(function(n){i.$apply(function(){n.cancel||(i.element.data=decodeURIComponent(n.element.data),i.element.setHtml(decodeURIComponent(n.element.html.replace(/\+/g,"%20"))))})})};i.updateContent=function(n){i.element.setHtml(n.target.innerHTML)};i.element.setHtml=function(t){i.element.html=t;i.element.htmlUnsafe=n.trustAsHtml(t)};i.element.setHtml(decodeURIComponent(i.element.html.replace(/\+/g,"%20")))},templateUrl:i.templateUrl("Content"),replace:!0,link:function(n,t){t.find(".layout-content-markup").mousedown(function(t){n.element.editor.inlineEditingIsActive&&t.stopPropagation()})}}});angular.module("LayoutEditor").directive("orcLayoutGrid",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="y"},templateUrl:i.templateUrl("Grid"),replace:!0}});angular.module("LayoutEditor").directive("orcLayoutRow",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="x";n.sortableOptions["ui-floating"]=!0},templateUrl:i.templateUrl("Row"),replace:!0}});angular.module("LayoutEditor").directive("orcLayoutPopup",function(){return{restrict:"A",link:function(n,t,i){var r=$(t),u=r.closest(".layout-popup-trigger"),f=r.closest(".layout-element");u.click(function(){r.toggle();r.is(":visible")&&(r.position({my:i.orcLayoutPopupMy||"left top",at:i.orcLayoutPopupAt||"left bottom+4px",of:u}),r.find("input").first().focus())});r.click(function(n){n.stopPropagation()});f.click(function(){r.hide()});r.keydown(function(n){n.ctrlKey||n.shiftKey||n.altKey||n.which!=27||r.hide();n.stopPropagation()})}}});angular.module("LayoutEditor").directive("orcLayoutToolbox",function(n,t){return{restrict:"E",controller:function(n,t){n.resetElements=function(){n.gridElements=[LayoutEditor.Grid.from({toolboxIcon:"",toolboxLabel:"Grid",toolboxDescription:"Empty grid.",children:[]})];n.rowElements=[LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (1 column)",toolboxDescription:"Row with 1 column.",children:LayoutEditor.Column.times(1)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (2 columns)",toolboxDescription:"Row with 2 columns.",children:LayoutEditor.Column.times(2)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (3 columns)",toolboxDescription:"Row with 3 columns.",children:LayoutEditor.Column.times(3)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (4 columns)",toolboxDescription:"Row with 4 columns.",children:LayoutEditor.Column.times(4)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (6 columns)",toolboxDescription:"Row with 6 columns.",children:LayoutEditor.Column.times(6)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (12 columns)",toolboxDescription:"Row with 12 columns.",children:LayoutEditor.Column.times(12)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (empty)",toolboxDescription:"Empty row.",children:[]})];n.columnElements=[LayoutEditor.Column.from({toolboxIcon:"",toolboxLabel:"Column",toolboxDescription:"Empty column.",width:1,offset:0,children:[]})];n.contentElementCategories=_(n.element.config.categories).map(function(n){return{name:n.name,elements:_(n.contentTypes).map(function(n){var i=n.type,r=LayoutEditor.factories[i]||LayoutEditor.factories.Content,u={isTemplated:!1,contentType:n.id,contentTypeLabel:n.label,contentTypeClass:n.typeClass,data:null,hasEditor:n.hasEditor,html:n.html},t=r(u);return t.toolboxIcon=n.icon||"",t.toolboxLabel=n.label,t.toolboxDescription=n.description,t})}})};n.resetElements();n.getSortableOptions=function(i){var e=t.closest(".layout-editor").attr("id"),r,u,f=!1;switch(i){case"Grid":r=[".layout-canvas",".layout-column",".layout-common-holder"];u="layout-element layout-container layout-grid ui-sortable-placeholder";break;case"Row":r=[".layout-grid"];u="layout-element layout-container layout-row row ui-sortable-placeholder";break;case"Column":r=[".layout-row:not(.layout-row-full)"];u="layout-element layout-container layout-column ui-sortable-placeholder";f=!0;break;case"Content":r=[".layout-canvas",".layout-column",".layout-common-holder"];u="layout-element layout-content ui-sortable-placeholder"}return{cursor:"move",connectWith:_(r).map(function(n){return"#"+e+" "+n+":not(.layout-container-sealed) > .layout-element-wrapper > .layout-children"}).join(", "),placeholder:u,"ui-floating":f,create:function(n){n.target.isToolbox=!0},start:function(){n.$apply(function(){n.element.isDragging=!0})},stop:function(){n.$apply(function(){n.element.isDragging=!1;n.resetElements()})},over:function(){n.$apply(function(){n.element.canvas.setIsDropTarget(!1)})}}};var i="layoutToolboxCategory_Layout_IsCollapsed";n.layoutIsCollapsed=$.cookie(i)==="true";n.toggleLayoutIsCollapsed=function(t){n.layoutIsCollapsed=!n.layoutIsCollapsed;$.cookie(i,n.layoutIsCollapsed,{expires:365});t.preventDefault();t.stopPropagation()}},templateUrl:t.templateUrl("Toolbox"),replace:!0}});angular.module("LayoutEditor").directive("orcLayoutToolboxGroup",function(n,t){return{restrict:"E",scope:{category:"="},controller:function(n){var t="layoutToolboxCategory_"+n.category.name+"_IsCollapsed";n.isCollapsed=$.cookie(t)==="true";n.toggleIsCollapsed=function(i){n.isCollapsed=!n.isCollapsed;$.cookie(t,n.isCollapsed,{expires:365});i.preventDefault();i.stopPropagation()}},templateUrl:t.templateUrl("ToolboxGroup"),replace:!0}}); \ No newline at end of file +angular.module("LayoutEditor",["ngSanitize","ngResource","ui.sortable"]);var LayoutEditor;(function(n){var t=function(){var n=this;this.clipboardData={};this.setData=function(t,i){n.clipboardData[t]=i};this.getData=function(t){return n.clipboardData[t]};this.disable=function(){this.disabled=!0}};n.Clipboard=new t;angular.module("LayoutEditor").factory("clipboard",[function(){return{setData:n.Clipboard.setData,getData:n.Clipboard.getData,disable:n.Clipboard.disable}}])})(LayoutEditor||(LayoutEditor={}));angular.module("LayoutEditor").factory("scopeConfigurator",["$timeout","clipboard",function(n,t){return{configureForElement:function(n,i){i.find(".layout-panel").click(function(n){n.stopPropagation()});i.parent().keydown(function(r){var f=!1,s=!1,u=n.element,e,o;if(!u.editor.isDragging&&!u.editor.inlineEditingIsActive){if(!t.disabled&&(e=u.editor.focusedElement,!!e&&r.ctrlKey))switch(r.which){case 67:e.copy(t);break;case 88:e.cut(t);break;case 86:e.paste(t)}r.ctrlKey||r.shiftKey||r.altKey||r.which!=46?r.ctrlKey||r.shiftKey||r.altKey||r.which!=32&&r.which!=27||(i.find(".layout-panel-action-properties").first().click(),f=!0):(n.delete(u),f=!0);u.type=="Content"&&(r.ctrlKey||r.shiftKey||r.altKey||r.which!=13||(i.find(".layout-panel-action-edit").first().click(),f=!0));!u.children||(r.ctrlKey||r.shiftKey||!r.altKey||r.which!=40||(u.children.length>0&&u.children[0].setIsFocused(),f=!0),u.type=="Column"&&(o=!r.ctrlKey,r.which==37?(r.altKey&&u.expandLeft(o),r.shiftKey&&u.contractRight(o),f=!0):r.which==39&&(r.altKey&&u.contractLeft(o),r.shiftKey&&u.expandRight(o),f=!0)));!u.parent||(r.altKey&&r.which==38&&(u.parent.setIsFocused(),f=!0),u.parent.type=="Row"?r.ctrlKey||r.shiftKey||r.altKey||r.which!=37?r.ctrlKey||r.shiftKey||r.altKey||r.which!=39?!r.ctrlKey||r.shiftKey||r.altKey||r.which!=37?!r.ctrlKey||r.shiftKey||r.altKey||r.which!=39||(u.moveDown(),f=!0):(u.moveUp(),s=!0,f=!0):(u.parent.moveFocusNextChild(u),f=!0):(u.parent.moveFocusPrevChild(u),f=!0):r.ctrlKey||r.shiftKey||r.altKey||r.which!=38?r.ctrlKey||r.shiftKey||r.altKey||r.which!=40?!r.ctrlKey||r.shiftKey||r.altKey||r.which!=38?!r.ctrlKey||r.shiftKey||r.altKey||r.which!=40||(u.moveDown(),f=!0):(u.moveUp(),s=!0,f=!0):(u.parent.moveFocusNextChild(u),f=!0):(u.parent.moveFocusPrevChild(u),f=!0));f&&r.preventDefault();r.stopPropagation();n.$apply();s&&window.setTimeout(function(){n.$apply(function(){u.editor.focusedElement.setIsFocused()})},100)}});n.element.setIsFocusedEventHandlers.push(function(){i.parent().focus()});n.delete=function(n){n.delete()}},configureForContainer:function(t,r){var u=t.element;t.getShowChildrenPlaceholder=function(){return t.element.children.length===0&&!t.element.getIsDropTarget()};t.sortableOptions={cursor:"move",delay:150,disabled:u.getIsSealed(),distance:5,start:function(n,i){t.$apply(function(){u.setIsDropTarget(!0);u.editor.isDragging=!0});i.placeholder.height(i.item.height()-4);i.placeholder.css("min-height",0)},stop:function(){t.$apply(function(){u.editor.isDragging=!1;u.setIsDropTarget(!1)})},over:function(t,f){!f.sender||!f.sender[0].isToolbox||(!f.sender[0].dropTargetTimeout||(n.cancel(f.sender[0].dropTargetTimeout),f.sender[0].dropTargetTimeout=null),n(function(){if(u.type=="Row"){var n=u.editor.dropTargetElement;!n||n.type!="Row"||n.rollbackAddColumn()}u.setIsDropTarget(!1)}),f.sender[0].dropTargetTimeout=n(function(){var n,t,e;if(u.type=="Row"){for(n=f.item.sortable.model,t=Math.floor(12/(u.children.length+1)),n.width=t,n.offset=0,u.beginAddColumn(t),e=_.max(_(r.find("> .layout-children > .layout-column:not(.ui-sortable-placeholder)")).map(function(n){return $(n).height()})),i=1;i<=12;i++)f.placeholder.removeClass("col-xs-"+i);f.placeholder.addClass("col-xs-"+n.width);e>0?(f.placeholder.height(e),f.placeholder.css("min-height",0)):(f.placeholder.height(0),f.placeholder.css("min-height",""))}u.setIsDropTarget(!0)},150))},receive:function(i,r){!r.sender||!r.sender[0].isToolbox||t.$apply(function(){var i=r.item.sortable.model;!i||(u.type=="Row"&&u.commitAddColumn(),i.setEditor(u.editor),i.setParent(u),i.type!="Content"||!i.hasEditor||t.$root.editElement(i).then(function(t){t.cancel||(i.data=decodeURIComponent(t.element.data),i.setHtml(decodeURIComponent(t.element.html.replace(/\+/g,"%20"))));n(function(){t.cancel?i.delete():i.setIsFocused();u.setIsDropTarget(!1)});return}));n(function(){u.setIsDropTarget(!1);!i||i.setIsFocused()})})}};t.click=function(n,t){n.editor.isDragging||n.setIsFocused();t.stopPropagation()};t.getClasses=function(n){var t=["layout-element"];return!n.children||(t.push("layout-container"),n.getIsSealed()&&t.push("layout-container-sealed")),t.push("layout-"+n.type.toLowerCase()),!n.dropTargetClass||t.push(n.dropTargetClass),n.type=="Row"&&(t.push("row"),n.canAddColumn()||t.push("layout-row-full")),n.type=="Column"&&(t.push("col-xs-"+n.width),t.push("col-xs-offset-"+n.offset)),n.type=="Content"&&t.push("layout-content-"+n.contentTypeClass),n.getIsActive()&&t.push("layout-element-active"),n.getIsFocused()&&t.push("layout-element-focused"),n.getIsSelected()&&t.push("layout-element-selected"),n.getIsDropTarget()&&t.push("layout-element-droptarget"),n.isTemplated&&t.push("layout-element-templated"),t}}}}]);angular.module("LayoutEditor").directive("orcLayoutEditor",["environment",function(n){return{restrict:"E",scope:{},controller:function($scope,$element,$attrs,$compile,clipboard){if(!$attrs.model)throw new Error("The 'model' attribute must evaluate to a LayoutEditor.Editor object.");else $scope.element=eval($attrs.model);$scope.click=function(n,t){n.editor.isDragging||n.setIsFocused();t.stopPropagation()};$scope.getClasses=function(n){var t=["layout-element","layout-container","layout-canvas"];return n.getIsActive()&&t.push("layout-element-active"),n.getIsFocused()&&t.push("layout-element-focused"),n.getIsSelected()&&t.push("layout-element-selected"),n.getIsDropTarget()&&t.push("layout-element-droptarget"),n.isTemplated&&t.push("layout-element-templated"),t};var layoutDesignerHost=$(".layout-designer").data("layout-designer-host");$scope.$root.layoutDesignerHost=layoutDesignerHost;layoutDesignerHost.element.on("replacecanvas",function(n,t){var u=$scope.element,f={data:t.canvas.data,htmlId:t.canvas.htmlId,htmlClass:t.canvas.htmlClass,htmlStyle:t.canvas.htmlStyle,isTemplated:t.canvas.isTemplated,children:t.canvas.children},i,r;layoutDesignerHost.editor=window.layoutEditor=new LayoutEditor.Editor(u.config,f);i="";r=$compile(i)($scope);$(".layout-editor-holder").html(r)});$scope.$root.editElement=function(n){var t=$scope.$root.layoutDesignerHost;return t.editElement(n)};$scope.$root.addElement=function(n){var t=$scope.$root.layoutDesignerHost;return t.addElement(n)};$scope.toggleInlineEditing=function(){if($scope.element.inlineEditingIsActive)tinymce.remove("#layout-editor-"+$scope.$id+" .layout-content-markup"),$element.find(".layout-toolbar-container").hide(),$scope.element.inlineEditingIsActive=!1;else{$scope.element.inlineEditingIsActive=!0;$element.find(".layout-toolbar-container").show();var n="#layout-editor-"+$scope.$id+" .layout-content-h-t-m-l .layout-content-markup[data-templated=false]",t=$(n).first().attr("id");tinymce.init({selector:n,theme:"modern",schema:"html5",plugins:["advlist autolink lists link image charmap print preview hr anchor pagebreak","searchreplace wordcount visualblocks visualchars code fullscreen","insertdatetime media nonbreaking table contextmenu directionality","emoticons template paste textcolor colorpicker textpattern","fullscreen autoresize"],toolbar:"undo redo cut copy paste | bold italic | bullist numlist outdent indent formatselect | alignleft aligncenter alignright alignjustify ltr rtl | link unlink charmap | code fullscreen close",convert_urls:!1,valid_elements:"*[*]",extended_valid_elements:"script[type|defer|src|language]",statusbar:!1,skin:"orchardlightgray",inline:!0,fixed_toolbar_container:"#layout-editor-"+$scope.$id+" .layout-toolbar-container",init_instance_callback:function(n){n.id==t&&tinymce.execCommand("mceFocus",!1,n.id)}})}};$(document).on("cut copy paste",function(n){clipboard.disable();var t=$scope.element.focusedElement;!t||($scope.$apply(function(){switch(n.type){case"copy":t.copy(n.originalEvent.clipboardData);break;case"cut":t.cut(n.originalEvent.clipboardData);break;case"paste":t.paste(n.originalEvent.clipboardData)}}),window.setTimeout(function(){$scope.$apply(function(){!$scope.element.focusedElement||$scope.element.focusedElement.setIsFocused()})},100),n.preventDefault())})},templateUrl:n.templateUrl("Editor"),replace:!0,link:function(n,t){t.find(".layout-toolbar-container").click(function(n){n.stopPropagation()});t.mousedown(function(t){n.element.inlineEditingIsActive&&(t.preventDefault(),t.stopPropagation())});$(window).click(function(){n.element.inlineEditingIsActive||n.$apply(function(){n.element.activeElement=null;n.element.focusedElement=null})})}}}]);angular.module("LayoutEditor").directive("orcLayoutCanvas",["scopeConfigurator","environment",function(n,t){return{restrict:"E",scope:{element:"="},controller:function(t,i){n.configureForElement(t,i);n.configureForContainer(t,i);t.sortableOptions.axis="y"},templateUrl:t.templateUrl("Canvas"),replace:!0}}]);angular.module("LayoutEditor").directive("orcLayoutChild",["$compile",function(n){return{restrict:"E",scope:{element:"="},link:function(t,i){var r="",u=n(r)(t);$(i).replaceWith(u)}}}]);angular.module("LayoutEditor").directive("orcLayoutColumn",["$compile","scopeConfigurator","environment",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="y"},templateUrl:i.templateUrl("Column"),replace:!0,link:function(n,t){t.find(".layout-column-resize-bar").draggable({axis:"x",helper:"clone",revert:!0,start:function(){n.$apply(function(){n.element.editor.isResizing=!0})},drag:function(i,r){var e=t.parent(),o=e.width()/n.element.width,u=!i.ctrlKey,f;$(i.target).hasClass("layout-column-resize-bar-left")?(f=r.offset.left-e.offset().left,f<-o&&n.element.canExpandLeft(u)?n.$apply(function(){n.element.expandLeft(u)}):f>o&&n.element.canContractLeft(u)&&n.$apply(function(){n.element.contractLeft(u)})):$(i.target).hasClass("layout-column-resize-bar-right")&&(f=r.offset.left-e.width()-e.offset().left,f>o&&n.element.canExpandRight(u)?n.$apply(function(){n.element.expandRight(u)}):f<-o&&n.element.canContractRight(u)&&n.$apply(function(){n.element.contractRight(u)}))},stop:function(){n.$apply(function(){n.element.editor.isResizing=!1})}})}}}]);angular.module("LayoutEditor").directive("orcLayoutContent",["$sce","scopeConfigurator","environment",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(i,r){t.configureForElement(i,r);i.edit=function(){i.$root.editElement(i.element).then(function(n){i.$apply(function(){n.cancel||(i.element.data=decodeURIComponent(n.element.data),i.element.setHtml(decodeURIComponent(n.element.html.replace(/\+/g,"%20"))))})})};i.updateContent=function(n){i.element.setHtml(n.target.innerHTML)};i.element.setHtml=function(t){i.element.html=t;i.element.htmlUnsafe=n.trustAsHtml(t)};i.element.setHtml(decodeURIComponent(i.element.html.replace(/\+/g,"%20")))},templateUrl:i.templateUrl("Content"),replace:!0,link:function(n,t){t.find(".layout-content-markup").mousedown(function(t){n.element.editor.inlineEditingIsActive&&t.stopPropagation()})}}}]);angular.module("LayoutEditor").directive("orcLayoutGrid",["$compile","scopeConfigurator","environment",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="y"},templateUrl:i.templateUrl("Grid"),replace:!0}}]);angular.module("LayoutEditor").directive("orcLayoutRow",["$compile","scopeConfigurator","environment",function(n,t,i){return{restrict:"E",scope:{element:"="},controller:function(n,i){t.configureForElement(n,i);t.configureForContainer(n,i);n.sortableOptions.axis="x";n.sortableOptions["ui-floating"]=!0},templateUrl:i.templateUrl("Row"),replace:!0}}]);angular.module("LayoutEditor").directive("orcLayoutPopup",[function(){return{restrict:"A",link:function(n,t,i){var r=$(t),u=r.closest(".layout-popup-trigger"),f=r.closest(".layout-element");u.click(function(){r.toggle();r.is(":visible")&&(r.position({my:i.orcLayoutPopupMy||"left top",at:i.orcLayoutPopupAt||"left bottom+4px",of:u}),r.find("input").first().focus())});r.click(function(n){n.stopPropagation()});f.click(function(){r.hide()});r.keydown(function(n){n.ctrlKey||n.shiftKey||n.altKey||n.which!=27||r.hide();n.stopPropagation()})}}}]);angular.module("LayoutEditor").directive("orcLayoutToolbox",["$compile","environment",function(n,t){return{restrict:"E",controller:function(n,t){n.resetElements=function(){n.gridElements=[LayoutEditor.Grid.from({toolboxIcon:"",toolboxLabel:"Grid",toolboxDescription:"Empty grid.",children:[]})];n.rowElements=[LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (1 column)",toolboxDescription:"Row with 1 column.",children:LayoutEditor.Column.times(1)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (2 columns)",toolboxDescription:"Row with 2 columns.",children:LayoutEditor.Column.times(2)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (3 columns)",toolboxDescription:"Row with 3 columns.",children:LayoutEditor.Column.times(3)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (4 columns)",toolboxDescription:"Row with 4 columns.",children:LayoutEditor.Column.times(4)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (6 columns)",toolboxDescription:"Row with 6 columns.",children:LayoutEditor.Column.times(6)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (12 columns)",toolboxDescription:"Row with 12 columns.",children:LayoutEditor.Column.times(12)}),LayoutEditor.Row.from({toolboxIcon:"",toolboxLabel:"Row (empty)",toolboxDescription:"Empty row.",children:[]})];n.columnElements=[LayoutEditor.Column.from({toolboxIcon:"",toolboxLabel:"Column",toolboxDescription:"Empty column.",width:1,offset:0,children:[]})];n.contentElementCategories=_(n.element.config.categories).map(function(n){return{name:n.name,elements:_(n.contentTypes).map(function(n){var i=n.type,r=LayoutEditor.factories[i]||LayoutEditor.factories.Content,u={isTemplated:!1,contentType:n.id,contentTypeLabel:n.label,contentTypeClass:n.typeClass,data:null,hasEditor:n.hasEditor,html:n.html},t=r(u);return t.toolboxIcon=n.icon||"",t.toolboxLabel=n.label,t.toolboxDescription=n.description,t})}})};n.resetElements();n.getSortableOptions=function(i){var e=t.closest(".layout-editor").attr("id"),r,u,f=!1;switch(i){case"Grid":r=[".layout-canvas",".layout-column",".layout-common-holder"];u="layout-element layout-container layout-grid ui-sortable-placeholder";break;case"Row":r=[".layout-grid"];u="layout-element layout-container layout-row row ui-sortable-placeholder";break;case"Column":r=[".layout-row:not(.layout-row-full)"];u="layout-element layout-container layout-column ui-sortable-placeholder";f=!0;break;case"Content":r=[".layout-canvas",".layout-column",".layout-common-holder"];u="layout-element layout-content ui-sortable-placeholder"}return{cursor:"move",connectWith:_(r).map(function(n){return"#"+e+" "+n+":not(.layout-container-sealed) > .layout-element-wrapper > .layout-children"}).join(", "),placeholder:u,"ui-floating":f,create:function(n){n.target.isToolbox=!0},start:function(){n.$apply(function(){n.element.isDragging=!0})},stop:function(){n.$apply(function(){n.element.isDragging=!1;n.resetElements()})},over:function(){n.$apply(function(){n.element.canvas.setIsDropTarget(!1)})}}};var i="layoutToolboxCategory_Layout_IsCollapsed";n.layoutIsCollapsed=$.cookie(i)==="true";n.toggleLayoutIsCollapsed=function(t){n.layoutIsCollapsed=!n.layoutIsCollapsed;$.cookie(i,n.layoutIsCollapsed,{expires:365});t.preventDefault();t.stopPropagation()}},templateUrl:t.templateUrl("Toolbox"),replace:!0}}]);angular.module("LayoutEditor").directive("orcLayoutToolboxGroup",["$compile","environment",function(n,t){return{restrict:"E",scope:{category:"="},controller:function(n){var t="layoutToolboxCategory_"+n.category.name+"_IsCollapsed";n.isCollapsed=$.cookie(t)==="true";n.toggleIsCollapsed=function(i){n.isCollapsed=!n.isCollapsed;$.cookie(t,n.isCollapsed,{expires:365});i.preventDefault();i.stopPropagation()}},templateUrl:t.templateUrl("ToolboxGroup"),replace:!0}}]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Canvas.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Canvas.js index adc64cb95..40100caa0 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Canvas.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Canvas.js @@ -1,15 +1,17 @@ angular .module("LayoutEditor") - .directive("orcLayoutCanvas", function (scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element, $attrs) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "y"; - }, - templateUrl: environment.templateUrl("Canvas"), - replace: true - }; - }); \ No newline at end of file + .directive("orcLayoutCanvas", ["scopeConfigurator", "environment", + function (scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element, $attrs) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "y"; + }, + templateUrl: environment.templateUrl("Canvas"), + replace: true + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Child.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Child.js index baf371e6a..8cbc59046 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Child.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Child.js @@ -1,13 +1,15 @@ angular .module("LayoutEditor") - .directive("orcLayoutChild", function ($compile) { - return { - restrict: "E", - scope: { element: "=" }, - link: function (scope, element) { - var template = ""; - var html = $compile(template)(scope); - $(element).replaceWith(html); - } - }; - }); \ No newline at end of file + .directive("orcLayoutChild", ["$compile", + function ($compile) { + return { + restrict: "E", + scope: { element: "=" }, + link: function (scope, element) { + var template = ""; + var html = $compile(template)(scope); + $(element).replaceWith(html); + } + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Column.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Column.js index 377ffe497..20cadbc58 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Column.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Column.js @@ -1,64 +1,66 @@ angular .module("LayoutEditor") - .directive("orcLayoutColumn", function ($compile, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "y"; - }, - templateUrl: environment.templateUrl("Column"), - replace: true, - link: function (scope, element, attrs) { - element.find(".layout-column-resize-bar").draggable({ - axis: "x", - helper: "clone", - revert: true, - start: function (e, ui) { - scope.$apply(function () { - scope.element.editor.isResizing = true; - }); - }, - drag: function (e, ui) { - var columnElement = element.parent(); - var columnSize = columnElement.width() / scope.element.width; - var connectAdjacent = !e.ctrlKey; - if ($(e.target).hasClass("layout-column-resize-bar-left")) { - var delta = ui.offset.left - columnElement.offset().left; - if (delta < -columnSize && scope.element.canExpandLeft(connectAdjacent)) { - scope.$apply(function () { - scope.element.expandLeft(connectAdjacent); - }); + .directive("orcLayoutColumn", ["$compile", "scopeConfigurator", "environment", + function ($compile, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "y"; + }, + templateUrl: environment.templateUrl("Column"), + replace: true, + link: function (scope, element, attrs) { + element.find(".layout-column-resize-bar").draggable({ + axis: "x", + helper: "clone", + revert: true, + start: function (e, ui) { + scope.$apply(function () { + scope.element.editor.isResizing = true; + }); + }, + drag: function (e, ui) { + var columnElement = element.parent(); + var columnSize = columnElement.width() / scope.element.width; + var connectAdjacent = !e.ctrlKey; + if ($(e.target).hasClass("layout-column-resize-bar-left")) { + var delta = ui.offset.left - columnElement.offset().left; + if (delta < -columnSize && scope.element.canExpandLeft(connectAdjacent)) { + scope.$apply(function () { + scope.element.expandLeft(connectAdjacent); + }); + } + else if (delta > columnSize && scope.element.canContractLeft(connectAdjacent)) { + scope.$apply(function () { + scope.element.contractLeft(connectAdjacent); + }); + } } - else if (delta > columnSize && scope.element.canContractLeft(connectAdjacent)) { - scope.$apply(function () { - scope.element.contractLeft(connectAdjacent); - }); + else if ($(e.target).hasClass("layout-column-resize-bar-right")) { + var delta = ui.offset.left - columnElement.width() - columnElement.offset().left; + if (delta > columnSize && scope.element.canExpandRight(connectAdjacent)) { + scope.$apply(function () { + scope.element.expandRight(connectAdjacent); + }); + } + else if (delta < -columnSize && scope.element.canContractRight(connectAdjacent)) { + scope.$apply(function () { + scope.element.contractRight(connectAdjacent); + }); + } } - } - else if ($(e.target).hasClass("layout-column-resize-bar-right")) { - var delta = ui.offset.left - columnElement.width() - columnElement.offset().left; - if (delta > columnSize && scope.element.canExpandRight(connectAdjacent)) { - scope.$apply(function () { - scope.element.expandRight(connectAdjacent); - }); - } - else if (delta < -columnSize && scope.element.canContractRight(connectAdjacent)) { - scope.$apply(function () { - scope.element.contractRight(connectAdjacent); - }); - } - } - }, - stop: function (e, ui) { - scope.$apply(function () { - scope.element.editor.isResizing = false; - }); - } - }); - } - }; - }); \ No newline at end of file + }, + stop: function (e, ui) { + scope.$apply(function () { + scope.element.editor.isResizing = false; + }); + } + }); + } + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Content.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Content.js index 5a891a1a8..e7f822dc1 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Content.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Content.js @@ -1,44 +1,46 @@ angular .module("LayoutEditor") - .directive("orcLayoutContent", function ($sce, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - $scope.edit = function () { - $scope.$root.editElement($scope.element).then(function (args) { - $scope.$apply(function () { - if (args.cancel) - return; + .directive("orcLayoutContent", ["$sce", "scopeConfigurator", "environment", + function ($sce, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + $scope.edit = function () { + $scope.$root.editElement($scope.element).then(function (args) { + $scope.$apply(function () { + if (args.cancel) + return; - $scope.element.data = decodeURIComponent(args.element.data); - $scope.element.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20"))); + $scope.element.data = decodeURIComponent(args.element.data); + $scope.element.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20"))); + }); }); + }; + $scope.updateContent = function (e) { + $scope.element.setHtml(e.target.innerHTML); + }; + + // Overwrite the setHtml function so that we can use the $sce service to trust the html (and not have the html binding strip certain tags). + $scope.element.setHtml = function (html) { + $scope.element.html = html; + $scope.element.htmlUnsafe = $sce.trustAsHtml(html); + }; + + $scope.element.setHtml(decodeURIComponent($scope.element.html.replace(/\+/g, "%20"))); + }, + templateUrl: environment.templateUrl("Content"), + replace: true, + link: function (scope, element) { + // Mouse down events must not be intercepted by drag and drop while inline editing is active, + // otherwise clicks in inline editors will have no effect. + element.find(".layout-content-markup").mousedown(function (e) { + if (scope.element.editor.inlineEditingIsActive) { + e.stopPropagation(); + } }); - }; - $scope.updateContent = function (e) { - $scope.element.setHtml(e.target.innerHTML); - }; - - // Overwrite the setHtml function so that we can use the $sce service to trust the html (and not have the html binding strip certain tags). - $scope.element.setHtml = function (html) { - $scope.element.html = html; - $scope.element.htmlUnsafe = $sce.trustAsHtml(html); - }; - - $scope.element.setHtml(decodeURIComponent($scope.element.html.replace(/\+/g, "%20"))); - }, - templateUrl: environment.templateUrl("Content"), - replace: true, - link: function (scope, element) { - // Mouse down events must not be intercepted by drag and drop while inline editing is active, - // otherwise clicks in inline editors will have no effect. - element.find(".layout-content-markup").mousedown(function (e) { - if (scope.element.editor.inlineEditingIsActive) { - e.stopPropagation(); - } - }); - } - }; - }); \ No newline at end of file + } + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Editor.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Editor.js index 869db07e0..47b98a025 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Editor.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Editor.js @@ -1,172 +1,174 @@ angular .module("LayoutEditor") - .directive("orcLayoutEditor", function (environment) { - return { - restrict: "E", - scope: {}, - controller: function ($scope, $element, $attrs, $compile, clipboard) { - if (!!$attrs.model) - $scope.element = eval($attrs.model); - else - throw new Error("The 'model' attribute must evaluate to a LayoutEditor.Editor object."); + .directive("orcLayoutEditor", ["environment", + function (environment) { + return { + restrict: "E", + scope: {}, + controller: function ($scope, $element, $attrs, $compile, clipboard) { + if (!!$attrs.model) + $scope.element = eval($attrs.model); + else + throw new Error("The 'model' attribute must evaluate to a LayoutEditor.Editor object."); - $scope.click = function (canvas, e) { - if (!canvas.editor.isDragging) - canvas.setIsFocused(); - e.stopPropagation(); - }; - - $scope.getClasses = function (canvas) { - var result = ["layout-element", "layout-container", "layout-canvas"]; - - if (canvas.getIsActive()) - result.push("layout-element-active"); - if (canvas.getIsFocused()) - result.push("layout-element-focused"); - if (canvas.getIsSelected()) - result.push("layout-element-selected"); - if (canvas.getIsDropTarget()) - result.push("layout-element-droptarget"); - if (canvas.isTemplated) - result.push("layout-element-templated"); - - return result; - }; - - // An unfortunate side-effect of the next hack on line 54 is that the created elements aren't added to the DOM yet, so we can't use it to get to the parent ".layout-desiger" element. - // Work around: access that element directly (which efectively turns multiple layout editors on a single page impossible). - // //var layoutDesignerHost = $element.closest(".layout-designer").data("layout-designer-host"); - var layoutDesignerHost = $(".layout-designer").data("layout-designer-host"); - - $scope.$root.layoutDesignerHost = layoutDesignerHost; - - layoutDesignerHost.element.on("replacecanvas", function (e, args) { - var editor = $scope.element; - var canvasData = { - data: args.canvas.data, - htmlId: args.canvas.htmlId, - htmlClass: args.canvas.htmlClass, - htmlStyle: args.canvas.htmlStyle, - isTemplated: args.canvas.isTemplated, - children: args.canvas.children + $scope.click = function (canvas, e) { + if (!canvas.editor.isDragging) + canvas.setIsFocused(); + e.stopPropagation(); }; - // HACK: Instead of simply updating the $scope.element with a new instance, we need to replace the entire orc-layout-editor markup - // in order for angular to rebind starting with the Canvas element. Otherwise, for some reason, it will rebind starting with the first child of Canvas. - // You can see this happening when setting a breakpoint in ScopeConfigurator where containers are initialized with drag & drop: on page load, the first element - // is a Canvas (good), but after having selected another template, the first element is (typically) a Grid (bad). - // Simply recompiling the orc-layout-editor directive will cause the entire thing to be generated, which works just fine as well (even though not is nice as simply leveraging model binding). - layoutDesignerHost.editor = window.layoutEditor = new LayoutEditor.Editor(editor.config, canvasData); - var template = ""; - var html = $compile(template)($scope); - $(".layout-editor-holder").html(html); - }); + $scope.getClasses = function (canvas) { + var result = ["layout-element", "layout-container", "layout-canvas"]; - $scope.$root.editElement = function (element) { - var host = $scope.$root.layoutDesignerHost; - return host.editElement(element); - }; + if (canvas.getIsActive()) + result.push("layout-element-active"); + if (canvas.getIsFocused()) + result.push("layout-element-focused"); + if (canvas.getIsSelected()) + result.push("layout-element-selected"); + if (canvas.getIsDropTarget()) + result.push("layout-element-droptarget"); + if (canvas.isTemplated) + result.push("layout-element-templated"); - $scope.$root.addElement = function (contentType) { - var host = $scope.$root.layoutDesignerHost; - return host.addElement(contentType); - }; + return result; + }; - $scope.toggleInlineEditing = function () { - if (!$scope.element.inlineEditingIsActive) { - $scope.element.inlineEditingIsActive = true; - $element.find(".layout-toolbar-container").show(); - var selector = "#layout-editor-" + $scope.$id + " .layout-content-h-t-m-l .layout-content-markup[data-templated=false]"; - var firstContentEditorId = $(selector).first().attr("id"); - tinymce.init({ - selector: selector, - theme: "modern", - schema: "html5", - plugins: [ - "advlist autolink lists link image charmap print preview hr anchor pagebreak", - "searchreplace wordcount visualblocks visualchars code fullscreen", - "insertdatetime media nonbreaking table contextmenu directionality", - "emoticons template paste textcolor colorpicker textpattern", - "fullscreen autoresize" - ], - toolbar: "undo redo cut copy paste | bold italic | bullist numlist outdent indent formatselect | alignleft aligncenter alignright alignjustify ltr rtl | link unlink charmap | code fullscreen close", - convert_urls: false, - valid_elements: "*[*]", - // Shouldn't be needed due to the valid_elements setting, but TinyMCE would strip script.src without it. - extended_valid_elements: "script[type|defer|src|language]", - statusbar: false, - skin: "orchardlightgray", - inline: true, - fixed_toolbar_container: "#layout-editor-" + $scope.$id + " .layout-toolbar-container", - init_instance_callback: function (editor) { - if (editor.id == firstContentEditorId) - tinymce.execCommand("mceFocus", false, editor.id); - } - }); - } - else { - tinymce.remove("#layout-editor-" + $scope.$id + " .layout-content-markup"); - $element.find(".layout-toolbar-container").hide(); - $scope.element.inlineEditingIsActive = false; - } - }; + // An unfortunate side-effect of the next hack on line 54 is that the created elements aren't added to the DOM yet, so we can't use it to get to the parent ".layout-desiger" element. + // Work around: access that element directly (which efectively turns multiple layout editors on a single page impossible). + // //var layoutDesignerHost = $element.closest(".layout-designer").data("layout-designer-host"); + var layoutDesignerHost = $(".layout-designer").data("layout-designer-host"); - $(document).on("cut copy paste", function (e) { - // The real clipboard is supported, so disable the peudo clipboard. - clipboard.disable(); - var focusedElement = $scope.element.focusedElement; - if (!!focusedElement) { - $scope.$apply(function () { - switch (e.type) { - case "copy": - focusedElement.copy(e.originalEvent.clipboardData); - break; - case "cut": - focusedElement.cut(e.originalEvent.clipboardData); - break; - case "paste": - focusedElement.paste(e.originalEvent.clipboardData); - break; - } - }); + $scope.$root.layoutDesignerHost = layoutDesignerHost; - // HACK: Workaround because of how Angular treats the DOM when elements are shifted around - input focus is sometimes lost. - window.setTimeout(function () { - $scope.$apply(function () { - if (!!$scope.element.focusedElement) - $scope.element.focusedElement.setIsFocused(); + layoutDesignerHost.element.on("replacecanvas", function (e, args) { + var editor = $scope.element; + var canvasData = { + data: args.canvas.data, + htmlId: args.canvas.htmlId, + htmlClass: args.canvas.htmlClass, + htmlStyle: args.canvas.htmlStyle, + isTemplated: args.canvas.isTemplated, + children: args.canvas.children + }; + + // HACK: Instead of simply updating the $scope.element with a new instance, we need to replace the entire orc-layout-editor markup + // in order for angular to rebind starting with the Canvas element. Otherwise, for some reason, it will rebind starting with the first child of Canvas. + // You can see this happening when setting a breakpoint in ScopeConfigurator where containers are initialized with drag & drop: on page load, the first element + // is a Canvas (good), but after having selected another template, the first element is (typically) a Grid (bad). + // Simply recompiling the orc-layout-editor directive will cause the entire thing to be generated, which works just fine as well (even though not is nice as simply leveraging model binding). + layoutDesignerHost.editor = window.layoutEditor = new LayoutEditor.Editor(editor.config, canvasData); + var template = ""; + var html = $compile(template)($scope); + $(".layout-editor-holder").html(html); + }); + + $scope.$root.editElement = function (element) { + var host = $scope.$root.layoutDesignerHost; + return host.editElement(element); + }; + + $scope.$root.addElement = function (contentType) { + var host = $scope.$root.layoutDesignerHost; + return host.addElement(contentType); + }; + + $scope.toggleInlineEditing = function () { + if (!$scope.element.inlineEditingIsActive) { + $scope.element.inlineEditingIsActive = true; + $element.find(".layout-toolbar-container").show(); + var selector = "#layout-editor-" + $scope.$id + " .layout-content-h-t-m-l .layout-content-markup[data-templated=false]"; + var firstContentEditorId = $(selector).first().attr("id"); + tinymce.init({ + selector: selector, + theme: "modern", + schema: "html5", + plugins: [ + "advlist autolink lists link image charmap print preview hr anchor pagebreak", + "searchreplace wordcount visualblocks visualchars code fullscreen", + "insertdatetime media nonbreaking table contextmenu directionality", + "emoticons template paste textcolor colorpicker textpattern", + "fullscreen autoresize" + ], + toolbar: "undo redo cut copy paste | bold italic | bullist numlist outdent indent formatselect | alignleft aligncenter alignright alignjustify ltr rtl | link unlink charmap | code fullscreen close", + convert_urls: false, + valid_elements: "*[*]", + // Shouldn't be needed due to the valid_elements setting, but TinyMCE would strip script.src without it. + extended_valid_elements: "script[type|defer|src|language]", + statusbar: false, + skin: "orchardlightgray", + inline: true, + fixed_toolbar_container: "#layout-editor-" + $scope.$id + " .layout-toolbar-container", + init_instance_callback: function (editor) { + if (editor.id == firstContentEditorId) + tinymce.execCommand("mceFocus", false, editor.id); + } }); - }, 100); + } + else { + tinymce.remove("#layout-editor-" + $scope.$id + " .layout-content-markup"); + $element.find(".layout-toolbar-container").hide(); + $scope.element.inlineEditingIsActive = false; + } + }; - e.preventDefault(); - } - }); - }, - templateUrl: environment.templateUrl("Editor"), - replace: true, - link: function (scope, element) { - // No clicks should propagate from the TinyMCE toolbars. - element.find(".layout-toolbar-container").click(function (e) { - e.stopPropagation(); - }); - // Intercept mousedown on editor while in inline editing mode to - // prevent current editor from losing focus. - element.mousedown(function (e) { - if (scope.element.inlineEditingIsActive) { - e.preventDefault(); + $(document).on("cut copy paste", function (e) { + // The real clipboard is supported, so disable the peudo clipboard. + clipboard.disable(); + var focusedElement = $scope.element.focusedElement; + if (!!focusedElement) { + $scope.$apply(function () { + switch (e.type) { + case "copy": + focusedElement.copy(e.originalEvent.clipboardData); + break; + case "cut": + focusedElement.cut(e.originalEvent.clipboardData); + break; + case "paste": + focusedElement.paste(e.originalEvent.clipboardData); + break; + } + }); + + // HACK: Workaround because of how Angular treats the DOM when elements are shifted around - input focus is sometimes lost. + window.setTimeout(function () { + $scope.$apply(function () { + if (!!$scope.element.focusedElement) + $scope.element.focusedElement.setIsFocused(); + }); + }, 100); + + e.preventDefault(); + } + }); + }, + templateUrl: environment.templateUrl("Editor"), + replace: true, + link: function (scope, element) { + // No clicks should propagate from the TinyMCE toolbars. + element.find(".layout-toolbar-container").click(function (e) { e.stopPropagation(); - } - }) - // Unfocus and unselect everything on click outside of canvas. - $(window).click(function (e) { - // Except when in inline editing mode. - if (!scope.element.inlineEditingIsActive) { - scope.$apply(function () { - scope.element.activeElement = null; - scope.element.focusedElement = null; - }); - } - }); - } - }; - }); \ No newline at end of file + }); + // Intercept mousedown on editor while in inline editing mode to + // prevent current editor from losing focus. + element.mousedown(function (e) { + if (scope.element.inlineEditingIsActive) { + e.preventDefault(); + e.stopPropagation(); + } + }) + // Unfocus and unselect everything on click outside of canvas. + $(window).click(function (e) { + // Except when in inline editing mode. + if (!scope.element.inlineEditingIsActive) { + scope.$apply(function () { + scope.element.activeElement = null; + scope.element.focusedElement = null; + }); + } + }); + } + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Grid.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Grid.js index 4ec328388..c87c27352 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Grid.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Grid.js @@ -1,15 +1,17 @@ angular .module("LayoutEditor") - .directive("orcLayoutGrid", function ($compile, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "y"; - }, - templateUrl: environment.templateUrl("Grid"), - replace: true - }; - }); \ No newline at end of file + .directive("orcLayoutGrid", ["$compile", "scopeConfigurator", "environment", + function ($compile, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "y"; + }, + templateUrl: environment.templateUrl("Grid"), + replace: true + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Popup.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Popup.js index c47992290..75167e286 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Popup.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Popup.js @@ -1,34 +1,36 @@ angular .module("LayoutEditor") - .directive("orcLayoutPopup", function () { - return { - restrict: "A", - link: function (scope, element, attrs) { - var popup = $(element); - var trigger = popup.closest(".layout-popup-trigger"); - var parentElement = popup.closest(".layout-element"); - trigger.click(function () { - popup.toggle(); - if (popup.is(":visible")) { - popup.position({ - my: attrs.orcLayoutPopupMy || "left top", - at: attrs.orcLayoutPopupAt || "left bottom+4px", - of: trigger - }); - popup.find("input").first().focus(); - } - }); - popup.click(function (e) { - e.stopPropagation(); - }); - parentElement.click(function (e) { - popup.hide(); - }); - popup.keydown(function (e) { - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 27) // Esc + .directive("orcLayoutPopup", [ + function () { + return { + restrict: "A", + link: function (scope, element, attrs) { + var popup = $(element); + var trigger = popup.closest(".layout-popup-trigger"); + var parentElement = popup.closest(".layout-element"); + trigger.click(function () { + popup.toggle(); + if (popup.is(":visible")) { + popup.position({ + my: attrs.orcLayoutPopupMy || "left top", + at: attrs.orcLayoutPopupAt || "left bottom+4px", + of: trigger + }); + popup.find("input").first().focus(); + } + }); + popup.click(function (e) { + e.stopPropagation(); + }); + parentElement.click(function (e) { popup.hide(); - e.stopPropagation(); - }); - } - }; - }); \ No newline at end of file + }); + popup.keydown(function (e) { + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 27) // Esc + popup.hide(); + e.stopPropagation(); + }); + } + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Row.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Row.js index 090990e24..b49eb86c1 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Row.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Row.js @@ -1,16 +1,18 @@ angular .module("LayoutEditor") - .directive("orcLayoutRow", function ($compile, scopeConfigurator, environment) { - return { - restrict: "E", - scope: { element: "=" }, - controller: function ($scope, $element) { - scopeConfigurator.configureForElement($scope, $element); - scopeConfigurator.configureForContainer($scope, $element); - $scope.sortableOptions["axis"] = "x"; - $scope.sortableOptions["ui-floating"] = true; - }, - templateUrl: environment.templateUrl("Row"), - replace: true - }; - }); \ No newline at end of file + .directive("orcLayoutRow", ["$compile", "scopeConfigurator", "environment", + function ($compile, scopeConfigurator, environment) { + return { + restrict: "E", + scope: { element: "=" }, + controller: function ($scope, $element) { + scopeConfigurator.configureForElement($scope, $element); + scopeConfigurator.configureForContainer($scope, $element); + $scope.sortableOptions["axis"] = "x"; + $scope.sortableOptions["ui-floating"] = true; + }, + templateUrl: environment.templateUrl("Row"), + replace: true + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Toolbox.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Toolbox.js index 0719c6c4f..f3edc0917 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Toolbox.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/Toolbox.js @@ -1,168 +1,170 @@ angular .module("LayoutEditor") - .directive("orcLayoutToolbox", function ($compile, environment) { - return { - restrict: "E", - controller: function ($scope, $element) { + .directive("orcLayoutToolbox", ["$compile", "environment", + function ($compile, environment) { + return { + restrict: "E", + controller: function ($scope, $element) { - $scope.resetElements = function () { + $scope.resetElements = function () { - $scope.gridElements = [ - LayoutEditor.Grid.from({ - toolboxIcon: "\uf00a", - toolboxLabel: "Grid", - toolboxDescription: "Empty grid.", - children: [] - }) - ]; - - $scope.rowElements = [ - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (1 column)", - toolboxDescription: "Row with 1 column.", - children: LayoutEditor.Column.times(1) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (2 columns)", - toolboxDescription: "Row with 2 columns.", - children: LayoutEditor.Column.times(2) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (3 columns)", - toolboxDescription: "Row with 3 columns.", - children: LayoutEditor.Column.times(3) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (4 columns)", - toolboxDescription: "Row with 4 columns.", - children: LayoutEditor.Column.times(4) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (6 columns)", - toolboxDescription: "Row with 6 columns.", - children: LayoutEditor.Column.times(6) - }), - LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (12 columns)", - toolboxDescription: "Row with 12 columns.", - children: LayoutEditor.Column.times(12) - }), LayoutEditor.Row.from({ - toolboxIcon: "\uf0c9", - toolboxLabel: "Row (empty)", - toolboxDescription: "Empty row.", - children: [] - }) - ]; - - $scope.columnElements = [ - LayoutEditor.Column.from({ - toolboxIcon: "\uf0db", - toolboxLabel: "Column", - toolboxDescription: "Empty column.", - width: 1, - offset: 0, - children: [] - }) - ]; - - $scope.contentElementCategories = _($scope.element.config.categories).map(function (category) { - return { - name: category.name, - elements: _(category.contentTypes).map(function (contentType) { - var type = contentType.type; - var factory = LayoutEditor.factories[type] || LayoutEditor.factories["Content"]; - var item = { - isTemplated: false, - contentType: contentType.id, - contentTypeLabel: contentType.label, - contentTypeClass: contentType.typeClass, - data: null, - hasEditor: contentType.hasEditor, - html: contentType.html - }; - var element = factory(item); - element.toolboxIcon = contentType.icon || "\uf1c9"; - element.toolboxLabel = contentType.label; - element.toolboxDescription = contentType.description; - return element; + $scope.gridElements = [ + LayoutEditor.Grid.from({ + toolboxIcon: "\uf00a", + toolboxLabel: "Grid", + toolboxDescription: "Empty grid.", + children: [] }) - }; - }); + ]; - }; + $scope.rowElements = [ + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (1 column)", + toolboxDescription: "Row with 1 column.", + children: LayoutEditor.Column.times(1) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (2 columns)", + toolboxDescription: "Row with 2 columns.", + children: LayoutEditor.Column.times(2) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (3 columns)", + toolboxDescription: "Row with 3 columns.", + children: LayoutEditor.Column.times(3) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (4 columns)", + toolboxDescription: "Row with 4 columns.", + children: LayoutEditor.Column.times(4) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (6 columns)", + toolboxDescription: "Row with 6 columns.", + children: LayoutEditor.Column.times(6) + }), + LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (12 columns)", + toolboxDescription: "Row with 12 columns.", + children: LayoutEditor.Column.times(12) + }), LayoutEditor.Row.from({ + toolboxIcon: "\uf0c9", + toolboxLabel: "Row (empty)", + toolboxDescription: "Empty row.", + children: [] + }) + ]; - $scope.resetElements(); + $scope.columnElements = [ + LayoutEditor.Column.from({ + toolboxIcon: "\uf0db", + toolboxLabel: "Column", + toolboxDescription: "Empty column.", + width: 1, + offset: 0, + children: [] + }) + ]; - $scope.getSortableOptions = function (type) { - var editorId = $element.closest(".layout-editor").attr("id"); - var parentClasses; - var placeholderClasses; - var floating = false; + $scope.contentElementCategories = _($scope.element.config.categories).map(function (category) { + return { + name: category.name, + elements: _(category.contentTypes).map(function (contentType) { + var type = contentType.type; + var factory = LayoutEditor.factories[type] || LayoutEditor.factories["Content"]; + var item = { + isTemplated: false, + contentType: contentType.id, + contentTypeLabel: contentType.label, + contentTypeClass: contentType.typeClass, + data: null, + hasEditor: contentType.hasEditor, + html: contentType.html + }; + var element = factory(item); + element.toolboxIcon = contentType.icon || "\uf1c9"; + element.toolboxLabel = contentType.label; + element.toolboxDescription = contentType.description; + return element; + }) + }; + }); - switch (type) { - case "Grid": - parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder"]; - placeholderClasses = "layout-element layout-container layout-grid ui-sortable-placeholder"; - break; - case "Row": - parentClasses = [".layout-grid"]; - placeholderClasses = "layout-element layout-container layout-row row ui-sortable-placeholder"; - break; - case "Column": - parentClasses = [".layout-row:not(.layout-row-full)"]; - placeholderClasses = "layout-element layout-container layout-column ui-sortable-placeholder"; - floating = true; // To ensure a smooth horizontal-list reordering. https://github.com/angular-ui/ui-sortable#floating - break; - case "Content": - parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder"]; - placeholderClasses = "layout-element layout-content ui-sortable-placeholder"; - break; - } + }; - return { - cursor: "move", - connectWith: _(parentClasses).map(function (e) { return "#" + editorId + " " + e + ":not(.layout-container-sealed) > .layout-element-wrapper > .layout-children"; }).join(", "), - placeholder: placeholderClasses, - "ui-floating": floating, - create: function (e, ui) { - e.target.isToolbox = true; // Will indicate to connected sortables that dropped items were sent from toolbox. - }, - start: function (e, ui) { - $scope.$apply(function () { - $scope.element.isDragging = true; - }); - }, - stop: function (e, ui) { - $scope.$apply(function () { - $scope.element.isDragging = false; - $scope.resetElements(); - }); - }, - over: function (e, ui) { - $scope.$apply(function () { - $scope.element.canvas.setIsDropTarget(false); - }); - }, - } - }; + $scope.resetElements(); - var layoutIsCollapsedCookieName = "layoutToolboxCategory_Layout_IsCollapsed"; - $scope.layoutIsCollapsed = $.cookie(layoutIsCollapsedCookieName) === "true"; + $scope.getSortableOptions = function (type) { + var editorId = $element.closest(".layout-editor").attr("id"); + var parentClasses; + var placeholderClasses; + var floating = false; - $scope.toggleLayoutIsCollapsed = function (e) { - $scope.layoutIsCollapsed = !$scope.layoutIsCollapsed; - $.cookie(layoutIsCollapsedCookieName, $scope.layoutIsCollapsed, { expires: 365 }); // Remember collapsed state for a year. - e.preventDefault(); - e.stopPropagation(); - }; - }, - templateUrl: environment.templateUrl("Toolbox"), - replace: true - }; - }); \ No newline at end of file + switch (type) { + case "Grid": + parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder"]; + placeholderClasses = "layout-element layout-container layout-grid ui-sortable-placeholder"; + break; + case "Row": + parentClasses = [".layout-grid"]; + placeholderClasses = "layout-element layout-container layout-row row ui-sortable-placeholder"; + break; + case "Column": + parentClasses = [".layout-row:not(.layout-row-full)"]; + placeholderClasses = "layout-element layout-container layout-column ui-sortable-placeholder"; + floating = true; // To ensure a smooth horizontal-list reordering. https://github.com/angular-ui/ui-sortable#floating + break; + case "Content": + parentClasses = [".layout-canvas", ".layout-column", ".layout-common-holder"]; + placeholderClasses = "layout-element layout-content ui-sortable-placeholder"; + break; + } + + return { + cursor: "move", + connectWith: _(parentClasses).map(function (e) { return "#" + editorId + " " + e + ":not(.layout-container-sealed) > .layout-element-wrapper > .layout-children"; }).join(", "), + placeholder: placeholderClasses, + "ui-floating": floating, + create: function (e, ui) { + e.target.isToolbox = true; // Will indicate to connected sortables that dropped items were sent from toolbox. + }, + start: function (e, ui) { + $scope.$apply(function () { + $scope.element.isDragging = true; + }); + }, + stop: function (e, ui) { + $scope.$apply(function () { + $scope.element.isDragging = false; + $scope.resetElements(); + }); + }, + over: function (e, ui) { + $scope.$apply(function () { + $scope.element.canvas.setIsDropTarget(false); + }); + }, + } + }; + + var layoutIsCollapsedCookieName = "layoutToolboxCategory_Layout_IsCollapsed"; + $scope.layoutIsCollapsed = $.cookie(layoutIsCollapsedCookieName) === "true"; + + $scope.toggleLayoutIsCollapsed = function (e) { + $scope.layoutIsCollapsed = !$scope.layoutIsCollapsed; + $.cookie(layoutIsCollapsedCookieName, $scope.layoutIsCollapsed, { expires: 365 }); // Remember collapsed state for a year. + e.preventDefault(); + e.stopPropagation(); + }; + }, + templateUrl: environment.templateUrl("Toolbox"), + replace: true + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/ToolboxGroup.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/ToolboxGroup.js index 6b2bc1355..7de7c3b70 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/ToolboxGroup.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Directives/ToolboxGroup.js @@ -1,20 +1,22 @@ angular .module("LayoutEditor") - .directive("orcLayoutToolboxGroup", function ($compile, environment) { - return { - restrict: "E", - scope: { category: "=" }, - controller: function ($scope, $element) { - var isCollapsedCookieName = "layoutToolboxCategory_" + $scope.category.name + "_IsCollapsed"; - $scope.isCollapsed = $.cookie(isCollapsedCookieName) === "true"; - $scope.toggleIsCollapsed = function (e) { - $scope.isCollapsed = !$scope.isCollapsed; - $.cookie(isCollapsedCookieName, $scope.isCollapsed, { expires: 365 }); // Remember collapsed state for a year. - e.preventDefault(); - e.stopPropagation(); - }; - }, - templateUrl: environment.templateUrl("ToolboxGroup"), - replace: true - }; - }); \ No newline at end of file + .directive("orcLayoutToolboxGroup", ["$compile", "environment", + function ($compile, environment) { + return { + restrict: "E", + scope: { category: "=" }, + controller: function ($scope, $element) { + var isCollapsedCookieName = "layoutToolboxCategory_" + $scope.category.name + "_IsCollapsed"; + $scope.isCollapsed = $.cookie(isCollapsedCookieName) === "true"; + $scope.toggleIsCollapsed = function (e) { + $scope.isCollapsed = !$scope.isCollapsed; + $.cookie(isCollapsedCookieName, $scope.isCollapsed, { expires: 365 }); // Remember collapsed state for a year. + e.preventDefault(); + e.stopPropagation(); + }; + }, + templateUrl: environment.templateUrl("ToolboxGroup"), + replace: true + }; + } + ]); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Services/Clipboard.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Services/Clipboard.js index 70ba97241..b0be0d474 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Services/Clipboard.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Services/Clipboard.js @@ -20,11 +20,13 @@ angular .module("LayoutEditor") - .factory("clipboard", function() { - return { - setData: LayoutEditor.Clipboard.setData, - getData: LayoutEditor.Clipboard.getData, - disable: LayoutEditor.Clipboard.disable - }; - }); + .factory("clipboard", [ + function() { + return { + setData: LayoutEditor.Clipboard.setData, + getData: LayoutEditor.Clipboard.getData, + disable: LayoutEditor.Clipboard.disable + }; + } + ]); })(LayoutEditor || (LayoutEditor = {})); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Services/ScopeConfigurator.js b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Services/ScopeConfigurator.js index fd41a58ba..dd5fbd221 100644 --- a/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Services/ScopeConfigurator.js +++ b/src/Orchard.Web/Modules/Orchard.Layouts/Scripts/LayoutEditor/Services/ScopeConfigurator.js @@ -1,317 +1,319 @@ angular .module("LayoutEditor") - .factory("scopeConfigurator", function ($timeout, clipboard) { - return { + .factory("scopeConfigurator", ["$timeout", "clipboard", + function ($timeout, clipboard) { + return { - configureForElement: function ($scope, $element) { + configureForElement: function ($scope, $element) { - $element.find(".layout-panel").click(function (e) { - e.stopPropagation(); - }); + $element.find(".layout-panel").click(function (e) { + e.stopPropagation(); + }); - $element.parent().keydown(function (e) { - var handled = false; - var resetFocus = false; - var element = $scope.element; + $element.parent().keydown(function (e) { + var handled = false; + var resetFocus = false; + var element = $scope.element; - if (element.editor.isDragging || element.editor.inlineEditingIsActive) - return; + if (element.editor.isDragging || element.editor.inlineEditingIsActive) + return; - // If the "real" clipboard works, then the pseudo-clipboard will have been disabled. - if (!clipboard.disabled) { - var focusedElement = element.editor.focusedElement; - if (!!focusedElement) { - // Pseudo clipboard handling for browsers not allowing real clipboard operations. - if (e.ctrlKey) { - switch (e.which) { - case 67: // C - focusedElement.copy(clipboard); - break; - case 88: // X - focusedElement.cut(clipboard); - break; - case 86: // V - focusedElement.paste(clipboard); - break; + // If the "real" clipboard works, then the pseudo-clipboard will have been disabled. + if (!clipboard.disabled) { + var focusedElement = element.editor.focusedElement; + if (!!focusedElement) { + // Pseudo clipboard handling for browsers not allowing real clipboard operations. + if (e.ctrlKey) { + switch (e.which) { + case 67: // C + focusedElement.copy(clipboard); + break; + case 88: // X + focusedElement.cut(clipboard); + break; + case 86: // V + focusedElement.paste(clipboard); + break; + } } } } - } - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 46) { // Del - $scope.delete(element); - handled = true; - } else if (!e.ctrlKey && !e.shiftKey && !e.altKey && (e.which == 32 || e.which == 27)) { // Space or Esc - $element.find(".layout-panel-action-properties").first().click(); - handled = true; - } - - if (element.type == "Content") { // This is a content element. - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 13) { // Enter - $element.find(".layout-panel-action-edit").first().click(); + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 46) { // Del + $scope.delete(element); handled = true; - } - } - - if (!!element.children) { // This is a container. - if (!e.ctrlKey && !e.shiftKey && e.altKey && e.which == 40) { // Alt+Down - if (element.children.length > 0) - element.children[0].setIsFocused(); + } else if (!e.ctrlKey && !e.shiftKey && !e.altKey && (e.which == 32 || e.which == 27)) { // Space or Esc + $element.find(".layout-panel-action-properties").first().click(); handled = true; } - if (element.type == "Column") { // This is a column. - var connectAdjacent = !e.ctrlKey; - if (e.which == 37) { // Left - if (e.altKey) - element.expandLeft(connectAdjacent); - if (e.shiftKey) - element.contractRight(connectAdjacent); - handled = true; - } else if (e.which == 39) { // Right - if (e.altKey) - element.contractLeft(connectAdjacent); - if (e.shiftKey) - element.expandRight(connectAdjacent); + if (element.type == "Content") { // This is a content element. + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 13) { // Enter + $element.find(".layout-panel-action-edit").first().click(); handled = true; } } + + if (!!element.children) { // This is a container. + if (!e.ctrlKey && !e.shiftKey && e.altKey && e.which == 40) { // Alt+Down + if (element.children.length > 0) + element.children[0].setIsFocused(); + handled = true; + } + + if (element.type == "Column") { // This is a column. + var connectAdjacent = !e.ctrlKey; + if (e.which == 37) { // Left + if (e.altKey) + element.expandLeft(connectAdjacent); + if (e.shiftKey) + element.contractRight(connectAdjacent); + handled = true; + } else if (e.which == 39) { // Right + if (e.altKey) + element.contractLeft(connectAdjacent); + if (e.shiftKey) + element.expandRight(connectAdjacent); + handled = true; + } + } + } + + if (!!element.parent) { // This is a child. + if (e.altKey && e.which == 38) { // Alt+Up + element.parent.setIsFocused(); + handled = true; + } + + if (element.parent.type == "Row") { // Parent is a horizontal container. + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 37) { // Left + element.parent.moveFocusPrevChild(element); + handled = true; + } + else if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 39) { // Right + element.parent.moveFocusNextChild(element); + handled = true; + } + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 37) { // Ctrl+Left + element.moveUp(); + resetFocus = true; + handled = true; + } + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 39) { // Ctrl+Right + element.moveDown(); + handled = true; + } + } + else { // Parent is a vertical container. + if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 38) { // Up + element.parent.moveFocusPrevChild(element); + handled = true; + } + else if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 40) { // Down + element.parent.moveFocusNextChild(element); + handled = true; + } + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 38) { // Ctrl+Up + element.moveUp(); + resetFocus = true; + handled = true; + } + else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 40) { // Ctrl+Down + element.moveDown(); + handled = true; + } + } + } + + if (handled) { + e.preventDefault(); + } + + e.stopPropagation(); + + $scope.$apply(); // Event is not triggered by Angular directive but raw event handler on element. + + // HACK: Workaround because of how Angular treats the DOM when elements are shifted around - input focus is sometimes lost. + if (resetFocus) { + window.setTimeout(function () { + $scope.$apply(function () { + element.editor.focusedElement.setIsFocused(); + }); + }, 100); + } + }); + + $scope.element.setIsFocusedEventHandlers.push(function () { + $element.parent().focus(); + }); + + $scope.delete = function (element) { + element.delete(); } + }, - if (!!element.parent) { // This is a child. - if (e.altKey && e.which == 38) { // Alt+Up - element.parent.setIsFocused(); - handled = true; - } + configureForContainer: function ($scope, $element) { + var element = $scope.element; - if (element.parent.type == "Row") { // Parent is a horizontal container. - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 37) { // Left - element.parent.moveFocusPrevChild(element); - handled = true; - } - else if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 39) { // Right - element.parent.moveFocusNextChild(element); - handled = true; - } - else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 37) { // Ctrl+Left - element.moveUp(); - resetFocus = true; - handled = true; - } - else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 39) { // Ctrl+Right - element.moveDown(); - handled = true; - } - } - else { // Parent is a vertical container. - if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 38) { // Up - element.parent.moveFocusPrevChild(element); - handled = true; - } - else if (!e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 40) { // Down - element.parent.moveFocusNextChild(element); - handled = true; - } - else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 38) { // Ctrl+Up - element.moveUp(); - resetFocus = true; - handled = true; - } - else if (e.ctrlKey && !e.shiftKey && !e.altKey && e.which == 40) { // Ctrl+Down - element.moveDown(); - handled = true; - } - } - } + //$scope.isReceiving = false; // True when container is receiving an external element via drag/drop. + $scope.getShowChildrenPlaceholder = function () { + return $scope.element.children.length === 0 && !$scope.element.getIsDropTarget(); + }; - if (handled) { - e.preventDefault(); - } - - e.stopPropagation(); - - $scope.$apply(); // Event is not triggered by Angular directive but raw event handler on element. - - // HACK: Workaround because of how Angular treats the DOM when elements are shifted around - input focus is sometimes lost. - if (resetFocus) { - window.setTimeout(function () { + $scope.sortableOptions = { + cursor: "move", + delay: 150, + disabled: element.getIsSealed(), + distance: 5, + //handle: element.children.length < 2 ? ".imaginary-class" : false, // For some reason doesn't get re-evaluated after adding more children. + start: function (e, ui) { $scope.$apply(function () { - element.editor.focusedElement.setIsFocused(); + element.setIsDropTarget(true); + element.editor.isDragging = true; }); - }, 100); - } - }); - - $scope.element.setIsFocusedEventHandlers.push(function () { - $element.parent().focus(); - }); - - $scope.delete = function (element) { - element.delete(); - } - }, - - configureForContainer: function ($scope, $element) { - var element = $scope.element; - - //$scope.isReceiving = false; // True when container is receiving an external element via drag/drop. - $scope.getShowChildrenPlaceholder = function () { - return $scope.element.children.length === 0 && !$scope.element.getIsDropTarget(); - }; - - $scope.sortableOptions = { - cursor: "move", - delay: 150, - disabled: element.getIsSealed(), - distance: 5, - //handle: element.children.length < 2 ? ".imaginary-class" : false, // For some reason doesn't get re-evaluated after adding more children. - start: function (e, ui) { - $scope.$apply(function () { - element.setIsDropTarget(true); - element.editor.isDragging = true; - }); - // Make the drop target placeholder as high as the item being dragged. - ui.placeholder.height(ui.item.height() - 4); - ui.placeholder.css("min-height", 0); - }, - stop: function (e, ui) { - $scope.$apply(function () { - element.editor.isDragging = false; - element.setIsDropTarget(false); - }); - }, - over: function (e, ui) { - if (!!ui.sender && !!ui.sender[0].isToolbox) { - if (!!ui.sender[0].dropTargetTimeout) { - $timeout.cancel(ui.sender[0].dropTargetTimeout); - ui.sender[0].dropTargetTimeout = null; - } - $timeout(function () { - if (element.type == "Row") { - // If there was a previous drop target and it was a row, roll back any pending column adds to it. - var previousDropTarget = element.editor.dropTargetElement; - if (!!previousDropTarget && previousDropTarget.type == "Row") - previousDropTarget.rollbackAddColumn(); - } + // Make the drop target placeholder as high as the item being dragged. + ui.placeholder.height(ui.item.height() - 4); + ui.placeholder.css("min-height", 0); + }, + stop: function (e, ui) { + $scope.$apply(function () { + element.editor.isDragging = false; element.setIsDropTarget(false); }); - ui.sender[0].dropTargetTimeout = $timeout(function () { - if (element.type == "Row") { - var receivedColumn = ui.item.sortable.model; - var receivedColumnWidth = Math.floor(12 / (element.children.length + 1)); - receivedColumn.width = receivedColumnWidth; - receivedColumn.offset = 0; - element.beginAddColumn(receivedColumnWidth); - // Make the drop target placeholder the correct width and as high as the highest existing column in the row. - var maxHeight = _.max(_($element.find("> .layout-children > .layout-column:not(.ui-sortable-placeholder)")).map(function (e) { - return $(e).height(); - })); - for (i = 1; i <= 12; i++) - ui.placeholder.removeClass("col-xs-" + i); - ui.placeholder.addClass("col-xs-" + receivedColumn.width); - if (maxHeight > 0) { - ui.placeholder.height(maxHeight); - ui.placeholder.css("min-height", 0); - } - else { - ui.placeholder.height(0); - ui.placeholder.css("min-height", ""); - } - } - element.setIsDropTarget(true); - }, 150); - } - }, - receive: function (e, ui) { - if (!!ui.sender && !!ui.sender[0].isToolbox) { - $scope.$apply(function () { - var receivedElement = ui.item.sortable.model; - if (!!receivedElement) { - if (element.type == "Row") - element.commitAddColumn(); - // Should ideally call LayoutEditor.Container.addChild() instead, but since this handler - // is run *before* the ui-sortable directive's handler, if we try to add the child to the - // array that handler will get an exception when trying to do the same. - // Because of this, we need to invoke "setParent" so that specific container types can perform element speficic initialization. - receivedElement.setEditor(element.editor); - receivedElement.setParent(element); - if (receivedElement.type == "Content" && !!receivedElement.hasEditor) { - $scope.$root.editElement(receivedElement).then(function (args) { - if (!args.cancel) { - receivedElement.data = decodeURIComponent(args.element.data); - receivedElement.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20"))); - } - $timeout(function () { - if (!!args.cancel) - receivedElement.delete(); - else - receivedElement.setIsFocused(); - //$scope.isReceiving = false; - element.setIsDropTarget(false); - - }); - return; - }); - } + }, + over: function (e, ui) { + if (!!ui.sender && !!ui.sender[0].isToolbox) { + if (!!ui.sender[0].dropTargetTimeout) { + $timeout.cancel(ui.sender[0].dropTargetTimeout); + ui.sender[0].dropTargetTimeout = null; } $timeout(function () { - //$scope.isReceiving = false; + if (element.type == "Row") { + // If there was a previous drop target and it was a row, roll back any pending column adds to it. + var previousDropTarget = element.editor.dropTargetElement; + if (!!previousDropTarget && previousDropTarget.type == "Row") + previousDropTarget.rollbackAddColumn(); + } element.setIsDropTarget(false); - if (!!receivedElement) - receivedElement.setIsFocused(); }); - }); + ui.sender[0].dropTargetTimeout = $timeout(function () { + if (element.type == "Row") { + var receivedColumn = ui.item.sortable.model; + var receivedColumnWidth = Math.floor(12 / (element.children.length + 1)); + receivedColumn.width = receivedColumnWidth; + receivedColumn.offset = 0; + element.beginAddColumn(receivedColumnWidth); + // Make the drop target placeholder the correct width and as high as the highest existing column in the row. + var maxHeight = _.max(_($element.find("> .layout-children > .layout-column:not(.ui-sortable-placeholder)")).map(function (e) { + return $(e).height(); + })); + for (i = 1; i <= 12; i++) + ui.placeholder.removeClass("col-xs-" + i); + ui.placeholder.addClass("col-xs-" + receivedColumn.width); + if (maxHeight > 0) { + ui.placeholder.height(maxHeight); + ui.placeholder.css("min-height", 0); + } + else { + ui.placeholder.height(0); + ui.placeholder.css("min-height", ""); + } + } + element.setIsDropTarget(true); + }, 150); + } + }, + receive: function (e, ui) { + if (!!ui.sender && !!ui.sender[0].isToolbox) { + $scope.$apply(function () { + var receivedElement = ui.item.sortable.model; + if (!!receivedElement) { + if (element.type == "Row") + element.commitAddColumn(); + // Should ideally call LayoutEditor.Container.addChild() instead, but since this handler + // is run *before* the ui-sortable directive's handler, if we try to add the child to the + // array that handler will get an exception when trying to do the same. + // Because of this, we need to invoke "setParent" so that specific container types can perform element speficic initialization. + receivedElement.setEditor(element.editor); + receivedElement.setParent(element); + if (receivedElement.type == "Content" && !!receivedElement.hasEditor) { + $scope.$root.editElement(receivedElement).then(function (args) { + if (!args.cancel) { + receivedElement.data = decodeURIComponent(args.element.data); + receivedElement.setHtml(decodeURIComponent(args.element.html.replace(/\+/g, "%20"))); + } + $timeout(function () { + if (!!args.cancel) + receivedElement.delete(); + else + receivedElement.setIsFocused(); + //$scope.isReceiving = false; + element.setIsDropTarget(false); + + }); + return; + }); + } + } + $timeout(function () { + //$scope.isReceiving = false; + element.setIsDropTarget(false); + if (!!receivedElement) + receivedElement.setIsFocused(); + }); + }); + } } - } - }; + }; - $scope.click = function (child, e) { - if (!child.editor.isDragging) - child.setIsFocused(); - e.stopPropagation(); - }; + $scope.click = function (child, e) { + if (!child.editor.isDragging) + child.setIsFocused(); + e.stopPropagation(); + }; - $scope.getClasses = function (child) { - var result = ["layout-element"]; + $scope.getClasses = function (child) { + var result = ["layout-element"]; - if (!!child.children) { - result.push("layout-container"); - if (child.getIsSealed()) - result.push("layout-container-sealed"); - } + if (!!child.children) { + result.push("layout-container"); + if (child.getIsSealed()) + result.push("layout-container-sealed"); + } - result.push("layout-" + child.type.toLowerCase()); + result.push("layout-" + child.type.toLowerCase()); - if (!!child.dropTargetClass) - result.push(child.dropTargetClass); + if (!!child.dropTargetClass) + result.push(child.dropTargetClass); - // TODO: Move these to either the Column directive or the Column model class. - if (child.type == "Row") { - result.push("row"); - if (!child.canAddColumn()) - result.push("layout-row-full"); - } - if (child.type == "Column") { - result.push("col-xs-" + child.width); - result.push("col-xs-offset-" + child.offset); - } - if (child.type == "Content") - result.push("layout-content-" + child.contentTypeClass); + // TODO: Move these to either the Column directive or the Column model class. + if (child.type == "Row") { + result.push("row"); + if (!child.canAddColumn()) + result.push("layout-row-full"); + } + if (child.type == "Column") { + result.push("col-xs-" + child.width); + result.push("col-xs-offset-" + child.offset); + } + if (child.type == "Content") + result.push("layout-content-" + child.contentTypeClass); - if (child.getIsActive()) - result.push("layout-element-active"); - if (child.getIsFocused()) - result.push("layout-element-focused"); - if (child.getIsSelected()) - result.push("layout-element-selected"); - if (child.getIsDropTarget()) - result.push("layout-element-droptarget"); - if (child.isTemplated) - result.push("layout-element-templated"); + if (child.getIsActive()) + result.push("layout-element-active"); + if (child.getIsFocused()) + result.push("layout-element-focused"); + if (child.getIsSelected()) + result.push("layout-element-selected"); + if (child.getIsDropTarget()) + result.push("layout-element-droptarget"); + if (child.isTemplated) + result.push("layout-element-templated"); - return result; - }; - } + return result; + }; + } + }; } - }); \ No newline at end of file + ]); \ No newline at end of file