no message

This commit is contained in:
Niels Lyngsø
2020-06-24 09:16:48 +02:00
82 changed files with 1244 additions and 455 deletions
@@ -68,7 +68,7 @@ namespace Umbraco.Core.Compose
{
var httpContext = HttpContext.Current == null ? (HttpContextBase) null : new HttpContextWrapper(HttpContext.Current);
var ip = httpContext.GetCurrentRequestIpAddress();
if (ip.ToLowerInvariant().StartsWith("unknown")) ip = "";
if (ip == null || ip.ToLowerInvariant().StartsWith("unknown")) ip = "";
return ip;
}
}
@@ -62,6 +62,10 @@ namespace Umbraco.Core.Manifest
partA = "member";
partB = member.ContentType.Alias;
break;
case IContentType contentType:
partA = "contentType";
partB = contentType.Alias;
break;
default:
return null;
@@ -2,168 +2,441 @@
namespace Umbraco.Core.Models.PublishedContent
{
/// <summary>
/// Represents an item in an array that stores its own index and the total count.
/// </summary>
/// <typeparam name="TContent">The type of the content.</typeparam>
public class IndexedArrayItem<TContent>
{
/// <summary>
/// Initializes a new instance of the <see cref="IndexedArrayItem{TContent}" /> class.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="index">The index.</param>
public IndexedArrayItem(TContent content, int index)
{
Content = content;
Index = index;
}
/// <summary>
/// Gets the content.
/// </summary>
/// <value>
/// The content.
/// </value>
public TContent Content { get; }
/// <summary>
/// Gets the index.
/// </summary>
/// <value>
/// The index.
/// </value>
public int Index { get; }
/// <summary>
/// Gets the total count.
/// </summary>
/// <value>
/// The total count.
/// </value>
public int TotalCount { get; internal set; }
/// <summary>
/// Determines whether this item is the first.
/// </summary>
/// <returns>
/// <c>true</c> if this item is the first; otherwise, <c>false</c>.
/// </returns>
public bool IsFirst()
{
return Index == 0;
}
/// <summary>
/// If this item is the first, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsFirst(string valueIfTrue)
{
return IsFirst(valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is the first, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsFirst(string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsFirst() ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsFirst() ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// Determines whether this item is not the first.
/// </summary>
/// <returns>
/// <c>true</c> if this item is not the first; otherwise, <c>false</c>.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public bool IsNotFirst()
{
return IsFirst() == false;
}
/// <summary>
/// If this item is not the first, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsNotFirst(string valueIfTrue)
{
return IsNotFirst(valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is not the first, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsNotFirst(string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsNotFirst() ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsNotFirst() ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// Determines whether this item is at the specified <paramref name="index" />.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>
/// <c>true</c> if this item is at the specified <paramref name="index" />; otherwise, <c>false</c>.
/// </returns>
public bool IsIndex(int index)
{
return Index == index;
}
/// <summary>
/// If this item is at the specified <paramref name="index" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsIndex(int index, string valueIfTrue)
{
return IsIndex(index, valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is at the specified <paramref name="index" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsIndex(int index, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsIndex(index) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsIndex(index) ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// Determines whether this item is at an index that can be divided by the specified <paramref name="modulus" />.
/// </summary>
/// <param name="modulus">The modulus.</param>
/// <returns>
/// <c>true</c> if this item is at an index that can be divided by the specified <paramref name="modulus" />; otherwise, <c>false</c>.
/// </returns>
public bool IsModZero(int modulus)
{
return Index % modulus == 0;
}
/// <summary>
/// If this item is at an index that can be divided by the specified <paramref name="modulus" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="modulus">The modulus.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsModZero(int modulus, string valueIfTrue)
{
return IsModZero(modulus, valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is at an index that can be divided by the specified <paramref name="modulus" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="modulus">The modulus.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsModZero(int modulus, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsModZero(modulus) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsModZero(modulus) ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// Determines whether this item is not at an index that can be divided by the specified <paramref name="modulus" />.
/// </summary>
/// <param name="modulus">The modulus.</param>
/// <returns>
/// <c>true</c> if this item is not at an index that can be divided by the specified <paramref name="modulus" />; otherwise, <c>false</c>.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public bool IsNotModZero(int modulus)
{
return IsModZero(modulus) == false;
}
/// <summary>
/// If this item is not at an index that can be divided by the specified <paramref name="modulus" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="modulus">The modulus.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsNotModZero(int modulus, string valueIfTrue)
{
return IsNotModZero(modulus, valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is not at an index that can be divided by the specified <paramref name="modulus" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="modulus">The modulus.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsNotModZero(int modulus, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsNotModZero(modulus) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsNotModZero(modulus) ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// Determines whether this item is not at the specified <paramref name="index" />.
/// </summary>
/// <param name="index">The index.</param>
/// <returns>
/// <c>true</c> if this item is not at the specified <paramref name="index" />; otherwise, <c>false</c>.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public bool IsNotIndex(int index)
{
return IsIndex(index) == false;
}
/// <summary>
/// If this item is not at the specified <paramref name="index" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsNotIndex(int index, string valueIfTrue)
{
return IsNotIndex(index, valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is at the specified <paramref name="index" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsNotIndex(int index, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsNotIndex(index) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsNotIndex(index) ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// Determines whether this item is the last.
/// </summary>
/// <returns>
/// <c>true</c> if this item is the last; otherwise, <c>false</c>.
/// </returns>
public bool IsLast()
{
return Index == TotalCount - 1;
}
/// <summary>
/// If this item is the last, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsLast(string valueIfTrue)
{
return IsLast(valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is the last, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsLast(string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsLast() ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsLast() ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// Determines whether this item is not the last.
/// </summary>
/// <returns>
/// <c>true</c> if this item is not the last; otherwise, <c>false</c>.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public bool IsNotLast()
{
return IsLast() == false;
}
/// <summary>
/// If this item is not the last, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsNotLast(string valueIfTrue)
{
return IsNotLast(valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is not the last, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsNotLast(string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsNotLast() ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsNotLast() ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// Determines whether this item is at an even index.
/// </summary>
/// <returns>
/// <c>true</c> if this item is at an even index; otherwise, <c>false</c>.
/// </returns>
public bool IsEven()
{
return Index % 2 == 0;
}
/// <summary>
/// If this item is at an even index, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsEven(string valueIfTrue)
{
return IsEven(valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is at an even index, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsEven(string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsEven() ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsEven() ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// Determines whether this item is at an odd index.
/// </summary>
/// <returns>
/// <c>true</c> if this item is at an odd index; otherwise, <c>false</c>.
/// </returns>
public bool IsOdd()
{
return Index % 2 == 1;
}
/// <summary>
/// If this item is at an odd index, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsOdd(string valueIfTrue)
{
return IsOdd(valueIfTrue, string.Empty);
}
/// <summary>
/// If this item is at an odd index, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public HtmlString IsOdd(string valueIfTrue, string valueIfFalse)
{
return new HtmlString(IsOdd() ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(IsOdd() ? valueIfTrue : valueIfFalse));
}
}
}
@@ -56,7 +56,7 @@ a.text-success:focus { color: darken(@successText, 10%); }
// Headings
// -------------------------
h1, h2, h3, h4, h5, h6 {
h1, .h1, h2, .h2, h3, .h3, h4, .h4, h5, .h5, h6, .h6 {
margin: (@baseLineHeight / 2) 0;
font-family: @headingsFontFamily;
font-weight: @headingsFontWeight;
@@ -70,21 +70,21 @@ h1, h2, h3, h4, h5, h6 {
}
}
h1,
h2,
h3 { line-height: @baseLineHeight * 2; }
h1, .h1,
h2, .h2,
h3, .h3 { line-height: @baseLineHeight * 2; }
h1 { font-size: @baseFontSize * 2.75; } // ~38px
h2 { font-size: @baseFontSize * 2.25; } // ~32px
h3 { font-size: @baseFontSize * 1.75; } // ~24px
h4 { font-size: @baseFontSize * 1.25; } // ~18px
h5 { font-size: @baseFontSize; }
h6 { font-size: @baseFontSize * 0.85; } // ~12px
h1, .h1 { font-size: @baseFontSize * 2.75; } // ~38px
h2, .h2 { font-size: @baseFontSize * 2.25; } // ~32px
h3, .h3 { font-size: @baseFontSize * 1.75; } // ~24px
h4, .h4 { font-size: @baseFontSize * 1.25; } // ~18px
h5, .h5 { font-size: @baseFontSize; }
h6, .h6 { font-size: @baseFontSize * 0.85; } // ~12px
h1 small { font-size: @baseFontSize * 1.75; } // ~24px
h2 small { font-size: @baseFontSize * 1.25; } // ~18px
h3 small { font-size: @baseFontSize; }
h4 small { font-size: @baseFontSize; }
h1 small, .h1 small { font-size: @baseFontSize * 1.75; } // ~24px
h2 small, .h2 small { font-size: @baseFontSize * 1.25; } // ~18px
h3 small, .h3 small { font-size: @baseFontSize; }
h4 small, .h4 small { font-size: @baseFontSize; }
// Page header
+3 -3
View File
@@ -11,7 +11,7 @@
},
"dependencies": {
"ace-builds": "1.4.2",
"angular": "1.7.9",
"angular": "1.8.0",
"angular-animate": "1.7.5",
"angular-aria": "1.7.9",
"angular-chart.js": "^1.1.1",
@@ -32,13 +32,13 @@
"diff": "3.5.0",
"flatpickr": "4.5.2",
"font-awesome": "4.7.0",
"jquery": "^3.4.1",
"jquery": "^3.5.1",
"jquery-ui-dist": "1.12.1",
"jquery-ui-touch-punch": "0.2.3",
"lazyload-js": "1.0.0",
"moment": "2.22.2",
"ng-file-upload": "12.2.13",
"nouislider": "14.1.1",
"nouislider": "14.4.0",
"npm": "^6.14.0",
"signalr": "2.4.0",
"spectrum-colorpicker": "1.8.0",
@@ -1,5 +1,5 @@
angular.module("umbraco.directives")
.directive('umbContextMenu', function (navigationService, keyboardService) {
.directive('umbContextMenu', function (navigationService, keyboardService, backdropService) {
return {
scope: {
menuDialogTitle: "@",
@@ -30,7 +30,6 @@ angular.module("umbraco.directives")
scope.$on('$destroy', function () {
keyboardService.unbind("esc");
});
}
};
});
@@ -274,6 +274,7 @@
vm.showEmailResetConfirmation = false;
if (vm.requestPasswordResetForm.$invalid) {
vm.errorMsg = 'Email address cannot be empty';
return;
}
@@ -11,7 +11,7 @@ You can easily add you own tours to the Help-drawer or show and start tours from
anywhere in the Umbraco backoffice. To see a real world example of a custom tour implementation, install <a href="https://our.umbraco.com/projects/starter-kits/the-starter-kit/">The Starter Kit</a> in Umbraco 7.8
<h1><b>Extending the help drawer with custom tours</b></h1>
The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file.
The easiet way to add new tours to Umbraco is through the Help-drawer. All it requires is a my-tour.json file.
Place the file in <i>App_Plugins/{MyPackage}/backoffice/tours/{my-tour}.json</i> and it will automatically be
picked up by Umbraco and shown in the Help-drawer.
@@ -99,7 +99,7 @@ Use this directive to render an umbraco button. The directive can be used to gen
alias: "@?",
addEllipsis: "@?",
showCaret: "@?",
autoFocus: "@?",
autoFocus: "<?",
hasPopup: "@?",
isExpanded: "<?"
}
@@ -27,7 +27,7 @@
var title = data[2] + ": ";
if (!scope.isNew) {
scope.a11yMessage += " " + scope.editor.content.name;
title += scope.content.name;
title += scope.editor.content.name;
} else {
var name = editorState.current.contentTypeName;
scope.a11yMessage += " " + name;
@@ -7,11 +7,12 @@
var evts = [];
var allowedNumberOfVisibleEditors = 3;
var aboveBackDropCssClass = 'above-backdrop';
var sectionId = '#leftcolumn';
var isLeftColumnAbove = false;
scope.editors = [];
function addEditor(editor) {
editor.inFront = true;
editor.moveRight = true;
editor.level = 0;
@@ -19,6 +20,14 @@
// push the new editor to the dom
scope.editors.push(editor);
if(scope.editors.length === 1){
isLeftColumnAbove = $(sectionId).hasClass(aboveBackDropCssClass);
if(isLeftColumnAbove){
$(sectionId).removeClass(aboveBackDropCssClass);
}
}
$timeout(() => {
editor.moveRight = false;
@@ -32,14 +41,20 @@
}
function removeEditor(editor) {
editor.moveRight = true;
editor.animating = true;
setTimeout(removeEditorFromDOM.bind(this, editor), 400);
updateEditors(-1);
if(scope.editors.length === 1){
if(isLeftColumnAbove){
$('#leftcolumn').addClass(aboveBackDropCssClass);
}
isLeftColumnAbove = false;
}
}
function revealEditorContent(editor) {
@@ -57,7 +72,7 @@
if (index !== -1) {
scope.editors.splice(index, 1);
}
updateEditors();
scope.$digest();
@@ -33,8 +33,15 @@ function umbPropEditor(umbPropEditorHelper) {
scope.model.alias = Math.random().toString(36).slice(2);
}
scope.propertyEditorView = umbPropEditorHelper.getViewPath(scope.model.view, scope.isPreValue);
var unbindWatcher = scope.$watch("model.view",
function() {
scope.propertyEditorView = umbPropEditorHelper.getViewPath(scope.model.view, scope.isPreValue);
}
);
scope.$on("$destroy", function () {
unbindWatcher();
});
}
};
};
@@ -1,7 +1,7 @@
(function() {
'use strict';
function UmbContextDialog(navigationService, keyboardService, localizationService, overlayService) {
function UmbContextDialog(navigationService, keyboardService, localizationService, overlayService, backdropService) {
function link($scope) {
@@ -23,6 +23,7 @@
});
function hide() {
if ($scope.dialog.confirmDiscardChanges) {
localizationService.localizeMany(["prompt_unsavedChanges", "prompt_unsavedChangesWarning", "prompt_discardChanges", "prompt_stay"]).then(
function (values) {
@@ -3,7 +3,7 @@
* @name umbraco.directives.directive:umbTree
* @restrict E
**/
function umbTreeDirective($q, $rootScope, treeService, notificationsService, userService) {
function umbTreeDirective($q, $rootScope, treeService, notificationsService, userService, backdropService) {
return {
restrict: 'E',
@@ -319,6 +319,18 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
}
}
// Close any potential backdrop and remove the #leftcolumn modifier class
function closeBackdrop() {
var aboveClass = 'above-backdrop';
var leftColumn = $('#leftcolumn');
var isLeftColumnOnTop = leftColumn.hasClass(aboveClass);
if(isLeftColumnOnTop){
backdropService.close();
leftColumn.removeClass(aboveClass);
}
}
/** Returns the css classses assigned to the node (div element) */
$scope.getNodeCssClass = function (node) {
if (!node) {
@@ -368,6 +380,8 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
*/
$scope.select = function (n, ev) {
closeBackdrop()
if (n.metaData && n.metaData.noAccess === true) {
ev.preventDefault();
return;
@@ -18,7 +18,7 @@
</example>
*/
angular.module("umbraco.directives")
.directive('umbTreeItem', function(treeService, $timeout, localizationService, eventsService, appState) {
.directive('umbTreeItem', function(treeService, $timeout, localizationService, eventsService, appState, navigationService) {
return {
restrict: 'E',
replace: true,
@@ -12,16 +12,18 @@
/* ---------- INIT ---------- */
const setDataType = (dataType) => {
scope.dataType = dataType;
listViewPrevalueHelper.setPrevalues(dataType.preValues);
}
const activate = () => {
if (scope.enableListView) {
dataTypeResource.getByName(scope.listViewName)
.then(dataType => {
scope.dataType = dataType;
listViewPrevalueHelper.setPrevalues(dataType.preValues);
setDataType(dataType);
scope.customListViewCreated = checkForCustomListView();
});
@@ -64,7 +66,7 @@
dataTypeResource.createCustomListView(scope.modelAlias).then(dataType => {
// store data type
scope.dataType = dataType;
setDataType(dataType);
// set list view name on scope
scope.listViewName = dataType.name;
@@ -95,7 +97,7 @@
.then(defaultDataType => {
// store data type
scope.dataType = defaultDataType;
setDataType(defaultDataType);
// change state to default list view
scope.customListViewCreated = false;
@@ -118,6 +118,8 @@ For extra details about options and events take a look here: https://refreshless
// create new slider
noUiSlider.create(sliderInstance, options);
mergeTooltips(sliderInstance, 15, ' - ');
if (ctrl.onSetup) {
ctrl.onSetup({
slider: sliderInstance
@@ -200,6 +202,91 @@ For extra details about options and events take a look here: https://refreshless
}
}
// Merging overlapping tooltips: https://refreshless.com/nouislider/examples/#section-merging-tooltips
/**
* @param slider HtmlElement with an initialized slider
* @param threshold Minimum proximity (in percentages) to merge tooltips
* @param separator String joining tooltips
*/
function mergeTooltips(slider, threshold, separator) {
var textIsRtl = getComputedStyle(slider).direction === 'rtl';
var isRtl = slider.noUiSlider.options.direction === 'rtl';
var isVertical = slider.noUiSlider.options.orientation === 'vertical';
var tooltips = slider.noUiSlider.getTooltips();
var origins = slider.noUiSlider.getOrigins();
// Move tooltips into the origin element. The default stylesheet handles this.
tooltips.forEach(function (tooltip, index) {
if (tooltip) {
origins[index].appendChild(tooltip);
}
});
slider.noUiSlider.on('update', function (values, handle, unencoded, tap, positions) {
var pools = [[]];
var poolPositions = [[]];
var poolValues = [[]];
var atPool = 0;
// Assign the first tooltip to the first pool, if the tooltip is configured
if (tooltips[0]) {
pools[0][0] = 0;
poolPositions[0][0] = positions[0];
poolValues[0][0] = values[0];
}
for (var i = 1; i < positions.length; i++) {
if (!tooltips[i] || (positions[i] - positions[i - 1]) > threshold) {
atPool++;
pools[atPool] = [];
poolValues[atPool] = [];
poolPositions[atPool] = [];
}
if (tooltips[i]) {
pools[atPool].push(i);
poolValues[atPool].push(values[i]);
poolPositions[atPool].push(positions[i]);
}
}
pools.forEach(function (pool, poolIndex) {
var handlesInPool = pool.length;
for (var j = 0; j < handlesInPool; j++) {
var handleNumber = pool[j];
if (j === handlesInPool - 1) {
var offset = 0;
poolPositions[poolIndex].forEach(function (value) {
offset += 1000 - 10 * value;
});
var direction = isVertical ? 'bottom' : 'right';
var last = isRtl ? 0 : handlesInPool - 1;
var lastOffset = 1000 - 10 * poolPositions[poolIndex][last];
offset = (textIsRtl && !isVertical ? 100 : 0) + (offset / handlesInPool) - lastOffset;
// Filter to unique values
var tooltipValues = poolValues[poolIndex].filter((v, i, a) => a.indexOf(v) === i);
// Center this tooltip over the affected handles
tooltips[handleNumber].innerHTML = tooltipValues.join(separator);
tooltips[handleNumber].style.display = 'block';
tooltips[handleNumber].style[direction] = offset + '%';
} else {
// Hide this tooltip
tooltips[handleNumber].style.display = 'none';
}
}
});
});
}
}
angular.module('umbraco.directives').component('umbRangeSlider', umbRangeSlider);
@@ -20,7 +20,7 @@ TODO
angular.module("umbraco.directives")
.directive('umbFileDropzone',
function($timeout, Upload, localizationService, umbRequestHelper) {
function ($timeout, Upload, localizationService, umbRequestHelper, overlayService) {
return {
restrict: 'E',
replace: true,
@@ -119,9 +119,8 @@ angular.module("umbraco.directives")
//auto-clear the done queue after 3 secs
var currentLength = scope.done.length;
$timeout(function() {
scope.done.splice(0, currentLength);
},
3000);
scope.done.splice(0, currentLength);
}, 3000);
}
}
@@ -197,30 +196,34 @@ angular.module("umbraco.directives")
}
function _chooseMediaType() {
scope.mediatypepickerOverlay = {
view: "mediatypepicker",
title: "Choose media type",
acceptedMediatypes: scope.acceptedMediatypes,
hideSubmitButton: true,
show: true,
submit: function(model) {
scope.contentTypeAlias = model.selectedType.alias;
scope.mediatypepickerOverlay.show = false;
scope.mediatypepickerOverlay = null;
_processQueueItem();
},
close: function(oldModel) {
scope.queue.map(function(file) {
const dialog = {
view: "itempicker",
filter: scope.acceptedMediatypes.length > 15,
availableItems: scope.acceptedMediatypes,
submit: function (model) {
scope.contentTypeAlias = model.selectedItem.alias;
_processQueueItem();
overlayService.close();
},
close: function () {
scope.queue.map(function (file) {
file.uploadStatus = "error";
file.serverErrorMessage = "Cannot upload this file, no mediatype selected";
scope.rejected.push(file);
});
scope.queue = [];
scope.mediatypepickerOverlay.show = false;
scope.mediatypepickerOverlay = null;
overlayService.close();
}
};
localizationService.localize("defaultdialogs_selectMediaType").then(value => {
dialog.title = value;
overlayService.open(dialog);
});
}
scope.handleFiles = function(files, event) {
@@ -13,7 +13,7 @@
* Section navigation and search, and maintain their state for the entire application lifetime
*
*/
function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState) {
function navigationService($routeParams, $location, $q, $injector, eventsService, umbModelMapper, treeService, appState, backdropService) {
//the promise that will be resolved when the navigation is ready
var navReadyPromise = $q.defer();
@@ -26,7 +26,10 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
navReadyPromise.resolve(mainTreeApi);
});
eventsService.on('appState.backdrop', function(e, args){
var element = $(args.element);
element.addClass('above-backdrop');
});
//A list of query strings defined that when changed will not cause a reload of the route
var nonRoutingQueryStrings = ["mculture", "cculture", "csegment", "lq", "sr"];
@@ -113,6 +116,17 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
}
}
function closeBackdrop() {
var aboveClass = 'above-backdrop';
var leftColumn = $('#leftcolumn');
var isLeftColumnOnTop = leftColumn.hasClass(aboveClass);
if(isLeftColumnOnTop){
backdropService.close();
leftColumn.removeClass(aboveClass);
}
}
var service = {
/**
@@ -410,12 +424,15 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
* @param {Event} event the click event triggering the method, passed from the DOM element
*/
showMenu: function (args) {
var self = this;
var backDropOptions = {
'element': $('#leftcolumn')[0]
};
return treeService.getMenu({ treeNode: args.node })
.then(function (data) {
backdropService.open(backDropOptions);
//check for a default
//NOTE: event will be undefined when a call to hideDialog is made so it won't re-load the default again.
// but perhaps there's a better way to deal with with an additional parameter in the args ? it works though.
@@ -466,6 +483,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
appState.setMenuState("currentNode", null);
appState.setMenuState("menuActions", []);
setMode("tree");
closeBackdrop();
},
/** Executes a given menu action */
@@ -650,6 +668,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
if (showMenu) {
this.showMenu({ skipDefault: true, node: appState.getMenuState("currentNode") });
} else {
closeBackdrop();
setMode("default");
}
},
@@ -685,6 +704,7 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
*/
hideNavigation: function () {
appState.setMenuState("menuActions", []);
closeBackdrop();
setMode("default");
}
};
@@ -133,7 +133,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
else if (rule.selector[0] !== "." && rule.selector.indexOf(".") > -1) {
var split = rule.selector.split(".");
r.block = split[0];
r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(".", " ");
r.classes = rule.selector.substring(rule.selector.indexOf(".") + 1).replace(/\./g, " ");
}
else if (rule.selector[0] != "#" && rule.selector.indexOf("#") > -1) {
var split = rule.selector.split("#");
@@ -80,6 +80,10 @@ body.umb-drawer-is-visible #mainwrapper{
float: left;
position: absolute;
top: 0;
&.above-backdrop{
z-index: 7501;
}
}
#navigation {
@@ -527,7 +527,6 @@ input.umb-group-builder__group-sort-value {
text-align: left;
display: flex;
align-items: center;
max-width: calc(100% - 48px);
min-height: 80px;
color: @ui-action-discreet-type;
@@ -1,7 +1,7 @@
.umb-property-file-upload {
.umb-property-editor--limit-width();
.umb-upload-button-big {
max-width: (@propertyEditorLimitedWidth - 40);
display: block;
padding: 20px;
opacity: 1;
+26 -4
View File
@@ -26,19 +26,29 @@
color: @gray-7;
}
h5{
h5, .h5{
color: @gray-1;
font-weight: bold;
font-size: 15px;
margin-top: 15px;
}
h5.-border-bottom {
h1.-border-bottom,
h2.-border-bottom,
h3.-border-bottom,
h4.-border-bottom,
h5.-border-bottom,
h6.-border-bottom {
border-bottom: 1px solid @gray-9;
padding-bottom: 5px;
}
h5.-black {
h1.-black,
h2.-black,
h3.-black,
h4.-black,
h5.-black,
h6.-black {
color: @black;
}
@@ -200,10 +210,16 @@ h5.-black {
clear: both;
}
}
.umb-editor--infiniteMode .umb-control-group .control-header {
padding-bottom: 5px;
}
.form-horizontal .umb-control-group .control-header {
float: left;
width: 160px;
padding-top: 5px;
padding-bottom: 0;
text-align: left;
.control-label {
@@ -347,10 +363,16 @@ label:not([for]) {
border: none !important;
}
table thead a {
table thead a,
table thead button {
color: @gray-2;
}
table thead button:hover,
table thead button:focus{
text-decoration: underline;
}
/* UI interactions */
.umb-table tbody.ui-sortable tr
@@ -448,9 +448,11 @@
}
// in these cases the gradient won't cover the background, so we override
&:focus,
&:hover {
color: @textColorHover;
background-color: @hoverColor;
text-decoration: none;
}
&.disabled, &[disabled] {
@@ -729,6 +729,7 @@
// --------------------------------------------------
.umb-fileupload {
display: flex;
flex-direction: column;
}
.umb-fileupload .preview {
@@ -18,29 +18,28 @@
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<div ng-if="!vm.loading">
<a href="" tabindex="0" umb-auto-focus></a>
<ul class="umb-card-grid -three-in-row">
<li ng-repeat="dataTypeConfig in vm.configs | orderBy:'name'"
<li ng-repeat="dataTypeConfig in vm.configs | orderBy:'name' track by $index"
data-element="datatypeconfig-{{dataTypeConfig.name}}">
<div ng-if="dataTypeConfig.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" ng-click="vm.pickDataType(dataTypeConfig)" title="{{ dataTypeConfig.name }}">
<button umb-auto-focus="{{$index === 0}}" class="btn-reset umb-card-grid-item" type="button" ng-click="vm.pickDataType(dataTypeConfig)" title="Select {{ dataTypeConfig.name }}">
<span>
<i class="{{ dataTypeConfig.icon }}" ng-class="{'icon-autofill': dataTypeConfig.icon == null}"></i>
{{ dataTypeConfig.name }}
<i class="{{ dataTypeConfig.icon }}" ng-class="{'icon-autofill': dataTypeConfig.icon == null}" aria-hidden="true"></i>
<span class="sr-only">Select</span> {{ dataTypeConfig.name }}
</span>
</a>
</button>
</li>
</ul>
<ul class="umb-card-grid -three-in-row">
<li>
<a class="umb-card-grid-item-slot" href="" ng-click="vm.newDataType()" title="Create a new configuration of {{ model.editor.name }}">
<button umb-auto-focus="{{vm.configs.length === 0}}" class="btn-reset umb-card-grid-item-slot" type="button" ng-click="vm.newDataType()" title="Create a new configuration of {{ model.editor.name }}">
<span>
<i class="icon icon-add"></i>
Create new
<i class="icon icon-add" aria-hidden="true"></i>
Create new <span class="sr-only">configuration of {{ model.editor.name }}</span>
</span>
</a>
</button>
</li>
</ul>
@@ -34,16 +34,19 @@
<div ng-if="vm.loading === false && vm.showDataTypes === true">
<div ng-repeat="(key,value) in vm.dataTypes">
<h5>{{key | umbCmsTitleCase}}</h5>
<h2 class="h5">{{key | umbCmsTitleCase}}</h2>
<ul class="umb-card-grid -six-in-row">
<li ng-repeat="dataType in value | orderBy:'name'"
data-element="datatype-{{dataType.name}}">
<a class="umb-card-grid-item" href="" ng-click="vm.viewOptionsForEditor(dataType)" title="{{ dataType.name }}">
<li ng-repeat="dataType in value | orderBy:'name'" data-element="datatype-{{dataType.name}}">
<button
type="button"
class="btn-reset umb-card-grid-item"
ng-click="vm.viewOptionsForEditor(dataType)"
title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}" aria-hidden="true"></i>
{{ dataType.name }}
</span>
</a>
</button>
</li>
</ul>
</div>
@@ -51,38 +54,50 @@
<!-- SEARCH RESULTS -->
<div ng-if="vm.loading === false && vm.showDataTypes === false && vm.searchResult !== null">
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_searchResultSettings"></localize></h5>
<h2 class="h5 -border-bottom -black">
<localize key="contentTypeEditor_searchResultSettings">Available configurations</localize>
</h2>
<div ng-repeat="result in vm.searchResult.configs">
<div ng-if="result.entries.length > 0">
<h5>{{result.group | umbCmsTitleCase}}</h5>
<h3 class="h5">{{result.group | umbCmsTitleCase}}</h3>
<ul class="umb-card-grid -six-in-row">
<li ng-repeat="dataType in result.entries | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(dataType)">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" ng-click="vm.pickDataType(dataType)" title="{{ dataType.name }}">
<button
type="button"
class="btn-reset umb-card-grid-item"
ng-click="vm.pickDataType(dataType)"
title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}" aria-hidden="true"></i>
{{ dataType.name }}
</span>
</a>
</button>
</li>
</ul>
</div>
</div>
<h5 class="-border-bottom -black"><localize key="contentTypeEditor_searchResultEditors"></localize></h5>
<h2 class="h5 -border-bottom -black">
<localize key="contentTypeEditor_searchResultEditors">Create a new configuration</localize>
</h2>
<div ng-repeat="result in vm.searchResult.dataTypes">
<div ng-if="result.entries.length > 0">
<h5>{{result.group | umbCmsTitleCase}}</h5>
<h3 class="h5">{{result.group | umbCmsTitleCase}}</h3>
<ul class="umb-card-grid -six-in-row">
<li ng-repeat="dataType in result.entries | orderBy:'name'">
<a class="umb-card-grid-item" href="" ng-click="vm.pickEditor(dataType)" title="{{ dataType.name }}">
<button
type="button"
class="btn-reset umb-card-grid-item"
ng-click="vm.pickEditor(dataType)"
title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}" aria-hidden="true"></i>
{{ dataType.name }}
</span>
</a>
</button>
</li>
</ul>
</div>
@@ -22,6 +22,7 @@ function MacroPickerController($scope, entityResource, macroResource, umbPropEdi
if ($scope.wizardStep === "macroSelect") {
editParams(true);
} else {
$scope.$broadcast("formSubmitting", { scope: $scope });
$scope.model.submit($scope.model);
}
};
@@ -20,15 +20,14 @@
<div class="umb-mediapicker-upload">
<div class="form-search">
<i class="icon-search" aria-hidden="true"></i>
<input class="umb-search-field search-query -full-width-input"
<input type="text"
class="umb-search-field search-query -full-width-input"
ng-model="vm.searchOptions.filter"
localize="placeholder"
placeholder="@placeholders_search"
ng-change="vm.changeSearch()"
type="text"
umb-auto-focus
no-dirty-check
/>
no-dirty-check />
<div class="form-search__toggle">
<umb-checkbox
@@ -56,17 +55,15 @@
<div class="row umb-control-group" ng-show="!vm.searchOptions.filter">
<ul class="umb-breadcrumbs">
<li ng-hide="startNodeId != -1" class="umb-breadcrumbs__ancestor">
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-click="vm.gotoFolder()" ng-class="{'--current':path.length === 0}">
<localize key="treeHeaders_media">Media</localize>
</button>
<span class="umb-breadcrumbs__separator" aria-hidden="true">&#47;</span>
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-click="vm.gotoFolder()" ng-class="{'--current':path.length === 0}">
<localize key="treeHeaders_media">Media</localize>
</button>
<span class="umb-breadcrumbs__separator" aria-hidden="true">&#47;</span>
</li>
<li ng-repeat="item in path" class="umb-breadcrumbs__ancestor">
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-click="vm.gotoFolder(item)" ng-class="{'--current':$last}">{{item.name}}</button>
<span class="umb-breadcrumbs__separator" aria-hidden="true">&#47;</span>
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-click="vm.gotoFolder(item)" ng-class="{'--current':$last}">{{item.name}}</button>
<span class="umb-breadcrumbs__separator" aria-hidden="true">&#47;</span>
</li>
<li class="umb-breadcrumbs__ancestor" ng-show="!lockedFolder">
<button type="button" class="umb-breadcrumbs__action umb-outline umb-outline--surronding" ng-hide="model.showFolderInput" ng-click="model.showFolderInput = true">
<i class="icon icon-add small" aria-hidden="true"></i>
@@ -81,11 +78,10 @@
ng-model="model.newFolderName"
ng-keydown="enterSubmitFolder($event)"
ng-blur="vm.submitFolder()"
focus-when="{{model.showFolderInput}}"
/>
focus-when="{{model.showFolderInput}}" />
</li>
</ul>
<div class="umb-loader" ng-if="model.creatingFolder"></div>
<umb-loader ng-if="model.creatingFolder"></umb-loader>
</div>
<umb-file-dropzone
@@ -54,34 +54,32 @@
<div class="editor-wrapper umb-control-group control-group" ng-model="model.property.editor" val-require-component ng-if="!model.property.locked">
<a data-element="editor-add" href="" ng-if="!model.property.editor" class="editor-placeholder" hotkey="alt+shift+e" ng-click="vm.openDataTypePicker(model.property)">
<button type="button" class="btn-reset editor-placeholder" data-element="editor-add" ng-if="!model.property.editor" hotkey="alt+shift+e" ng-click="vm.openDataTypePicker(model.property)">
<localize key="defaultdialogs_selectEditor"></localize>
<div ng-messages="model.property.editor" show-validation-on-submit>
<span class="umb-validation-label" ng-message="required">Required editor</span>
</div>
</a>
</button>
<div class="editor clearfix" ng-if="model.property.editor">
<button class="btn-reset editor-info" ng-click="vm.openDataTypeSettings(model.property)">
<button type="button" class="btn-reset editor-info" ng-click="vm.openDataTypeSettings(model.property)">
<div class="editor-icon-wrapper">
<i class="icon {{ model.property.dataTypeIcon }}" ng-class="{'icon-autofill': model.property.dataTypeIcon == null}"></i>
<i class="icon {{ model.property.dataTypeIcon }}" ng-class="{'icon-autofill': model.property.dataTypeIcon == null}" aria-hidden="true"></i>
</div>
<div class="editor-details">
<span class="editor-name">{{ model.property.dataTypeName }}</span>
<span class="editor-editor">{{ model.property.editor }}</span>
</div>
</button>
<button class="editor-remove-icon btn-reset pull-right"
<button type="button" class="editor-remove-icon btn-reset pull-right"
ng-click="vm.openDataTypePicker(model.property)"
hotkey="alt+shift+d"
localize="title"
title="@actions_changeDataType"
>
<i class="icon icon-wrong"></i>
title="@actions_changeDataType">
<i class="icon icon-wrong" aria-hidden="true"></i>
</button>
</div>
@@ -108,8 +106,7 @@
placeholder="@validation_mandatoryMessage"
ng-model="model.property.validation.mandatoryMessage"
ng-if="model.property.validation.mandatory"
ng-keypress="vm.submitOnEnter($event)">
</input>
ng-keypress="vm.submitOnEnter($event)" />
<label class="mt3">
<localize key="validation_customValidation"></localize>
@@ -144,8 +141,7 @@
<h5><localize key="contentTypeEditor_cultureVariantHeading" /></h5>
<umb-toggle data-element="permissions-allow-culture-variant"
checked="model.property.allowCultureVariant"
on-click="vm.toggleAllowCultureVariants()"
>
on-click="vm.toggleAllowCultureVariants()">
</umb-toggle>
</div>
@@ -155,8 +151,7 @@
<umb-toggle data-element="permissions-allow-segment-variant"
checked="model.property.allowSegmentVariant"
on-click="vm.toggleAllowSegmentVariants()"
>
on-click="vm.toggleAllowSegmentVariants()">
</umb-toggle>
</div>
@@ -195,7 +190,6 @@
on-click="vm.toggleIsSensitiveData()">
</umb-toggle>
</div>
</div>
@@ -68,14 +68,20 @@
if(version && version.versionId) {
vm.loading = true;
const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null;
contentResource.getRollbackVersion(version.versionId, culture)
.then(function(data){
.then(function(data) {
vm.previousVersion = data;
vm.previousVersion.versionId = version.versionId;
createDiff(vm.currentVersion, vm.previousVersion);
vm.loading = false;
vm.rollbackButtonDisabled = false;
}, function () {
vm.loading = false;
});
} else {
@@ -118,6 +124,10 @@
tab.properties.forEach((property, propertyIndex) => {
var oldProperty = previousVersion.tabs[tabIndex].properties[propertyIndex];
// copy existing properties, so it doesn't manipulate existing properties on page
oldProperty = Utilities.copy(oldProperty);
property = Utilities.copy(property);
// we have to make properties storing values as object into strings (Grid, nested content, etc.)
if(property.value instanceof Object) {
property.value = JSON.stringify(property.value, null, 1);
@@ -31,8 +31,8 @@
<div>
<h5><localize key="rollback_currentVersion"></localize></h5>
<p>{{vm.currentVersion.name}} (Created: {{vm.currentVersion.createDate}})</p>
<h5><localize key="rollback_currentVersion">Current version</localize></h5>
<p>{{vm.currentVersion.name}} (<localize key="rollback_created">Created</localize>: {{vm.currentVersion.createDate}})</p>
<h5><localize key="rollback_rollbackTo"></localize></h5>
<select
@@ -44,9 +44,9 @@
</select>
</div>
<div ng-if="vm.diff" class="diff" style="border-top: 1px solid #e9e9eb; margin-top: 30px;">
<div ng-if="vm.diff && !vm.loading" class="diff" style="border-top: 1px solid #e9e9eb; margin-top: 30px;">
<h5>Changes</h5>
<h5><localize key="rollback_changes">Changes</localize></h5>
<small style="margin-bottom: 15px; display: block;"><localize key="rollback_diffHelp"></localize></small>
<table class="table table-condensed table-bordered">
@@ -54,7 +54,7 @@
<tr>
<td class="bold">Name</td>
<td>
<span ng-repeat="part in vm.diff.name">
<span ng-repeat="part in vm.diff.name track by $id(part)">
<ins ng-if="part.added">{{part.value}}</ins>
<del ng-if="part.removed">{{part.value}}</del>
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
@@ -64,7 +64,7 @@
<tr ng-repeat="property in vm.diff.properties track by property.alias">
<td class="bold">{{property.label}}</td>
<td ng-class="{'pre-line': property.isObject, 'word-wrap': !property.isObject}">
<span ng-repeat="part in property.diff">
<span ng-repeat="part in property.diff track by $id(part)">
<ins ng-if="part.added">{{part.value}}</ins>
<del ng-if="part.removed">{{part.value}}</del>
<span ng-if="!part.added && !part.removed">{{part.value}}</span>
@@ -19,6 +19,31 @@ function ItemPickerOverlay($scope, localizationService) {
$scope.submitForm($scope.model);
};
$scope.tooltip = {
show: false,
event: null
};
$scope.showTooltip = function(item, $event) {
if (!item.tooltip) {
$scope.mouseLeave();
return;
}
$scope.tooltip = {
show: true,
event: $event,
text: item.tooltip
};
}
$scope.hideTooltip = function () {
$scope.tooltip = {
show: false,
event: null,
text: null
};
}
onInit();
}
@@ -38,7 +38,7 @@
<button class="umb-card-grid-item btn-reset" ng-click="selectItem(availableItem)">
<span>
<i class="{{ availableItem.icon }}"></i>
{{ availableItem.name }}
<span ng-mouseover="showTooltip(availableItem, $event)" ng-mouseleave="hideTooltip()">{{ availableItem.name }}</span>
</span>
</button>
</li>
@@ -51,4 +51,8 @@
</button>
</li>
</ul>
<umb-tooltip ng-if="tooltip.show" event="tooltip.event">
{{ tooltip.text }}
</umb-tooltip>
</div>
@@ -5,13 +5,9 @@
<div class='umb-modalcolumn-body'>
<ul class="umb-actions">
<li data-element="action-{{action.alias}}" ng-click="executeMenuItem(action)" class="umb-action" ng-class="{sep:action.separator, '-opens-dialog': action.opensDialog}" ng-repeat="action in menuActions">
<button class="umb-action-link btn-reset umb-outline" prevent-default umb-auto-focus ng-if="$index === 0">
<i class="icon icon-{{action.cssclass}}"></i>
<span class="menu-label">{{action.name}}</span>
</button>
<button class="umb-action-link btn-reset umb-outline" prevent-default ng-if="$index !== 0">
<i class="icon icon-{{action.cssclass}}"></i>
<li data-element="action-{{action.alias}}" class="umb-action" ng-class="{sep:action.separator, '-opens-dialog': action.opensDialog}" ng-repeat="action in menuActions">
<button type="button" umb-auto-focus="{{$index === 0}}" ng-click="executeMenuItem(action)" class="umb-action-link btn-reset umb-outline" prevent-default>
<i class="icon icon-{{action.cssclass}}" aria-hidden="true"></i>
<span class="menu-label">{{action.name}}</span>
</button>
</li>
@@ -23,7 +23,7 @@
<small style="font-size: 13px;">{{vm.invitedUserPasswordModel.passwordPolicyText}}</small>
</label>
<input type="password" ng-model="vm.invitedUserPasswordModel.password" name="password" id="umb-passwordOne" class="-full-width-input" umb-auto-focus required val-server-field="value" ng-minlength="{{vm.invitedUserPasswordModel.passwordPolicies.minPasswordLength}}" />
<span ng-messages="inviteUserPasswordForm.password.$error" show-validation-on-submit >
<span ng-messages="inviteUserPasswordForm.password.$error" show-validation-on-submit>
<span class="help-inline" ng-message="required"><localize key="user_passwordIsBlank">Your new password cannot be blank!</localize></span>
<span class="help-inline" ng-message="minlength">Minimum {{vm.invitedUserPasswordModel.passwordPolicies.minPasswordLength}} characters</span>
<span class="help-inline" ng-message="valServerField">{{inviteUserPasswordForm.password.errorMsg}}</span>
@@ -33,7 +33,7 @@
<div class="control-group" ng-class="{error: vm.setPasswordForm.confirmPassword.$invalid}">
<label for="umb-confirmPasswordOne"><localize key="user_confirmNewPassword">Confirm new password</localize></label>
<input type="password" ng-model="vm.invitedUserPasswordModel.confirmPassword" name="confirmPassword" id="umb-confirmPasswordOne" class="-full-width-input" required val-compare="password" />
<span ng-messages="inviteUserPasswordForm.confirmPassword.$error" show-validation-on-submit >
<span ng-messages="inviteUserPasswordForm.confirmPassword.$error" show-validation-on-submit>
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" ng-message="valCompare"><localize key="user_passwordMismatch">The confirmed password doesn't match the new password!</localize></span>
</span>
@@ -41,11 +41,10 @@
</div>
<div class="flex justify-between items-center">
<umb-button
type="submit"
button-style="success"
state="vm.invitedUserPasswordModel.buttonState"
label="Save password">
<umb-button type="submit"
button-style="success"
state="vm.invitedUserPasswordModel.buttonState"
label="Save password">
</umb-button>
</div>
@@ -58,11 +57,10 @@
<ng-form name="vm.avatarForm">
<umb-progress-bar
style="max-width: 100px; margin-bottom: 5px;"
ng-show="vm.avatarFile.uploadStatus === 'uploading'"
progress="{{ vm.avatarFile.uploadProgress }}"
size="s">
<umb-progress-bar style="max-width: 100px; margin-bottom: 5px;"
ng-show="vm.avatarFile.uploadStatus === 'uploading'"
progress="{{ vm.avatarFile.uploadProgress }}"
size="s">
</umb-progress-bar>
<div class="umb-info-local-item text-error mt3" ng-if="vm.avatarFile.uploadStatus === 'error'">
@@ -70,19 +68,18 @@
</div>
<a class="umb-avatar-btn"
ngf-select
ng-model="vm.avatarFile.filesHolder"
ngf-change="vm.changeAvatar($files, $event)"
ngf-multiple="false"
ngf-pattern="{{vm.avatarFile.acceptedFileTypes}}"
ngf-max-size="{{ vm.avatarFile.maxFileSize }}">
ngf-select
ng-model="vm.avatarFile.filesHolder"
ngf-change="vm.changeAvatar($files, $event)"
ngf-multiple="false"
ngf-pattern="{{vm.avatarFile.acceptedFileTypes}}"
ngf-max-size="{{ vm.avatarFile.maxFileSize }}">
<umb-avatar
color="gray"
size="xl"
unknown-char="+"
img-src="{{vm.invitedUser.avatars[3]}}"
img-srcset="{{vm.invitedUser.avatars[4]}} 2x, {{invitedUser.avatars[4]}} 3x">
<umb-avatar color="gray"
size="xl"
unknown-char="+"
img-src="{{vm.invitedUser.avatars[3]}}"
img-srcset="{{vm.invitedUser.avatars[4]}} 2x, {{invitedUser.avatars[4]}} 3x">
</umb-avatar>
</a>
@@ -94,11 +91,10 @@
<localize key="user_userinviteAvatarMessage"></localize>
</p>
<div class="flex justify-center items-center">
<umb-button
type="button"
button-style="success"
label="Done"
action="vm.getStarted()">
<umb-button type="button"
button-style="success"
label="Done"
action="vm.getStarted()">
</umb-button>
</div>
</div>
@@ -153,7 +149,7 @@
</div>
<div class="control-group" ng-class="{error: vm.loginForm.username.$invalid}">
<label for="umb-username">{{vm.labels.usernameLabel}}</label>
<input type="text" ng-model="vm.login" name="username" id="umb-username" class="-full-width-input" placeholder="{{vm.labels.usernamePlaceholder}}" focus-when="{{vm.view === 'login'}}" aria-required="true"/>
<input type="text" ng-model="vm.login" name="username" id="umb-username" class="-full-width-input" placeholder="{{vm.labels.usernamePlaceholder}}" focus-when="{{vm.view === 'login'}}" aria-required="true" />
</div>
<div class="control-group" ng-class="{error: vm.loginForm.password.$invalid}">
@@ -188,21 +184,21 @@
</p>
<form method="POST" name="vm.requestPasswordResetForm" ng-submit="vm.requestPasswordResetSubmit(email)">
<div class="control-group" ng-class="{error: requestPasswordResetForm.email.$invalid}">
<div class="control-group" ng-class="{error: vm.requestPasswordResetForm.email.$invalid}">
<label for="umb-email"><localize key="general_email">Email</localize></label>
<input type="email" val-email ng-model="email" name="email" id="umb-email" class="-full-width-input" localize="placeholder" placeholder="@placeholders_email" focus-when="{{vm.view === 'request-password-reset'}}" />
</div>
<div class="control-group" ng-show="requestPasswordResetForm.$invalid">
<div class="text-error">{{errorMsg}}</div>
<div ng-messages="vm.requestPasswordResetForm.$error" class="control-group" ng-show="vm.requestPasswordResetForm.$invalid">
<p ng-message="auth" class="text-error" role="alert" tabindex="0">{{vm.errorMsg}}</p>
</div>
<div class="control-group" ng-show="vm.showEmailResetConfirmation">
<div class="text-info" role="alert">
<p tabindex="0">
<localize key="login_requestPasswordResetConfirmation">An email with password reset instructions will be sent to the specified address if it matched our records</localize>
</p>
</div>
<localize key="login_requestPasswordResetConfirmation">An email with password reset instructions will be sent to the specified address if it matched our records</localize>
</p>
</div>
</div>
<div class="flex justify-between items-center">
@@ -231,16 +227,16 @@
<input type="password" ng-model="vm.confirmPassword" name="confirmPassword" id="umb-confirmPasswordThree" class="-full-width-input" localize="placeholder" placeholder="@placeholders_confirmPassword" />
</div>
<div ng-hide="vm.resetComplete" class="control-group" ng-show="vm.setPasswordForm.$invalid">
<div class="text-error">{{vm.errorMsg}}</div>
<div ng-messages="vm.setPasswordForm.$error" class="control-group" ng-show="vm.setPasswordForm.$invalid">
<p ng-message="auth" class="text-error" role="alert" tabindex="0">{{vm.errorMsg}}</p>
</div>
<div class="control-group" ng-show="vm.showSetPasswordConfirmation">
<div class="text-info">
<p tabindex="0">
<localize key="login_setPasswordConfirmation">Your new password has been set and you may now use it to log in.</localize>
</p>
</div>
</div>
<div class="flex justify-between items-center">
<button ng-hide="vm.resetComplete" type="submit" class="btn btn-success" val-trigger-change="#login .form input"><localize key="general_submit">Submit</localize></button>
<a class="muted" href="#" prevent-default ng-click="vm.showLogin()"><localize key="login_returnToLogin">Return to login form</localize></a>
@@ -251,7 +247,7 @@
<div ng-show="vm.view == 'password-reset-code-expired'">
<div class="text-error" ng-repeat="error in vm.resetPasswordCodeInfo.errors">
<span>{{error}}</span>
<p class="text-error">{{error}}</p>
</div>
<div class="switch-view">
@@ -5,18 +5,16 @@
<umb-box ng-if="currentUrls" data-element="node-info-urls">
<umb-box-header title-key="general_links"></umb-box-header>
<umb-box-content class="block-form">
<ul class="nav nav-stacked" style="margin-bottom: 0;">
<ul class="nav nav-stacked mb0">
<li ng-repeat="url in currentUrls">
<a href="{{url.text}}" target="_blank" ng-if="url.isUrl">
<span ng-if="currentUrlsHaveMultipleCultures && url.culture" style="font-size: 13px; color: #cccccc; width: 50px;display: inline-block">{{url.culture}}</span>
<i class="icon icon-out"></i>
<i class="icon icon-out" aria-hidden="true"></i>
<span>{{url.text}}</span>
</a>
<div ng-if="!url.isUrl" style="margin-top: 4px;">
<span ng-if="currentUrlsHaveMultipleCultures && url.culture" style="font-size: 13px; color: #cccccc; width: 50px;display: inline-block">{{url.culture}}</span>
<em>{{url.text}}</em>
</div>
</li>
</ul>
@@ -32,7 +30,10 @@
<p><localize key="redirectUrls_panelInformation" class="ng-isolate-scope ng-scope">The following URLs redirect to this content item:</localize></p>
<ul class="nav nav-stacked" style="margin-bottom: 0;">
<li ng-repeat="redirectUrl in redirectUrls">
<a href="{{redirectUrl.originalUrl}}" target="_blank"><i ng-class="value.icon" class="icon-out"></i> {{redirectUrl.originalUrl}}</a>
<a href="{{redirectUrl.originalUrl}}" target="_blank">
<i ng-class="value.icon" class="icon-out" aria-hidden="true"></i>
{{redirectUrl.originalUrl}}
</a>
</li>
</ul>
</div>
@@ -42,9 +43,8 @@
<umb-box data-element="node-info-history">
<umb-box-header
title="{{historyLabel}}">
<umb-box-header title="{{historyLabel}}">
<umb-button
ng-hide="node.trashed"
type="button"
@@ -104,8 +104,7 @@
<span>
<localize key="auditTrails_{{ item.logType | lowercase }}" tokens="[item.parameters]">{{ item.comment }}</localize>
</span>
</div>
</div>
@@ -12,16 +12,16 @@
<div class="flex items-center" style="flex: 1;">
<ng-form data-element="editor-icon" name="iconForm">
<button
type="button" class="umb-panel-header-icon"
ng-if="!hideIcon"
ng-click="openIconPicker()"
<button
type="button" class="umb-panel-header-icon"
ng-if="!hideIcon"
ng-click="openIconPicker()"
ng-class="{'-placeholder': $parent.icon==='' || $parent.icon===null}"
title="{{$parent.icon}}">
<i class="icon {{$parent.icon}}" ng-if="$parent.icon!=='' && $parent.icon!==null" aria-hidden="true"></i>
<div class="umb-panel-header-icon-text" ng-if="$parent.icon==='' || $parent.icon===null">
<span class="umb-panel-header-icon-text" ng-if="$parent.icon==='' || $parent.icon===null">
<localize key="settings_addIcon">Add icon</localize>
</div>
</span>
</button>
</ng-form>
@@ -67,7 +67,7 @@
</div>
<div class="umb-panel-header-name" ng-if="nameLocked" title="{{key}}">{{ name }}</div>
<h1 class="umb-panel-header-name" id="editor-headline-{{$id}}" ng-if="nameLocked" title="{{key}}">{{ name }}</h1>
<input data-element="editor-description"
no-password-manager
@@ -78,7 +78,7 @@
ng-if="!hideDescription && !descriptionLocked"
ng-model="$parent.description"/>
<div class="umb-panel-header-locked-description" ng-if="descriptionLocked">{{ description }}</div>
<p class="umb-panel-header-locked-description" id="editor-description-{{$id}}" ng-if="descriptionLocked">{{ description }}</p>
</div>
@@ -240,7 +240,7 @@
</div>
<ng-form inert class="umb-group-builder__property-preview-form" name="propertyEditorPreviewForm" umb-disable-form-validation ng-click="editPropertyTypeSettings(property, tab)">
<ng-form inert class="umb-group-builder__property-preview-form" name="propertyEditorPreviewForm" umb-disable-form-validation ng-click="editPropertyTypeSettings(property, tab)" tabindex="-1">
<umb-property-editor
ng-if="property.view !== undefined"
model="property"
@@ -104,11 +104,4 @@
</ng-form>
<umb-overlay
ng-if="mediatypepickerOverlay.show"
model="mediatypepickerOverlay"
view="mediatypepickerOverlay.view"
position="right">
</umb-overlay>
</div>
@@ -92,6 +92,8 @@ function contentCreateController($scope,
} else {
createBlank(docType);
}
navigationService.hideDialog();
}
function createFromBlueprint(blueprintId) {
@@ -1,7 +1,7 @@
(function () {
"use strict";
function ContentSortController($scope, $filter, $routeParams, contentResource, navigationService) {
function ContentSortController($scope, $filter, $routeParams, contentResource, navigationService, eventsService) {
var vm = this;
var id = $scope.currentNode.id;
@@ -50,6 +50,7 @@
navigationService.syncTree({ tree: "content", path: $scope.currentNode.path, forceReload: true })
.then(() => navigationService.reloadNode($scope.currentNode));
eventsService.emit("sortCompleted", { id: id });
vm.saveButtonState = "success";
}, function(error) {
vm.error = error;
@@ -1,37 +1,35 @@
<div ng-controller="Umbraco.Editors.Content.SortController as vm" ng-cloak>
<div class="umb-dialog-body with-footer sort-order">
<div class="umb-pane">
<div ng-if="vm.error">
<div class="alert alert-error">
<div>{{vm.error.errorMsg}}</div>
</div>
<div ng-if="vm.error" class="alert alert-error">
<p>{{vm.error.errorMsg}}</p>
</div>
<umb-load-indicator
ng-show="vm.loading">
</umb-load-indicator>
<div ng-if="!vm.loading">
<p class="abstract"><localize key="sort_sortHelp"></localize></p>
<p class="abstract"><localize key="sort_sortHelp">Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items</localize></p>
<table class="table table-condensed table-sortable" ng-if="vm.children">
<thead>
<tr>
<th colspan="2">
<a ng-href="#" ng-click="vm.sort('name')" prevent-default>
<localize key="general_name"></localize>
<span ng-if="vm.sortOrder.column === 'name'">
<button type="button" class="btn-reset" ng-click="vm.sort('name')">
<b><localize key="general_name">Name</localize></b>
<span ng-if="vm.sortOrder.column === 'name'" aria-hidden="true">
<i ng-if="vm.sortOrder.reverse" class="icon-navigation-up"></i>
<i ng-if="!vm.sortOrder.reverse" class="icon-navigation-down"></i>
</span>
</a>
</button>
</th>
<th>
<a ng-href="#" ng-click="vm.sort('createDate')" prevent-default>
<localize key="sort_sortCreationDate"></localize>
<span ng-if="vm.sortOrder.column === 'createDate'">
<button type="button" class="btn-reset" ng-click="vm.sort('createDate')">
<b><localize key="sort_sortCreationDate">Creation date</localize></b>
<span ng-if="vm.sortOrder.column === 'createDate'" aria-hidden="true">
<i ng-if="vm.sortOrder.reverse" class="icon-navigation-up"></i>
<i ng-if="!vm.sortOrder.reverse" class="icon-navigation-down"></i>
</span>
@@ -51,14 +49,14 @@
<umb-empty-state
ng-if="!vm.children"
position="center">
<localize key="sort_sortEmptyState"></localize>
<localize key="sort_sortEmptyState">This node has no child nodes to sort</localize>
</umb-empty-state>
</div>
</div>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
<umb-button
type="button"
@@ -68,12 +66,12 @@
</umb-button>
<umb-button
ng-if="vm.children"
type="button"
type="button"
action="vm.save()"
state="vm.saveButtonState"
button-style="success"
label-key="buttons_save">
</umb-button>
</div>
</div>
@@ -38,7 +38,7 @@
vm.page.loading = false;
vm.page.saveButtonState = "init";
vm.page.navigation = [];
var labelKeys = [
"general_design",
"general_listView",
@@ -92,33 +92,6 @@
vm.labels.addTemplate = values[13];
vm.labels.allowCultureVariants = values[14];
var buttons = [
{
"name": vm.labels.design,
"alias": "design",
"icon": "icon-document-dashed-line",
"view": "views/documenttypes/views/design/design.html"
},
{
"name": vm.labels.listview,
"alias": "listView",
"icon": "icon-list",
"view": "views/documenttypes/views/listview/listview.html"
},
{
"name": vm.labels.permissions,
"alias": "permissions",
"icon": "icon-keychain",
"view": "views/documenttypes/views/permissions/permissions.html"
},
{
"name": vm.labels.templates,
"alias": "templates",
"icon": "icon-layout",
"view": "views/documenttypes/views/templates/templates.html"
}
];
vm.page.keyboardShortcutsOverview = [
{
"name": vm.labels.sections,
@@ -187,9 +160,6 @@
]
}
];
loadButtons(buttons);
});
contentTypeHelper.checkModelsBuilderStatus().then(function (result) {
@@ -279,25 +249,25 @@
contentTypeResource.getById(documentTypeId).then(function (dt) {
init(dt);
// we don't need to sync the tree in infinite mode
if(!infiniteMode) {
if (!infiniteMode) {
syncTreeNode(vm.contentType, dt.path, true);
}
vm.page.loading = false;
});
}
function loadButtons(buttons) {
function loadButtons() {
vm.page.navigation = vm.contentType.apps;
angular.forEach(buttons,
function (val, index) {
if (disableTemplates === true) {
Utilities.forEach(vm.contentType.apps,
(app, index) => {
if (app.alias === "templates") {
vm.page.navigation.splice(index, 1);
}
});
}
if (disableTemplates === true && val.alias === "templates") {
buttons.splice(index, 1);
}
});
vm.page.navigation = buttons;
initializeActiveNavigationPanel();
}
@@ -307,16 +277,14 @@
var initialViewSetFromRouteParams = false;
var view = $routeParams.view;
if (view) {
var viewPath = "views/documenttypes/views/" + view + "/" + view + ".html";
for (var i = 0; i < vm.page.navigation.length; i++) {
if (vm.page.navigation[i].view === viewPath) {
if (vm.page.navigation[i].alias.localeCompare(view, undefined, { sensitivity: 'accent' }) === 0) {
vm.page.navigation[i].active = true;
initialViewSetFromRouteParams = true;
break;
}
}
}
if (initialViewSetFromRouteParams === false) {
vm.page.navigation[0].active = true;
}
@@ -379,18 +347,17 @@
}).then(function (data) {
//success
// we don't need to sync the tree in infinite mode
if(!infiniteMode) {
if (!infiniteMode) {
syncTreeNode(vm.contentType, data.path);
}
// emit event
var args = { documentType: vm.contentType };
eventsService.emit("editors.documentType.saved", args);
vm.page.saveButtonState = "success";
if(infiniteMode && $scope.model.submit) {
$scope.model.documentTypeKey = vm.contentType.key;
if (infiniteMode && $scope.model.submit) {
$scope.model.documentTypeAlias = vm.contentType.alias;
$scope.model.submit($scope.model);
}
@@ -447,10 +414,12 @@
// convert icons for content type
convertLegacyIcons(contentType);
//set a shared state
editorState.set(contentType);
vm.contentType = contentType;
//set a shared state
editorState.set(vm.contentType);
loadButtons();
}
/** Syncs the template alias for new doc types before saving if a template is to be created */
@@ -526,11 +495,10 @@
if (treeExists) {
navigationService.syncTree({ tree: "templates", path: [], forceReload: true })
.then(function (syncArgs) {
navigationService.reloadNode(syncArgs.node)
}
);
navigationService.reloadNode(syncArgs.node);
});
}
});
});
}
}));
@@ -41,25 +41,22 @@
</div>
</div>
<a data-element="editor-add" href="" ng-if="!model.parameter.editor" class="editor-placeholder" hotkey="alt+shift+e" ng-click="vm.openMacroParameterPicker(model.parameter)">
<button type="button" class="btn-reset editor-placeholder" data-element="editor-add" ng-if="!model.parameter.editor" hotkey="alt+shift+e" ng-click="vm.openMacroParameterPicker(model.parameter)">
<localize key="shortcuts_addEditor"></localize>
</a>
</button>
<div class="editor clearfix" ng-if="model.parameter.editor">
<div class="editor-details">
<button class="btn-reset editor-info" ng-click="vm.openMacroParameterPicker(model.parameter)">
<button type="button" class="btn-reset editor-info" ng-click="vm.openMacroParameterPicker(model.parameter)">
<div class="editor-icon-wrapper">
<i class="icon {{ model.parameter.dataTypeIcon }}" ng-class="{'icon-autofill': model.parameter.dataTypeIcon == null}" aria-hidden="true"></i>
</div>
<div class="editor-icon-wrapper">
<i class="icon {{model.parameter.dataTypeIcon}}" ng-class="{'icon-autofill': model.parameter.dataTypeIcon == null}"></i>
</div>
<div class="editor-details">
<a href="" class="editor-name">{{model.parameter.dataTypeName}}</a>
<a href="" class="editor-editor">{{model.parameter.editor}}</a>
</div>
</button>
</div>
<div class="editor-details">
<span class="editor-name">{{ model.parameter.dataTypeName }}</span>
<span class="editor-editor">{{ model.parameter.editor }}</span>
</div>
</button>
</div>
@@ -21,15 +21,15 @@
on-edit="model.openViewPicker()"
on-remove="model.removeMacroView()">
</umb-node-preview>
<input type="hidden" ng-model="mandatoryViewValidator" ng-required="!model.macro.node" />
<a href=""
ng-show="!model.macro.node"
class="umb-node-preview-add"
ng-click="model.openViewPicker()"
prevent-default>
<button type="button"
class="umb-node-preview-add"
ng-show="!model.macro.node"
ng-click="model.openViewPicker()">
<localize key="general_add">Add</localize>
</a>
</button>
</umb-control-group>
</umb-box-content>
@@ -1,7 +1,7 @@
(function () {
"use strict";
function MediaSortController($scope, $filter, mediaResource, navigationService) {
function MediaSortController($scope, $filter, mediaResource, navigationService, eventsService) {
var vm = this;
var id = $scope.currentNode.id;
@@ -50,6 +50,7 @@
navigationService.syncTree({ tree: "media", path: $scope.currentNode.path, forceReload: true })
.then(() => navigationService.reloadNode($scope.currentNode));
eventsService.emit("sortCompleted", { id: id });
vm.saveButtonState = "success";
}, function(error) {
vm.error = error;
@@ -1,13 +1,11 @@
<div ng-controller="Umbraco.Editors.Media.SortController as vm" ng-cloak>
<div class="umb-dialog-body with-footer sort-order">
<div class="umb-pane">
<div ng-if="vm.error">
<div class="alert alert-error">
<div>{{vm.error.errorMsg}}</div>
</div>
<div ng-if="vm.error" class="alert alert-error">
<p>{{vm.error.errorMsg}}</p>
</div>
<umb-load-indicator
@@ -15,27 +13,27 @@
</umb-load-indicator>
<div ng-if="!vm.loading">
<p class="abstract"><localize key="sort_sortHelp"></localize></p>
<p class="abstract"><localize key="sort_sortHelp">Drag the different items up or down below to set how they should be arranged. Or click the column headers to sort the entire collection of items</localize></p>
<table class="table table-condensed table-sortable" ng-if="vm.children">
<thead>
<tr>
<th colspan="2">
<a ng-href="#" ng-click="vm.sort('name')" prevent-default>
<localize key="general_name"></localize>
<span ng-if="vm.sortOrder.column === 'name'">
<button type="button" class="btn-reset" ng-click="vm.sort('name')">
<b><localize key="general_name">Name</localize></b>
<span ng-if="vm.sortOrder.column === 'name'" aria-hidden="true">
<i ng-if="vm.sortOrder.reverse" class="icon-navigation-up"></i>
<i ng-if="!vm.sortOrder.reverse" class="icon-navigation-down"></i>
</span>
</a>
</button>
</th>
<th>
<a ng-href="#" ng-click="vm.sort('createDate')" prevent-default>
<localize key="sort_sortCreationDate"></localize>
<span ng-if="vm.sortOrder.column === 'createDate'">
<button type="button" class="btn-reset" ng-click="vm.sort('createDate')">
<b><localize key="sort_sortCreationDate">Creation date</localize></b>
<span ng-if="vm.sortOrder.column === 'createDate'" aria-hidden="true">
<i ng-if="vm.sortOrder.reverse" class="icon-navigation-up"></i>
<i ng-if="!vm.sortOrder.reverse" class="icon-navigation-down"></i>
</span>
</a>
</button>
</th>
</tr>
</thead>
@@ -68,7 +66,7 @@
</umb-button>
<umb-button
ng-if="vm.children"
type="button"
type="button"
action="vm.save()"
state="vm.saveButtonState"
button-style="success"
@@ -7,6 +7,7 @@
ng-model="model.value"
ng-required="model.validation.mandatory"
aria-required="{{model.validation.mandatory}}"
id="{{model.alias}}"
val-server="value"
fix-number min="{{model.config.min}}" max="{{model.config.max}}" step="{{model.config.step}}" />
@@ -318,25 +318,26 @@ angular.module("umbraco")
// Add items overlay menu
// *********************************************
$scope.openEditorOverlay = function (event, area, index, key) {
var title = "";
localizationService.localize("grid_insertControl").then(function (value) {
title = value;
overlayService.open({
view: "itempicker",
filter: area.$allowedEditors.length > 15,
title: title,
availableItems: area.$allowedEditors,
event: event,
submit: function (model) {
if (model.selectedItem) {
$scope.addControl(model.selectedItem, area, index);
overlayService.close();
}
},
close: function () {
const dialog = {
view: "itempicker",
filter: area.$allowedEditors.length > 15,
availableItems: area.$allowedEditors,
event: event,
submit: function (model) {
if (model.selectedItem) {
$scope.addControl(model.selectedItem, area, index);
overlayService.close();
}
});
},
close: function () {
overlayService.close();
}
};
localizationService.localize("grid_insertControl").then(value => {
dialog.title = value;
overlayService.open(dialog);
});
};
@@ -251,7 +251,9 @@
<button
class="iconBox btn-reset"
ng-click="toggleAddRow()"
type="button"
title="{{ section.$allowedLayouts.length == 1 ? section.$allowedLayouts[0].label || section.$allowedLayouts[0].name : '' }}"
ng-click="section.$allowedLayouts.length == 1 ? addRow(section, section.$allowedLayouts[0]) : toggleAddRow()"
ng-if="!showRowConfigurations">
<i class="icon icon-add" ></i>
@@ -53,10 +53,9 @@ function includePropsPreValsController($rootScope, $scope, localizationService,
$scope.errorMsg = "";
}
$scope.removeField = function(e) {
$scope.model.value = _.reject($scope.model.value, function (x) {
return x.alias === e.alias;
});
$scope.removeField = function (e) {
var index = $scope.model.value.indexOf(e);
$scope.model.value.splice(index, 1);
}
//now we'll localize these strings, for some reason the directive doesn't work inside of the select group with an ng-model declared
@@ -1,4 +1,4 @@
function listViewController($scope, $interpolate, $routeParams, $injector, $timeout, currentUserResource, notificationsService, iconHelper, editorState, localizationService, appState, $location, listViewHelper, navigationService, editorService, overlayService, languageResource, mediaHelper) {
function listViewController($scope, $interpolate, $routeParams, $injector, $timeout, currentUserResource, notificationsService, iconHelper, editorState, localizationService, appState, $location, listViewHelper, navigationService, editorService, overlayService, languageResource, mediaHelper, eventsService) {
//this is a quick check to see if we're in create mode, if so just exit - we cannot show children for content
// that isn't created yet, if we continue this will use the parent id in the route params which isn't what
@@ -838,6 +838,19 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
$scope.toggleDropdown = toggleDropdown;
$scope.leaveDropdown = leaveDropdown;
// if this listview has sort order in it, make sure it is updated when sorting is performed on the current content
if (_.find($scope.options.includeProperties, property => property.alias === "sortOrder")) {
var eventSubscription = eventsService.on("sortCompleted", function (e, args) {
if (parseInt(args.id) === parseInt($scope.contentId)) {
$scope.reloadView($scope.contentId);
}
});
$scope.$on('$destroy', function () {
eventsService.unsubscribe(eventSubscription);
});
}
//GO!
initView();
}
@@ -46,7 +46,7 @@ function sortByPreValsController($rootScope, $scope, localizationService, editor
var systemFields = [
{ value: "SortOrder", key: "general_sort" },
{ value: "Name", key: "general_name" },
{ value: "VersionDate", key: "content_updateDate" },
{ value: "UpdateDate", key: "content_updateDate" },
{ value: "Updater", key: "content_updatedBy" },
{ value: "CreateDate", key: "content_createDate" },
{ value: "Owner", key: "content_createBy" },
@@ -57,28 +57,19 @@ function sortByPreValsController($rootScope, $scope, localizationService, editor
];
_.each(systemFields, function (e) {
localizationService.localize(e.key).then(function (v) {
var sortByListValue = findFromSortByFields(e.value);
if (sortByListValue) {
sortByListValue.name = v;
switch (e.value) {
case "Updater":
e.name += " (Content only)";
break;
case "Published":
e.name += " (Content only)";
break;
case "Email":
e.name += " (Members only)";
break;
case "Username":
e.name += " (Members only)";
break;
}
}
});
});
_.each($scope.sortByFields, function (sortByField) {
if (!sortByField.name) {
sortByField.name = "(" + sortByField.value + ")";
}
});
// Check existing model value is available in list and ensure a value is set
var existingValue = findFromSortByFields($scope.model.value);
if (existingValue) {
@@ -228,7 +228,8 @@
vm.overlayMenu.availableItems.push({
alias: scaffold.contentTypeAlias,
name: scaffold.contentTypeName,
icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
icon: iconHelper.convertFromLegacyIcon(scaffold.icon),
tooltip: scaffold.documentType.description
});
});
@@ -2,10 +2,9 @@
<ul class="nav nav-stacked">
<li ng-repeat="value in renderModel">
<a href="{{getUrl(value.url)}}" prevent-default="{{value.url.indexOf('/') == -1}}" target="{{value.urlTarget}}">
<i ng-class="value.icon"></i>
<span ng-if="value.linkText">{{value.linkText}}</span>
<span ng-if="!value.linkText">{{value.url}}</span>
<i ng-class="value.icon" aria-hidden="true"></i>
<span>{{value.linkText || value.url}}</span>
</a>
</li>
</ul>
</div>
</div>
@@ -1098,10 +1098,13 @@ Mange hilsner fra Umbraco robotten
<key alias="customCrop">Brugerdefineret</key>
</area>
<area alias="rollback">
<key alias="changes">Ændringer</key>
<key alias="created">Oprettet</key>
<key alias="headline">Vælg en version at sammenligne med den nuværende version</key>
<key alias="currentVersion">Nuværende version</key>
<key alias="diffHelp"><![CDATA[Her vises forskellene mellem den nuværende version og den valgte version<br /><del>Rød</del> tekst vil ikke blive vist i den valgte version. <ins>Grøn betyder tilføjet</ins>]]></key>
<key alias="documentRolledBack">Dokument tilbagerullet</key>
<key alias="headline">Vælg en version at sammenligne med den nuværende version</key>
<key alias="htmlHelp">Her vises den valgte version som html. Hvis du ønsker at se forskellen mellem de 2 versioner på samme tid, brug 'diff'-oversigten</key>
<key alias="rollbackTo">Tilbagerulning til</key>
<key alias="selectVersion">Vælg version</key>
@@ -1345,10 +1345,12 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="customCrop">User defined</key>
</area>
<area alias="rollback">
<key alias="headline">Select a version to compare with the current version</key>
<key alias="changes">Changes</key>
<key alias="created">Created</key>
<key alias="currentVersion">Current version</key>
<key alias="diffHelp"><![CDATA[This shows the differences between the current version and the selected version<br /><del>Red</del> text will not be shown in the selected version. , <ins>green means added</ins>]]></key>
<key alias="documentRolledBack">Document has been rolled back</key>
<key alias="headline">Select a version to compare with the current version</key>
<key alias="htmlHelp">This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view</key>
<key alias="rollbackTo">Rollback to</key>
<key alias="selectVersion">Select version</key>
@@ -1353,10 +1353,13 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="customCrop">User defined</key>
</area>
<area alias="rollback">
<key alias="changes">Changes</key>
<key alias="created">Created</key>
<key alias="headline">Select a version to compare with the current version</key>
<key alias="currentVersion">Current version</key>
<key alias="diffHelp"><![CDATA[This shows the differences between the current version and the selected version<br /><del>Red</del> text will not be shown in the selected version. , <ins>green means added</ins>]]></key>
<key alias="documentRolledBack">Document has been rolled back</key>
<key alias="headline">Select a version to compare with the current version</key>
<key alias="htmlHelp">This displays the selected version as HTML, if you wish to see the difference between 2 versions at the same time, use the diff view</key>
<key alias="rollbackTo">Rollback to</key>
<key alias="selectVersion">Select version</key>
+1 -10
View File
@@ -1,8 +1,7 @@
<?xml version="1.0"?>
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<!--
<!--
Defines transforms that apply to web.Template.config when creating the Debug web.config.
This file should contain changes that need to go into the Debug config (the one that everybody
@@ -13,14 +12,6 @@
file everytime Umbraco builds.
-->
<configSections>
<section name="Examine" xdt:Transform="Remove" xdt:Locator="Match(name)" />
<section name="ExamineLuceneIndexSets" xdt:Transform="Remove" xdt:Locator="Match(name)" />
</configSections>
<Examine xdt:Transform="Remove" />
<ExamineLuceneIndexSets xdt:Transform="Remove" />
<appSettings>
<add key="Umbraco.TestData.Enabled" xdt:Transform="Remove" xdt:Locator="Match(key)"/>
<add key="Umbraco.TestData.Enabled" value="true" xdt:Transform="Insert" />
@@ -30,9 +30,6 @@ namespace Umbraco.Web.ContentApps
Weight = Weight
});
case IContent _:
return null;
case IMedia media when !media.ContentType.IsContainer || media.Properties.Count > 0:
return _mediaApp ?? (_mediaApp = new ContentApp
{
@@ -43,9 +40,6 @@ namespace Umbraco.Web.ContentApps
Weight = Weight
});
case IMedia _:
return null;
case IMember _:
return _memberApp ?? (_memberApp = new ContentApp
{
@@ -57,7 +51,7 @@ namespace Umbraco.Web.ContentApps
});
default:
throw new NotSupportedException($"Object type {o.GetType()} is not supported here.");
return null;
}
}
}
@@ -49,7 +49,7 @@ namespace Umbraco.Web.ContentApps
});
default:
throw new NotSupportedException($"Object type {o.GetType()} is not supported here.");
return null;
}
}
}
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.ContentApps
{
internal class ContentTypeDesignContentAppFactory : IContentAppFactory
{
private const int Weight = -200;
private ContentApp _contentTypeApp;
public ContentApp GetContentAppFor(object source, IEnumerable<IReadOnlyUserGroup> userGroups)
{
switch (source)
{
case IContentType _:
return _contentTypeApp ?? (_contentTypeApp = new ContentApp()
{
Alias = "design",
Name = "Design",
Icon = "icon-document-dashed-line",
View = "views/documenttypes/views/design/design.html",
Weight = Weight
});
default:
return null;
}
}
}
}
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.ContentApps
{
internal class ContentTypeListViewContentAppFactory : IContentAppFactory
{
private const int Weight = -180;
private ContentApp _contentTypeApp;
public ContentApp GetContentAppFor(object source, IEnumerable<IReadOnlyUserGroup> userGroups)
{
switch (source)
{
case IContentType _:
return _contentTypeApp ?? (_contentTypeApp = new ContentApp()
{
Alias = "listView",
Name = "List view",
Icon = "icon-list",
View = "views/documenttypes/views/listview/listview.html",
Weight = Weight
});
default:
return null;
}
}
}
}
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.ContentApps
{
internal class ContentTypePermissionsContentAppFactory : IContentAppFactory
{
private const int Weight = -160;
private ContentApp _contentTypeApp;
public ContentApp GetContentAppFor(object source, IEnumerable<IReadOnlyUserGroup> userGroups)
{
switch (source)
{
case IContentType _:
return _contentTypeApp ?? (_contentTypeApp = new ContentApp()
{
Alias = "permissions",
Name = "Permissions",
Icon = "icon-keychain",
View = "views/documenttypes/views/permissions/permissions.html",
Weight = Weight
});
default:
return null;
}
}
}
}
@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Models.Membership;
namespace Umbraco.Web.ContentApps
{
internal class ContentTypeTemplatesContentAppFactory : IContentAppFactory
{
private const int Weight = -140;
private ContentApp _contentTypeApp;
public ContentApp GetContentAppFor(object source, IEnumerable<IReadOnlyUserGroup> userGroups)
{
switch (source)
{
case IContentType _:
return _contentTypeApp ?? (_contentTypeApp = new ContentApp()
{
Alias = "templates",
Name = "Templates",
Icon = "icon-layout",
View = "views/documenttypes/views/templates/templates.html",
Weight = Weight
});
default:
return null;
}
}
}
}
@@ -45,10 +45,8 @@ namespace Umbraco.Web.ContentApps
entityType = "media";
dtdId = Core.Constants.DataTypes.DefaultMediaListView;
break;
case IMember member:
return null;
default:
throw new NotSupportedException($"Object type {o.GetType()} is not supported here.");
return null;
}
return CreateContentApp(_dataTypeService, _propertyEditors, entityType, contentTypeAlias, dtdId);
@@ -551,7 +551,6 @@ namespace Umbraco.Web.Editors
}
[HttpPost]
[FileUploadCleanupFilter(false)]
public async Task<ContentTypeImportModel> Upload()
{
if (Request.Content.IsMimeMultipartContent() == false)
@@ -577,20 +576,28 @@ namespace Umbraco.Web.Editors
var fileName = file.Headers.ContentDisposition.FileName.Trim('\"');
var ext = fileName.Substring(fileName.LastIndexOf('.') + 1).ToLower();
// renaming the file because MultipartFormDataStreamProvider has created a random fileName instead of using the name from the
// content-disposition for more than 6 years now. Creating a CustomMultipartDataStreamProvider deriving from MultipartFormDataStreamProvider
// seems like a cleaner option, but I'm not sure where to put it and renaming only takes one line of code.
System.IO.File.Move(result.FileData[0].LocalFileName, root + "\\" + fileName);
var destFileName = root + "\\" + fileName;
try
{
// due to a bug before 8.7.0 we didn't delete temp files, so we need to make sure to delete before
// moving else you get errors and the upload fails without a message in the UI (there's a JS error)
if(System.IO.File.Exists(destFileName))
System.IO.File.Delete(destFileName);
// renaming the file because MultipartFormDataStreamProvider has created a random fileName instead of using the name from the
// content-disposition for more than 6 years now. Creating a CustomMultipartDataStreamProvider deriving from MultipartFormDataStreamProvider
// seems like a cleaner option, but I'm not sure where to put it and renaming only takes one line of code.
System.IO.File.Move(result.FileData[0].LocalFileName, destFileName);
}
catch (Exception ex)
{
Logger.Error<ContentTypeController>(ex, "Error uploading udt file to App_Data: {File}", destFileName);
}
if (ext.InvariantEquals("udt"))
{
model.TempFileName = Path.Combine(root, fileName);
model.UploadedFiles.Add(new ContentPropertyFile
{
TempFilePath = model.TempFileName
});
var xd = new XmlDocument
{
XmlResolver = null
@@ -142,7 +142,7 @@ namespace Umbraco.Web.Editors
//need to create a specific content result formatted as HTML since this controller has been configured
//with only json formatters.
result.Content = new StringContent(
_componentRenderer.RenderMacro(pageId, m.Alias, macroParams).ToString(),
_componentRenderer.RenderMacroForContent(publishedContent, m.Alias, macroParams).ToString(),
Encoding.UTF8,
"text/html");
+53 -24
View File
@@ -253,12 +253,12 @@ namespace Umbraco.Web
}
/// <summary>
/// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller
/// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller.
/// </summary>
/// <param name="html"></param>
/// <param name="action"></param>
/// <param name="controllerName"></param>
/// <param name="method"></param>
/// <param name="html">The HTML helper.</param>
/// <param name="action">Name of the action.</param>
/// <param name="controllerName">Name of the controller.</param>
/// <param name="method">The method.</param>
/// <returns></returns>
public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, FormMethod method)
{
@@ -315,9 +315,9 @@ namespace Umbraco.Web
/// <param name="method"></param>
/// <returns></returns>
public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName,
object additionalRouteVals,
object htmlAttributes,
FormMethod method)
object additionalRouteVals,
object htmlAttributes,
FormMethod method)
{
return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes), method);
}
@@ -332,8 +332,8 @@ namespace Umbraco.Web
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName,
object additionalRouteVals,
object htmlAttributes)
object additionalRouteVals,
object htmlAttributes)
{
return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, HtmlHelper.AnonymousObjectToHtmlAttributes(htmlAttributes));
}
@@ -349,9 +349,9 @@ namespace Umbraco.Web
/// <param name="method"></param>
/// <returns></returns>
public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName,
object additionalRouteVals,
IDictionary<string, object> htmlAttributes,
FormMethod method)
object additionalRouteVals,
IDictionary<string, object> htmlAttributes,
FormMethod method)
{
if (action == null) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrWhiteSpace(action)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(action));
@@ -371,8 +371,8 @@ namespace Umbraco.Web
/// <param name="htmlAttributes"></param>
/// <returns></returns>
public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName,
object additionalRouteVals,
IDictionary<string, object> htmlAttributes)
object additionalRouteVals,
IDictionary<string, object> htmlAttributes)
{
if (action == null) throw new ArgumentNullException(nameof(action));
if (string.IsNullOrWhiteSpace(action)) throw new ArgumentException("Value can't be empty or consist only of white-space characters.", nameof(action));
@@ -840,19 +840,32 @@ namespace Umbraco.Web
#region If
/// <summary>
/// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned.
/// If <paramref name="test" /> is <c>true</c>, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
public static IHtmlString If(this HtmlHelper html, bool test, string valueIfTrue, string valueIfFalse)
/// <param name="html">The HTML helper.</param>
/// <param name="test">If set to <c>true</c> returns <paramref name="valueIfTrue" />; otherwise, <see cref="string.Empty" />.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
public static IHtmlString If(this HtmlHelper html, bool test, string valueIfTrue)
{
return test ? new HtmlString(valueIfTrue) : new HtmlString(valueIfFalse);
return If(html, test, valueIfTrue, string.Empty);
}
/// <summary>
/// If the test is true, the string valueIfTrue will be returned, otherwise the valueIfFalse will be returned.
/// If <paramref name="test" /> is <c>true</c>, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
public static IHtmlString If(this HtmlHelper html, bool test, string valueIfTrue)
/// <param name="html">The HTML helper.</param>
/// <param name="test">If set to <c>true</c> returns <paramref name="valueIfTrue" />; otherwise, <paramref name="valueIfFalse" />.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
public static IHtmlString If(this HtmlHelper html, bool test, string valueIfTrue, string valueIfFalse)
{
return test ? new HtmlString(valueIfTrue) : new HtmlString(string.Empty);
return new HtmlString(HttpUtility.HtmlEncode(test ? valueIfTrue : valueIfFalse));
}
#endregion
@@ -862,16 +875,32 @@ namespace Umbraco.Web
private static readonly HtmlStringUtilities StringUtilities = new HtmlStringUtilities();
/// <summary>
/// Replaces text line breaks with HTML line breaks
/// Replaces text line breaks with HTML line breaks.
/// </summary>
/// <param name="helper"></param>
/// <param name="helper">The HTML helper.</param>
/// <param name="text">The text.</param>
/// <returns>The text with text line breaks replaced with HTML line breaks (<br/>)</returns>
/// <returns>
/// The text with text line breaks replaced with HTML line breaks (<c>&lt;br /&gt;</c>).
/// </returns>
[Obsolete("This method doesn't HTML encode the text. Use ReplaceLineBreaks instead.")]
public static IHtmlString ReplaceLineBreaksForHtml(this HtmlHelper helper, string text)
{
return StringUtilities.ReplaceLineBreaksForHtml(text);
}
/// <summary>
/// HTML encodes the text and replaces text line breaks with HTML line breaks.
/// </summary>
/// <param name="helper">The HTML helper.</param>
/// <param name="text">The text.</param>
/// <returns>
/// The HTML encoded text with text line breaks replaced with HTML line breaks (<c>&lt;br /&gt;</c>).
/// </returns>
public static IHtmlString ReplaceLineBreaks(this HtmlHelper helper, string text)
{
return StringUtilities.ReplaceLineBreaks(text);
}
/// <summary>
/// Generates a hash based on the text string passed in. This method will detect the
/// security requirements (is FIPS enabled) and return an appropriate hash.
+9 -5
View File
@@ -16,21 +16,25 @@ namespace Umbraco.Web
public sealed class HtmlStringUtilities
{
/// <summary>
/// Replaces text line breaks with HTML line breaks
/// Replaces text line breaks with HTML line breaks.
/// </summary>
/// <param name="text">The text.</param>
/// <returns>The text with text line breaks replaced with HTML line breaks (<c>&lt;br /&gt;</c>).
/// <returns>
/// The text with text line breaks replaced with HTML line breaks (<c>&lt;br /&gt;</c>).
/// </returns>
[Obsolete("This method doesn't HTML encode the text. Use ReplaceLineBreaks instead.")]
public HtmlString ReplaceLineBreaksForHtml(string text)
{
return new HtmlString(text.Replace("\r\n", @"<br />").Replace("\n", @"<br />").Replace("\r", @"<br />"));
return new HtmlString(text.Replace("\r\n", @"<br />").Replace("\n", @"<br />").Replace("\r", @"<br />"));
}
/// <summary>
/// HTML encodes the text and replaces text line breaks with HTML line breaks.
/// </summary>
/// <param name="text">The text.</param>
/// <returns>The HTML encoded text with text line breaks replaced with HTML line breaks (<c>&lt;br /&gt;</c>).</returns>
/// <returns>
/// The HTML encoded text with text line breaks replaced with HTML line breaks (<c>&lt;br /&gt;</c>).
/// </returns>
public IHtmlString ReplaceLineBreaks(string text)
{
var value = HttpUtility.HtmlEncode(text)?
@@ -42,5 +42,18 @@ namespace Umbraco.Web
/// <param name="parameters">The parameters.</param>
/// <returns></returns>
IHtmlString RenderMacro(int contentId, string alias, IDictionary<string, object> parameters);
/// <summary>
/// Renders the macro with the specified alias, passing in the specified parameters.
/// </summary>
/// <param name="content">An IPublishedContent to use for the context for the macro rendering</param>
/// <param name="alias">The alias.</param>
/// <param name="parameters">The parameters.</param>
/// <returns>A raw HTML string of the macro output</returns>
/// <remarks>
/// Currently only used when the node is unpublished and unable to get the contentId item from the
/// content cache as its unpublished. This deals with taking in a preview/draft version of the content node
/// </remarks>
IHtmlString RenderMacroForContent(IPublishedContent content, string alias, IDictionary<string, object> parameters);
}
}
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.ContentEditing;
namespace Umbraco.Web.Models.ContentEditing
{
@@ -27,5 +28,8 @@ namespace Umbraco.Web.Models.ContentEditing
[DataMember(Name = "allowSegmentVariant")]
public bool AllowSegmentVariant { get; set; }
[DataMember(Name = "apps")]
public IEnumerable<ContentApp> ContentApps { get; set; }
}
}
@@ -1,12 +1,11 @@
using System.Collections.Generic;
using System.Runtime.Serialization;
using Umbraco.Core.Models.Editors;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models
{
[DataContract(Name = "contentTypeImportModel")]
public class ContentTypeImportModel : INotificationModel, IHaveUploadedFiles
public class ContentTypeImportModel : INotificationModel
{
[DataMember(Name = "alias")]
public string Alias { get; set; }
@@ -19,7 +18,5 @@ namespace Umbraco.Web.Models
[DataMember(Name = "tempFileName")]
public string TempFileName { get; set; }
public List<ContentPropertyFile> UploadedFiles => new List<ContentPropertyFile>();
}
}
@@ -7,6 +7,7 @@ using Umbraco.Core;
using Umbraco.Core.Mapping;
using Umbraco.Core.Models;
using Umbraco.Core.Models.ContentEditing;
using Umbraco.Core.Models.Entities;
using Umbraco.Core.Models.Membership;
using Umbraco.Core.Services;
using Umbraco.Web.ContentApps;
@@ -81,7 +82,7 @@ namespace Umbraco.Web.Models.Mapping
return urlHelper.GetUmbracoApiService<MemberTreeController>(controller => controller.GetTreeNode(source.Key.ToString("N"), null));
}
public IEnumerable<ContentApp> GetContentApps(IContentBase source)
public IEnumerable<ContentApp> GetContentApps(IUmbracoEntity source)
{
var apps = _contentAppDefinitions.GetContentAppsFor(source).ToArray();
@@ -17,6 +17,7 @@ namespace Umbraco.Web.Models.Mapping
/// </summary>
internal class ContentTypeMapDefinition : IMapDefinition
{
private readonly CommonMapper _commonMapper;
private readonly PropertyEditorCollection _propertyEditors;
private readonly IDataTypeService _dataTypeService;
private readonly IFileService _fileService;
@@ -25,10 +26,11 @@ namespace Umbraco.Web.Models.Mapping
private readonly IMemberTypeService _memberTypeService;
private readonly ILogger _logger;
public ContentTypeMapDefinition(PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService,
public ContentTypeMapDefinition(CommonMapper commonMapper, PropertyEditorCollection propertyEditors, IDataTypeService dataTypeService, IFileService fileService,
IContentTypeService contentTypeService, IMediaTypeService mediaTypeService, IMemberTypeService memberTypeService,
ILogger logger)
{
_commonMapper = commonMapper;
_propertyEditors = propertyEditors;
_dataTypeService = dataTypeService;
_fileService = fileService;
@@ -122,6 +124,7 @@ namespace Umbraco.Web.Models.Mapping
target.AllowCultureVariant = source.VariesByCulture();
target.AllowSegmentVariant = source.VariesBySegment();
target.ContentApps = _commonMapper.GetContentApps(source);
//sync templates
target.AllowedTemplates = context.MapEnumerable<ITemplate, EntityBasic>(source.AllowedTemplates);
@@ -12,7 +12,7 @@ namespace Umbraco.Web.Mvc
public void WriteToHtmlTextWriter(System.Web.UI.HtmlTextWriter html)
{
html.Write(Content);
html.WriteEncodedText(Content);
}
}
}
+143 -6
View File
@@ -287,29 +287,82 @@ namespace Umbraco.Web
return content.Id == other.Id;
}
/// <summary>
/// If the specified <paramref name="content" /> is equal to <paramref name="other" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
{
return content.IsEqual(other, valueIfTrue, string.Empty);
}
/// <summary>
/// If the specified <paramref name="content" /> is equal to <paramref name="other" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(content.IsEqual(other) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(content.IsEqual(other) ? valueIfTrue : valueIfFalse));
}
/// <summary>
/// If the specified <paramref name="content" /> is not equal to <paramref name="other" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static bool IsNotEqual(this IPublishedContent content, IPublishedContent other)
{
return content.IsEqual(other) == false;
}
/// <summary>
/// If the specified <paramref name="content" /> is not equal to <paramref name="other" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
{
return content.IsNotEqual(other, valueIfTrue, string.Empty);
}
/// <summary>
/// If the specified <paramref name="content" /> is not equal to <paramref name="other" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsNotEqual(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(content.IsNotEqual(other) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(content.IsNotEqual(other) ? valueIfTrue : valueIfFalse));
}
#endregion
@@ -321,14 +374,35 @@ namespace Umbraco.Web
return other.Level < content.Level && content.Path.InvariantStartsWith(other.Path.EnsureEndsWith(','));
}
/// <summary>
/// If the specified <paramref name="content" /> is a decendant of <paramref name="other" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
{
return content.IsDescendant(other, valueIfTrue, string.Empty);
}
/// <summary>
/// If the specified <paramref name="content" /> is a decendant of <paramref name="other" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsDescendant(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(content.IsDescendant(other) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(content.IsDescendant(other) ? valueIfTrue : valueIfFalse));
}
public static bool IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other)
@@ -336,14 +410,35 @@ namespace Umbraco.Web
return content.Path.InvariantEquals(other.Path) || content.IsDescendant(other);
}
/// <summary>
/// If the specified <paramref name="content" /> is a decendant of <paramref name="other" /> or are the same, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
{
return content.IsDescendantOrSelf(other, valueIfTrue, string.Empty);
}
/// <summary>
/// If the specified <paramref name="content" /> is a decendant of <paramref name="other" /> or are the same, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsDescendantOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(content.IsDescendantOrSelf(other) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(content.IsDescendantOrSelf(other) ? valueIfTrue : valueIfFalse));
}
public static bool IsAncestor(this IPublishedContent content, IPublishedContent other)
@@ -351,14 +446,35 @@ namespace Umbraco.Web
return content.Level < other.Level && other.Path.InvariantStartsWith(content.Path.EnsureEndsWith(','));
}
/// <summary>
/// If the specified <paramref name="content" /> is an ancestor of <paramref name="other" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
{
return content.IsAncestor(other, valueIfTrue, string.Empty);
}
/// <summary>
/// If the specified <paramref name="content" /> is an ancestor of <paramref name="other" />, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsAncestor(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(content.IsAncestor(other) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(content.IsAncestor(other) ? valueIfTrue : valueIfFalse));
}
public static bool IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other)
@@ -366,14 +482,35 @@ namespace Umbraco.Web
return other.Path.InvariantEquals(content.Path) || content.IsAncestor(other);
}
/// <summary>
/// If the specified <paramref name="content" /> is an ancestor of <paramref name="other" /> or are the same, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <see cref="string.Empty" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue)
{
return content.IsAncestorOrSelf(other, valueIfTrue, string.Empty);
}
/// <summary>
/// If the specified <paramref name="content" /> is an ancestor of <paramref name="other" /> or are the same, the HTML encoded <paramref name="valueIfTrue" /> will be returned; otherwise, <paramref name="valueIfFalse" />.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="other">The other content.</param>
/// <param name="valueIfTrue">The value if <c>true</c>.</param>
/// <param name="valueIfFalse">The value if <c>false</c>.</param>
/// <returns>
/// The HTML encoded value.
/// </returns>
// TODO: This method should be removed or moved to an extension method on HtmlHelper.
public static HtmlString IsAncestorOrSelf(this IPublishedContent content, IPublishedContent other, string valueIfTrue, string valueIfFalse)
{
return new HtmlString(content.IsAncestorOrSelf(other) ? valueIfTrue : valueIfFalse);
return new HtmlString(HttpUtility.HtmlEncode(content.IsAncestorOrSelf(other) ? valueIfTrue : valueIfFalse));
}
#endregion
@@ -239,7 +239,11 @@ namespace Umbraco.Web.Runtime
composition.ContentApps()
.Append<ListViewContentAppFactory>()
.Append<ContentEditorContentAppFactory>()
.Append<ContentInfoContentAppFactory>();
.Append<ContentInfoContentAppFactory>()
.Append<ContentTypeDesignContentAppFactory>()
.Append<ContentTypeListViewContentAppFactory>()
.Append<ContentTypePermissionsContentAppFactory>()
.Append<ContentTypeTemplatesContentAppFactory>();
// register back office sections in the order we want them rendered
composition.Sections()
+4
View File
@@ -136,6 +136,10 @@
<Compile Include="Composing\CompositionExtensions\Installer.cs" />
<Compile Include="Composing\LightInject\LightInjectContainer.cs" />
<Compile Include="Compose\BackOfficeUserAuditEventsComponent.cs" />
<Compile Include="ContentApps\ContentTypeTemplatesContentAppFactory.cs" />
<Compile Include="ContentApps\ContentTypePermissionsContentAppFactory.cs" />
<Compile Include="ContentApps\ContentTypeListViewContentAppFactory.cs" />
<Compile Include="ContentApps\ContentTypeDesignContentAppFactory.cs" />
<Compile Include="ContentApps\ListViewContentAppFactory.cs" />
<Compile Include="Dashboards\ContentDashboard.cs" />
<Compile Include="Dashboards\DashboardCollection.cs" />
@@ -102,6 +102,15 @@ namespace Umbraco.Web
return RenderMacro(content, alias, parameters);
}
public IHtmlString RenderMacroForContent(IPublishedContent content, string alias, IDictionary<string, object> parameters)
{
if(content == null)
throw new InvalidOperationException("Cannot render a macro, IPublishedContent is null");
return RenderMacro(content, alias, parameters);
}
/// <summary>
/// Renders the macro with the specified alias, passing in the specified parameters.
/// </summary>