diff --git a/src/Umbraco.Web.UI.Client/src/less/listview.less b/src/Umbraco.Web.UI.Client/src/less/listview.less index b6efa9567f..88762edf0c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/listview.less +++ b/src/Umbraco.Web.UI.Client/src/less/listview.less @@ -243,7 +243,8 @@ background-color: @grayLighter } -.table-striped tbody i:hover { +/* don't hide all icons, e.g. for a sortable handle */ +.umb-listview .table-striped tbody i:not(.handle):hover { display: none !important } diff --git a/src/Umbraco.Web.UI.Client/src/less/property-editors.less b/src/Umbraco.Web.UI.Client/src/less/property-editors.less index f274740020..a06484b8a0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/property-editors.less +++ b/src/Umbraco.Web.UI.Client/src/less/property-editors.less @@ -714,7 +714,45 @@ ul.color-picker li a { } // -// tags +// Related links +// -------------------------------------------------- +.umb-relatedlinks table > tr > td { word-wrap:break-word; word-break: break-all; border-bottom: 1px solid transparent; } +.umb-relatedlinks .handle { cursor:move; } +.umb-relatedlinks table > tbody > tr.unsortable .handle { cursor:default; } + +.umb-relatedlinks table td.col-sort { width: 20px; } +.umb-relatedlinks table td.col-caption { min-width: 200px; } +.umb-relatedlinks table td.col-link { min-width: 200px; } +.umb-relatedlinks table td.col-actions { min-width: 120px; } + +.umb-relatedlinks table td.col-caption .control-wrapper, +.umb-relatedlinks table td.col-link .control-wrapper { display: flex; } + +.umb-relatedlinks table td.col-caption .control-wrapper input[type="text"], +.umb-relatedlinks table td.col-link .control-wrapper input[type="text"] { width: auto; flex: 1; } + +/* sortable placeholder */ +.umb-relatedlinks .sortable-placeholder { + background-color: @tableBackgroundAccent; + display: table-row; +} +.umb-relatedlinks .sortable-placeholder > td { + display: table-cell; + padding: 8px; +} +.umb-relatedlinks .ui-sortable-helper { + display: table-row; + background-color: @white; + opacity: 0.7; +} +.umb-relatedlinks .ui-sortable-helper > td { + display: table-cell; + border-bottom: 1px solid @tableBorder; +} + + +// +// Tags // -------------------------------------------------- .umb-tags{border: @grayLighter solid 1px; padding: 10px; font-size: 13px; text-shadow: none;} .umb-tags .tag{cursor: pointer; margin: 7px; padding: 7px; background: @blue} diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js index e10bce28b2..cb6c8e595a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.controller.js @@ -1,16 +1,19 @@ angular.module("umbraco") .controller("Umbraco.PropertyEditors.RelatedLinksController", - function ($rootScope, $scope, dialogService) { + function ($rootScope, $scope, dialogService, iconHelper) { if (!$scope.model.value) { $scope.model.value = []; } + + $scope.model.config.max = isNumeric($scope.model.config.max) && $scope.model.config.max !== 0 ? $scope.model.config.max : Number.MAX_VALUE; $scope.newCaption = ''; $scope.newLink = 'http://'; $scope.newNewWindow = false; $scope.newInternal = null; $scope.newInternalName = ''; + $scope.newInternalIcon = null; $scope.addExternal = true; $scope.currentEditLink = null; $scope.hasError = false; @@ -72,7 +75,6 @@ } $scope.model.value[idx].edit = true; }; - $scope.saveEdit = function (idx) { $scope.model.value[idx].title = $scope.model.value[idx].caption; @@ -107,6 +109,7 @@ this.edit = false; this.isInternal = true; this.internalName = $scope.newInternalName; + this.internalIcon = $scope.newInternalIcon; this.type = "internal"; this.title = $scope.newCaption; }; @@ -117,7 +120,7 @@ $scope.newNewWindow = false; $scope.newInternal = null; $scope.newInternalName = ''; - + $scope.newInternalIcon = null; } $event.preventDefault(); }; @@ -141,23 +144,73 @@ $scope.model.value[index + direction] = temp; }; + //helper for determining if a user can add items + $scope.canAdd = function () { + return $scope.model.config.max <= 0 || $scope.model.config.max > countVisible(); + } + + //helper that returns if an item can be sorted + $scope.canSort = function () { + return countVisible() > 1; + } + $scope.sortableOptions = { - containment: 'parent', + axis: 'y', + handle: '.handle', cursor: 'move', + cancel: '.no-drag', + containment: 'parent', + placeholder: 'sortable-placeholder', + forcePlaceholderSize: true, helper: function (e, ui) { - // When sorting , the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ + // When sorting table rows, the cells collapse. This helper fixes that: http://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/ ui.children().each(function () { $(this).width($(this).width()); }); return ui; }, - items: '> tr', + items: '> tr:not(.unsortable)', tolerance: 'pointer', update: function (e, ui) { - + // Get the new and old index for the moved element (using the URL as the identifier) + var newIndex = ui.item.index(); + var movedLinkUrl = ui.item.attr('data-link'); + var originalIndex = getElementIndexByUrl(movedLinkUrl); + + // Move the element in the model + var movedElement = $scope.model.value[originalIndex]; + $scope.model.value.splice(originalIndex, 1); + $scope.model.value.splice(newIndex, 0, movedElement); + }, + start: function (e, ui) { + //ui.placeholder.html(""); + + // Build a placeholder cell that spans all the cells in the row: http://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size + var cellCount = 0; + $('td, th', ui.helper).each(function () { + // For each td or th try and get it's colspan attribute, and add that or 1 to the total + var colspan = 1; + var colspanAttr = $(this).attr('colspan'); + if (colspanAttr > 1) { + colspan = colspanAttr; + } + cellCount += colspan; + }); + + // Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr + ui.placeholder.html('').height(ui.item.height()); } }; + //helper to count what is visible + function countVisible() { + return $scope.model.value.length; + } + + function isNumeric(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + } + function getElementIndexByUrl(url) { for (var i = 0; i < $scope.model.value.length; i++) { if ($scope.model.value[i].link == url) { @@ -172,10 +225,12 @@ if ($scope.currentEditLink != null) { $scope.currentEditLink.internal = data.id; $scope.currentEditLink.internalName = data.name; + $scope.currentEditLink.internalIcon = iconHelper.convertFromLegacyIcon(data.icon); $scope.currentEditLink.link = data.id; } else { $scope.newInternal = data.id; $scope.newInternalName = data.name; + $scope.newInternalIcon = iconHelper.convertFromLegacyIcon(data.icon); } } }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html index 8b3381bf75..c6e02976f5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/relatedlinks/relatedlinks.html @@ -11,68 +11,79 @@ - - - + + + {{link.caption}} - +
+ +
- +
+ {{link.link}}
-
- or choose internal page -
+
+ +
+ or choose internal page +
+ Choose
or enter external link
- + {{link.newWindow}} - - +
-
+
- - + - - - -
+ + +
+ +
+ + +
+ + or + choose internal page +
-
- Choose
- or enter external link -
- - - -
- -
- +
+ + Choose
+ or enter external link +
+ + + +
+ +
+ @@ -84,4 +95,4 @@ position="right"> -
+
\ No newline at end of file diff --git a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs index 3009b39518..1fc4d7f471 100644 --- a/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs +++ b/src/Umbraco.Web/PropertyEditors/RelatedLinksPropertyEditor.cs @@ -11,5 +11,15 @@ namespace Umbraco.Web.PropertyEditors [PropertyEditor(Constants.PropertyEditors.RelatedLinksAlias, "Related links", "relatedlinks", ValueType ="JSON", Icon="icon-thumbnail-list", Group="pickers")] public class RelatedLinksPropertyEditor : PropertyEditor { + protected override PreValueEditor CreatePreValueEditor() + { + return new RelatedLinksPreValueEditor(); + } + + internal class RelatedLinksPreValueEditor : PreValueEditor + { + [PreValueField("max", "Maximum number of links", "number", Description = "Enter the maximum amount of links to be added, enter 0 for unlimited")] + public int Maximum { get; set; } + } } }