mirror of
https://github.com/OrchardCMS/Orchard.git
synced 2026-02-09 09:16:41 +08:00
986 lines
51 KiB
JavaScript
986 lines
51 KiB
JavaScript
angular.module("LayoutEditor", ["ngSanitize", "ngResource", "ui.sortable"]);
|
|
var LayoutEditor;
|
|
(function(LayoutEditor) {
|
|
|
|
var Clipboard = function () {
|
|
var self = this;
|
|
this.clipboardData = {};
|
|
this.setData = function(contentType, data, realClipBoard) {
|
|
self.clipboardData[contentType] = data;
|
|
};
|
|
this.getData = function (contentType, realClipBoard) {
|
|
return self.clipboardData[contentType];
|
|
};
|
|
|
|
this.disable = function() {
|
|
this.disabled = true;
|
|
};
|
|
}
|
|
|
|
LayoutEditor.Clipboard = new Clipboard();
|
|
|
|
angular
|
|
.module("LayoutEditor")
|
|
.factory("clipboard", [
|
|
function() {
|
|
return {
|
|
setData: LayoutEditor.Clipboard.setData,
|
|
getData: LayoutEditor.Clipboard.getData,
|
|
disable: LayoutEditor.Clipboard.disable
|
|
};
|
|
}
|
|
]);
|
|
})(LayoutEditor || (LayoutEditor = {}));
|
|
angular
|
|
.module("LayoutEditor")
|
|
.factory("scopeConfigurator", ["$timeout", "clipboard",
|
|
function ($timeout, clipboard) {
|
|
return {
|
|
|
|
configureForElement: function ($scope, $element) {
|
|
|
|
$element.find(".layout-panel").click(function (e) {
|
|
e.stopPropagation();
|
|
});
|
|
|
|
$element.parent().keydown(function (e) {
|
|
var handled = false;
|
|
var resetFocus = false;
|
|
var element = $scope.element;
|
|
|
|
|
|
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 (!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();
|
|
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();
|
|
}
|
|
},
|
|
|
|
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();
|
|
}
|
|
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.hasEditor) {
|
|
$scope.$root.editElement(receivedElement).then(function (args) {
|
|
if (!args.cancel) {
|
|
receivedElement.data = args.element.data;
|
|
receivedElement.setHtml(args.element.html);
|
|
}
|
|
$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", ["environment",
|
|
function (environment) {
|
|
return {
|
|
restrict: "E",
|
|
scope: {},
|
|
controller: ["$scope", "$element", "$attrs", "$compile", "clipboard",
|
|
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 = "<orc-layout-editor" + " model='window.layoutEditor' />";
|
|
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-html .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: ["$scope", "$element", "$attrs",
|
|
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 = "<orc-layout-" + scope.element.type.toLowerCase() + " element='element' />";
|
|
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: ["$scope", "$element",
|
|
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: ["$scope", "$element",
|
|
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 = args.element.data;
|
|
$scope.element.setHtml(args.element.html);
|
|
});
|
|
});
|
|
};
|
|
|
|
// 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($scope.element.html);
|
|
}
|
|
],
|
|
templateUrl: environment.templateUrl("Content"),
|
|
replace: true
|
|
};
|
|
}
|
|
]);
|
|
angular
|
|
.module("LayoutEditor")
|
|
.directive("orcLayoutHtml", ["$sce", "scopeConfigurator", "environment",
|
|
function ($sce, scopeConfigurator, environment) {
|
|
return {
|
|
restrict: "E",
|
|
scope: { element: "=" },
|
|
controller: ["$scope", "$element",
|
|
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 = args.element.data;
|
|
$scope.element.setHtml(args.element.html);
|
|
});
|
|
});
|
|
};
|
|
$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($scope.element.html);
|
|
}
|
|
],
|
|
templateUrl: environment.templateUrl("Html"),
|
|
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: ["$scope", "$element",
|
|
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: ["$scope", "$element",
|
|
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: ["$scope", "$element",
|
|
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: ["$scope", "$element",
|
|
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
|
|
};
|
|
}
|
|
]);
|