From b2a5ce3e4873a000a0038619b959c8785a400c64 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 24 Jul 2023 08:17:21 +0000 Subject: [PATCH 01/23] V10: Dropzone should handle internal and external errors when uploading (#14578) * fix: mark files that result in error as processed * fix: for safety measure check that a file is truthy before trying to upload it * fix: push an error when file.$error is encountered to make sure it does not get uploaded * fix: remove header from error messages since it is not being used anyway * fix: check for maxFileSize before uploading pasted images in tinymce * use stored blob variable * feat: add property to fileManager to get and format the maxFileSize * fix: make tinymce use fileManager to get maxFileSize * fix(image cropper): check for maxFileSize before setting file to upload * multiply by 1000 to get bytes --------- Co-authored-by: Elitsa --- .../upload/umbfiledropzone.directive.js | 33 ++++++++++++++++--- .../upload/umbpropertyfileupload.directive.js | 18 ++++++++-- .../common/services/filemanager.service.js | 11 +++++++ .../src/common/services/tinymce.service.js | 22 +++++++++---- .../components/upload/umb-file-dropzone.html | 6 ++-- 5 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index 98f02b7e06..25ebbd0cbc 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -31,7 +31,7 @@ angular.module("umbraco.directives") propertyAlias: '@', accept: '@', maxFileSize: '@', - + compact: '@', hideDropzone: '@', acceptedMediatypes: '=', @@ -87,7 +87,7 @@ angular.module("umbraco.directives") // Add the processed length, as we might be uploading in stages scope.totalQueued = scope.queue.length + scope.processed.length; - _processQueueItems(); + _processQueueItems(); } function _processQueueItems() { @@ -115,6 +115,18 @@ angular.module("umbraco.directives") function _upload(file) { + if (!file) { + return; + } + + if (file.$error) { + scope.processed.push(file); + scope.currentFile = undefined; + file.messages.push({type: "Error"}); + _processQueueItems(); + return; + } + scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : "umbracoFile"; scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : "Image"; @@ -158,11 +170,22 @@ angular.module("umbraco.directives") } else if (evt && typeof evt === "string") { file.messages.push({message: evt, type: "Error"}); } - // If file not found, server will return a 404 and display this message - if (status === 404) { - file.messages.push({message: "File not found", type: "Error"}); + + // If there were no errors with the request, but the status code was 404, we'll add a custom message + // or a generic message for all other status codes. + if (!file.messages.length) { + if (status === 404) { + file.messages.push({message: "File not found", type: "Error"}); + } else { + file.messages.push({message: "Error uploading file", type: "Error"}); + } } + + // The file has been processed, even though it resulted in an error, so we add it to the processed queue + scope.processed.push(file); scope.currentFile = undefined; + + // Return to queue processing _processQueueItems(); }); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js index 7de961bb34..eebdb7c223 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js @@ -7,8 +7,10 @@ * @param {any} fileManager * @param {any} mediaHelper * @param {any} angularHelper + * @param {any} $attrs + * @param {any} notificationsService */ - function umbPropertyFileUploadController($scope, $q, fileManager, mediaHelper, angularHelper, $attrs) { + function umbPropertyFileUploadController($scope, $q, fileManager, mediaHelper, angularHelper, $attrs, notificationsService) { //NOTE: this component supports multiple files, though currently the uploader does not but perhaps sometime in the future // we'd want it to, so i'll leave the multiple file support in place @@ -271,15 +273,25 @@ if (args.files && args.files.length > 0) { + const filesAllowed = []; + + for (let i = 0; i < args.files.length; i++) { + if (fileManager.maxFileSize && args.files[i].size > fileManager.maxFileSize) { + notificationsService.error(`File upload "${args.files[i].name}"`, `File size of ${args.files[i].size / 1000} KB exceeds the maximum allowed size of ${fileManager.maxFileSize / 1000} KB`); + } else { + filesAllowed.push(args.files[i]); + } + } + //set the files collection fileManager.setFiles({ propertyAlias: vm.propertyAlias, - files: args.files, + files: filesAllowed, culture: vm.culture, segment: vm.segment }); - updateModelFromSelectedFiles(args.files).then(function(newVal) { + updateModelFromSelectedFiles(filesAllowed).then(function(newVal) { angularHelper.safeApply($scope, function() { //pass in the file names and the model files diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 38aee3fc4a..6b2bd3c295 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -14,6 +14,17 @@ function fileManager($rootScope) { var mgr = { + /** + * @ngdoc property + * @name umbraco.services.fileManager#maxFileSize + * @propertyOf umbraco.services.fileManager + * @type {Number} + * @default 0 + * @description + * The max file size allowed to be uploaded to the server in bytes + */ + maxFileSize: parseInt(Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize ?? '0', 10) * 1000, + /** * @ngdoc function * @name umbraco.services.fileManager#setFiles diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 584870e6e5..b9971bdee6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -7,7 +7,7 @@ * A service containing all logic for all of the Umbraco TinyMCE plugins */ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, - $routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource, eventsService, localStorageService, mediaHelper) { + $routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource, eventsService, localStorageService, mediaHelper, fileManager) { //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce @@ -222,6 +222,14 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } function uploadImageHandler(blobInfo, success, failure, progress){ + const blob = blobInfo.blob(); + + // if the file size is greater than the max file size, reject it + if (fileManager.maxFileSize > 0 && blob.size > fileManager.maxFileSize) { + failure(`The file size (${blob.size / 1000} KB) exceeded the maximum allowed size of ${fileManager.maxFileSize / 1000} KB.`); + return; + } + const xhr = new XMLHttpRequest(); xhr.open('POST', Umbraco.Sys.ServerVariables.umbracoUrls.tinyMceApiBaseUrl + 'UploadImage'); @@ -285,7 +293,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }; const formData = new FormData(); - formData.append('file', blobInfo.blob(), blobInfo.blob().name); + formData.append('file', blob, blob.name); xhr.send(formData); } @@ -727,11 +735,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }; var newImage = editor.dom.createHTML('img', data); var parentElement = editor.selection.getNode().parentElement; - + if (img.caption) { var figCaption = editor.dom.createHTML('figcaption', {}, img.caption); var combined = newImage + figCaption; - + if (parentElement.nodeName !== 'FIGURE') { var fragment = editor.dom.createHTML('figure', {}, combined); editor.selection.setContent(fragment); @@ -749,7 +757,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s editor.selection.setContent(newImage); } } - + // Using settimeout to wait for a DoM-render, so we can find the new element by ID. $timeout(function () { @@ -770,7 +778,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } }); - + } }, @@ -1454,7 +1462,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s // Then we need to add an event listener to the editor // That will update native browser drag & drop events // To update the icon to show you can NOT drop something into the editor - + var toolbarItems = args.editor.settings.toolbar === false ? [] : args.editor.settings.toolbar.split(" "); if(isMediaPickerEnabled(toolbarItems) === false){ // Wire up the event listener diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html index 6581fca14d..448539f7c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html @@ -41,7 +41,7 @@ From 24844c6ce25c66c113e7b6ed2f9f900c624a0cdb Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Mon, 24 Jul 2023 08:17:58 +0000 Subject: [PATCH 02/23] V12: Dropzone should handle internal and external errors when uploading (#14579) * fix: for safety measure check that a file is truthy before trying to upload it * fix: push an error when file.$error is encountered to make sure it does not get uploaded * fix: mark file as done if it errors * format error messages * fix: check for maxFileSize before uploading pasted images in tinymce * remove the image from the DOM if any error is encountered * feat: add property to fileManager to get and format the maxFileSize * fix: make tinymce use fileManager to get maxFileSize * fix(image cropper): check for maxFileSize before setting file to upload * multiply by 1000 to get bytes --- .../upload/umbfiledropzone.directive.js | 11 +++++ .../upload/umbpropertyfileupload.directive.js | 18 +++++++-- .../common/services/filemanager.service.js | 11 +++++ .../src/common/services/tinymce.service.js | 40 +++++++++++++++---- .../components/upload/umb-file-dropzone.html | 2 +- 5 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js index fc84e53979..8c51763364 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbfiledropzone.directive.js @@ -148,6 +148,17 @@ angular.module("umbraco.directives") */ function _upload(file) { + if (!file) { + return; + } + + if (file.$error) { + file.done = true; + scope.processed.push(file); + file.messages.push({type: "Error", header: "Error"}); + return; + } + scope.propertyAlias = scope.propertyAlias ? scope.propertyAlias : "umbracoFile"; scope.contentTypeAlias = scope.contentTypeAlias ? scope.contentTypeAlias : "umbracoAutoSelect"; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js index 7de961bb34..eebdb7c223 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/upload/umbpropertyfileupload.directive.js @@ -7,8 +7,10 @@ * @param {any} fileManager * @param {any} mediaHelper * @param {any} angularHelper + * @param {any} $attrs + * @param {any} notificationsService */ - function umbPropertyFileUploadController($scope, $q, fileManager, mediaHelper, angularHelper, $attrs) { + function umbPropertyFileUploadController($scope, $q, fileManager, mediaHelper, angularHelper, $attrs, notificationsService) { //NOTE: this component supports multiple files, though currently the uploader does not but perhaps sometime in the future // we'd want it to, so i'll leave the multiple file support in place @@ -271,15 +273,25 @@ if (args.files && args.files.length > 0) { + const filesAllowed = []; + + for (let i = 0; i < args.files.length; i++) { + if (fileManager.maxFileSize && args.files[i].size > fileManager.maxFileSize) { + notificationsService.error(`File upload "${args.files[i].name}"`, `File size of ${args.files[i].size / 1000} KB exceeds the maximum allowed size of ${fileManager.maxFileSize / 1000} KB`); + } else { + filesAllowed.push(args.files[i]); + } + } + //set the files collection fileManager.setFiles({ propertyAlias: vm.propertyAlias, - files: args.files, + files: filesAllowed, culture: vm.culture, segment: vm.segment }); - updateModelFromSelectedFiles(args.files).then(function(newVal) { + updateModelFromSelectedFiles(filesAllowed).then(function(newVal) { angularHelper.safeApply($scope, function() { //pass in the file names and the model files diff --git a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js index 38aee3fc4a..6b2bd3c295 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/filemanager.service.js @@ -14,6 +14,17 @@ function fileManager($rootScope) { var mgr = { + /** + * @ngdoc property + * @name umbraco.services.fileManager#maxFileSize + * @propertyOf umbraco.services.fileManager + * @type {Number} + * @default 0 + * @description + * The max file size allowed to be uploaded to the server in bytes + */ + maxFileSize: parseInt(Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize ?? '0', 10) * 1000, + /** * @ngdoc function * @name umbraco.services.fileManager#setFiles diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 96b0681b8f..829b7d66a4 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -9,7 +9,7 @@ * @doc https://www.tiny.cloud/docs/tinymce/6/ */ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, stylesheetResource, macroResource, macroService, - $routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource, eventsService, localStorageService, mediaHelper) { + $routeParams, umbRequestHelper, angularHelper, userService, editorService, entityResource, eventsService, localStorageService, mediaHelper, fileManager) { //These are absolutely required in order for the macros to render inline //we put these as extended elements because they get merged on top of the normal allowed elements by tiny mce @@ -202,6 +202,17 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s function uploadImageHandler(blobInfo, progress) { return new Promise(function (resolve, reject) { + const blob = blobInfo.blob(); + + // if the file size is greater than the max file size, reject it + if (fileManager.maxFileSize > 0 && blob.size > fileManager.maxFileSize) { + reject({ + message: `The file size (${blob.size / 1000} KB) exceeded the maximum allowed size of ${fileManager.maxFileSize / 1000} KB.`, + remove: true + }); + return; + } + const xhr = new XMLHttpRequest(); xhr.open('POST', Umbraco.Sys.ServerVariables.umbracoUrls.tinyMceApiBaseUrl + 'UploadImage'); @@ -222,12 +233,18 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }; xhr.onerror = function () { - reject('Image upload failed due to a XHR Transport error. Code: ' + xhr.status); + reject({ + message: 'Image upload failed due to a XHR Transport error. Code: ' + xhr.status, + remove: true + }); }; xhr.onload = function () { if (xhr.status < 200 || xhr.status >= 300) { - reject('HTTP Error: ' + xhr.status); + reject({ + message: 'HTTP Error: ' + xhr.status, + remove: true + }); return; } @@ -237,7 +254,10 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s data = data.split("\n"); if (!data.length > 1) { - reject('Unrecognized text string: ' + data); + reject({ + message: 'Unrecognized text string: ' + data, + remove: true + }); return; } @@ -246,12 +266,18 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s try { json = JSON.parse(data[1]); } catch (e) { - reject('Invalid JSON: ' + data + ' - ' + e.message); + reject({ + message: 'Invalid JSON: ' + data + ' - ' + e.message, + remove: true + }); return; } if (!json || typeof json.tmpLocation !== 'string') { - reject('Invalid JSON: ' + data); + reject({ + message: 'Invalid JSON: ' + data, + remove: true + }); return; } @@ -265,7 +291,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s }; const formData = new FormData(); - formData.append('file', blobInfo.blob(), blobInfo.blob().name); + formData.append('file', blob, blob.name); xhr.send(formData); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html index c90f3139a8..985eec7e99 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/upload/umb-file-dropzone.html @@ -56,7 +56,7 @@
{{ file.name }} - {{message.header}}: {{message.message}} + {{::message.header}}: {{::message.message}} "{{maxFileSize}}" From eed9feb5f305f71e4da437ea31d4b81e1b799a60 Mon Sep 17 00:00:00 2001 From: Adrian Cojocariu <95346674+acoumb@users.noreply.github.com> Date: Mon, 24 Jul 2023 16:18:23 +0300 Subject: [PATCH 03/23] Fix method invoke. (#14597) --- .../PropertyEditors/GridPropertyIndexValueFactory.cs | 4 ++-- .../PropertyEditors/RichTextPropertyEditor.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs index a3bb55e643..7f6683fa59 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Text; @@ -91,6 +91,6 @@ namespace Umbraco.Cms.Core.PropertyEditors [Obsolete("Use the overload that specifies availableCultures, scheduled for removal in v14")] public IEnumerable>> GetIndexValues(IProperty property, string? culture, string? segment, bool published) - => GetIndexValues(property, culture, segment, published); + => GetIndexValues(property, culture, segment, published, Enumerable.Empty()); } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index c11498af7a..a3a1b31c34 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -328,6 +328,6 @@ public class RichTextPropertyEditor : DataEditor [Obsolete("Use the overload with the 'availableCultures' parameter instead, scheduled for removal in v14")] public IEnumerable>> GetIndexValues(IProperty property, string? culture, string? segment, bool published) - => GetIndexValues(property, culture, segment, published); + => GetIndexValues(property, culture, segment, published, Enumerable.Empty()); } } From 43700d2bcbdcd465e1994ba90156f888459b1272 Mon Sep 17 00:00:00 2001 From: Kenn Jacobsen Date: Tue, 25 Jul 2023 08:39:59 +0200 Subject: [PATCH 04/23] Ensure that the Slider does not crash the back-office (#14601) * Ensure that the Slider does not crash the back-office * Add field descriptions to config --- .../PropertyEditors/SliderConfiguration.cs | 6 +++--- .../PropertyEditors/SliderConfigurationEditor.cs | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs b/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs index 709fb3ce9f..4000af6b82 100644 --- a/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs @@ -14,12 +14,12 @@ public class SliderConfiguration [ConfigurationField("initVal2", "Initial value 2", "number", Description = "Used when range is enabled")] public decimal InitialValue2 { get; set; } - [ConfigurationField("minVal", "Minimum value", "number")] + [ConfigurationField("minVal", "Minimum value", "number", Description = "Must be smaller than the Maximum value")] public decimal MinimumValue { get; set; } - [ConfigurationField("maxVal", "Maximum value", "number")] + [ConfigurationField("maxVal", "Maximum value", "number", Description = "Must be larger than the Minimum value")] public decimal MaximumValue { get; set; } - [ConfigurationField("step", "Step increments", "number")] + [ConfigurationField("step", "Step increments", "number", Description = "Must be a positive value")] public decimal StepIncrements { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs index 586e4cd3af..deba35012e 100644 --- a/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs @@ -25,4 +25,19 @@ public class SliderConfigurationEditor : ConfigurationEditor ToConfigurationEditor(SliderConfiguration? configuration) + { + // negative step increments can be configured in the back-office. they will cause the slider to + // crash the entire back-office. as we can't configure min and max values for the number prevalue + // editor, we have to this instead to limit the damage. + // logically, the step increments should be inverted instead of hardcoding them to 1, but the + // latter might point people in the direction of their misconfiguration. + if (configuration?.StepIncrements <= 0) + { + configuration.StepIncrements = 1; + } + + return base.ToConfigurationEditor(configuration); + } } From c8206cd7d7a685b7315d71a913bd1d3e00e8d403 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 25 Jul 2023 10:18:49 +0200 Subject: [PATCH 05/23] V11/merge v10 into v11 (#14602) * V10: Dropzone should handle internal and external errors when uploading (#14578) * fix: mark files that result in error as processed * fix: for safety measure check that a file is truthy before trying to upload it * fix: push an error when file.$error is encountered to make sure it does not get uploaded * fix: remove header from error messages since it is not being used anyway * fix: check for maxFileSize before uploading pasted images in tinymce * use stored blob variable * feat: add property to fileManager to get and format the maxFileSize * fix: make tinymce use fileManager to get maxFileSize * fix(image cropper): check for maxFileSize before setting file to upload * multiply by 1000 to get bytes --------- Co-authored-by: Elitsa * Fix method invoke. (#14597) * Ensure that the Slider does not crash the back-office (#14601) * Ensure that the Slider does not crash the back-office * Add field descriptions to config --------- Co-authored-by: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Co-authored-by: Elitsa Co-authored-by: Adrian Cojocariu <95346674+acoumb@users.noreply.github.com> Co-authored-by: Kenn Jacobsen --- .../PropertyEditors/SliderConfiguration.cs | 6 +++--- .../PropertyEditors/SliderConfigurationEditor.cs | 15 +++++++++++++++ .../GridPropertyIndexValueFactory.cs | 4 ++-- .../PropertyEditors/RichTextPropertyEditor.cs | 2 +- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs b/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs index 709fb3ce9f..4000af6b82 100644 --- a/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs +++ b/src/Umbraco.Core/PropertyEditors/SliderConfiguration.cs @@ -14,12 +14,12 @@ public class SliderConfiguration [ConfigurationField("initVal2", "Initial value 2", "number", Description = "Used when range is enabled")] public decimal InitialValue2 { get; set; } - [ConfigurationField("minVal", "Minimum value", "number")] + [ConfigurationField("minVal", "Minimum value", "number", Description = "Must be smaller than the Maximum value")] public decimal MinimumValue { get; set; } - [ConfigurationField("maxVal", "Maximum value", "number")] + [ConfigurationField("maxVal", "Maximum value", "number", Description = "Must be larger than the Minimum value")] public decimal MaximumValue { get; set; } - [ConfigurationField("step", "Step increments", "number")] + [ConfigurationField("step", "Step increments", "number", Description = "Must be a positive value")] public decimal StepIncrements { get; set; } } diff --git a/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs b/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs index 915e92d709..fbf45a9c41 100644 --- a/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs +++ b/src/Umbraco.Core/PropertyEditors/SliderConfigurationEditor.cs @@ -25,4 +25,19 @@ public class SliderConfigurationEditor : ConfigurationEditor ToConfigurationEditor(SliderConfiguration? configuration) + { + // negative step increments can be configured in the back-office. they will cause the slider to + // crash the entire back-office. as we can't configure min and max values for the number prevalue + // editor, we have to this instead to limit the damage. + // logically, the step increments should be inverted instead of hardcoding them to 1, but the + // latter might point people in the direction of their misconfiguration. + if (configuration?.StepIncrements <= 0) + { + configuration.StepIncrements = 1; + } + + return base.ToConfigurationEditor(configuration); + } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs index cc3c912f4a..c190a7b6a8 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/GridPropertyIndexValueFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) Umbraco. +// Copyright (c) Umbraco. // See LICENSE for more details. using System.Text; @@ -92,6 +92,6 @@ namespace Umbraco.Cms.Core.PropertyEditors [Obsolete("Use the overload that specifies availableCultures, scheduled for removal in v14")] public IEnumerable>> GetIndexValues(IProperty property, string? culture, string? segment, bool published) - => GetIndexValues(property, culture, segment, published); + => GetIndexValues(property, culture, segment, published, Enumerable.Empty()); } } diff --git a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs index 8f59681afc..053e98d9cb 100644 --- a/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs +++ b/src/Umbraco.Infrastructure/PropertyEditors/RichTextPropertyEditor.cs @@ -328,6 +328,6 @@ public class RichTextPropertyEditor : DataEditor [Obsolete("Use the overload with the 'availableCultures' parameter instead, scheduled for removal in v14")] public IEnumerable>> GetIndexValues(IProperty property, string? culture, string? segment, bool published) - => GetIndexValues(property, culture, segment, published); + => GetIndexValues(property, culture, segment, published, Enumerable.Empty()); } } From c11d0d3f5ae9b2e17274c553a82eec629611c42b Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 1 Aug 2023 14:23:30 +0200 Subject: [PATCH 06/23] Added missing async awaits in notifications that ensures objects are not disposed too early Fixes https://github.com/umbraco/Umbraco-CMS/issues/14574 --- src/Umbraco.Core/Events/EventAggregator.Notifications.cs | 4 ++-- src/Umbraco.Core/Events/EventAggregator.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs index d65c8f772c..5676af3f3c 100644 --- a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs +++ b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs @@ -238,7 +238,7 @@ internal class NotificationAsyncHandlerWrapperImpl : Notifica /// confusion. /// /// - public override Task HandleAsync( + public override async Task HandleAsync( IEnumerable notifications, CancellationToken cancellationToken, ServiceFactory serviceFactory, @@ -256,7 +256,7 @@ internal class NotificationAsyncHandlerWrapperImpl : Notifica .Select(x => new Func, CancellationToken, Task>( (handlerNotifications, handlerCancellationToken) => x.HandleAsync(handlerNotifications.Cast(), handlerCancellationToken))); - return publish(handlers, notifications, cancellationToken); + await publish(handlers, notifications, cancellationToken); } } diff --git a/src/Umbraco.Core/Events/EventAggregator.cs b/src/Umbraco.Core/Events/EventAggregator.cs index caaa160146..9861b1a318 100644 --- a/src/Umbraco.Core/Events/EventAggregator.cs +++ b/src/Umbraco.Core/Events/EventAggregator.cs @@ -42,13 +42,13 @@ public partial class EventAggregator : IEventAggregator } /// - public Task PublishAsync(IEnumerable notifications, CancellationToken cancellationToken = default) + public async Task PublishAsync(IEnumerable notifications, CancellationToken cancellationToken = default) where TNotification : INotification where TNotificationHandler : INotificationHandler { PublishNotifications(notifications); - return PublishNotificationsAsync(notifications, cancellationToken); + await PublishNotificationsAsync(notifications, cancellationToken); } /// From bc127d95bab195702a630bd1cd3f18fc6a13ed1a Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Tue, 1 Aug 2023 16:11:44 +0200 Subject: [PATCH 07/23] Fixed note on #13145 - don't make IncludeDescendants settable --- src/Umbraco.Core/Notifications/ContentPublishedNotification.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs b/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs index 7c82561785..58bb76280b 100644 --- a/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs +++ b/src/Umbraco.Core/Notifications/ContentPublishedNotification.cs @@ -27,5 +27,5 @@ public sealed class ContentPublishedNotification : EnumerableObjectNotification< /// public IEnumerable PublishedEntities => Target; - public bool IncludeDescendants { get; set; } + public bool IncludeDescendants { get; } } From 8ce48a2634b7d9128a1e5c5630a95c16279bbc90 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 2 Aug 2023 10:01:20 +0200 Subject: [PATCH 08/23] post merge fix --- .../src/common/services/tinymce.service.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index a18d218f10..82eadd68b5 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -213,14 +213,6 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s return; } - const blob = blobInfo.blob(); - - // if the file size is greater than the max file size, reject it - if (fileManager.maxFileSize > 0 && blob.size > fileManager.maxFileSize) { - failure(`The file size (${blob.size / 1000} KB) exceeded the maximum allowed size of ${fileManager.maxFileSize / 1000} KB.`); - return; - } - const xhr = new XMLHttpRequest(); xhr.open('POST', Umbraco.Sys.ServerVariables.umbracoUrls.tinyMceApiBaseUrl + 'UploadImage'); From e48350e064d1f8f606712e74d30183d4f160fb32 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 1 Aug 2023 14:23:30 +0200 Subject: [PATCH 09/23] Added missing async awaits in notifications that ensures objects are not disposed too early Fixes https://github.com/umbraco/Umbraco-CMS/issues/14574 --- src/Umbraco.Core/Events/EventAggregator.Notifications.cs | 4 ++-- src/Umbraco.Core/Events/EventAggregator.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs index d65c8f772c..5676af3f3c 100644 --- a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs +++ b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs @@ -238,7 +238,7 @@ internal class NotificationAsyncHandlerWrapperImpl : Notifica /// confusion. /// /// - public override Task HandleAsync( + public override async Task HandleAsync( IEnumerable notifications, CancellationToken cancellationToken, ServiceFactory serviceFactory, @@ -256,7 +256,7 @@ internal class NotificationAsyncHandlerWrapperImpl : Notifica .Select(x => new Func, CancellationToken, Task>( (handlerNotifications, handlerCancellationToken) => x.HandleAsync(handlerNotifications.Cast(), handlerCancellationToken))); - return publish(handlers, notifications, cancellationToken); + await publish(handlers, notifications, cancellationToken); } } diff --git a/src/Umbraco.Core/Events/EventAggregator.cs b/src/Umbraco.Core/Events/EventAggregator.cs index caaa160146..9861b1a318 100644 --- a/src/Umbraco.Core/Events/EventAggregator.cs +++ b/src/Umbraco.Core/Events/EventAggregator.cs @@ -42,13 +42,13 @@ public partial class EventAggregator : IEventAggregator } /// - public Task PublishAsync(IEnumerable notifications, CancellationToken cancellationToken = default) + public async Task PublishAsync(IEnumerable notifications, CancellationToken cancellationToken = default) where TNotification : INotification where TNotificationHandler : INotificationHandler { PublishNotifications(notifications); - return PublishNotificationsAsync(notifications, cancellationToken); + await PublishNotificationsAsync(notifications, cancellationToken); } /// From 04b74dddd0d61889064a3fdfd632554fb46350f4 Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Tue, 1 Aug 2023 14:23:30 +0200 Subject: [PATCH 10/23] Added missing async awaits in notifications that ensures objects are not disposed too early Fixes https://github.com/umbraco/Umbraco-CMS/issues/14574 --- src/Umbraco.Core/Events/EventAggregator.Notifications.cs | 4 ++-- src/Umbraco.Core/Events/EventAggregator.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs index d298f5bbec..94d4804cd4 100644 --- a/src/Umbraco.Core/Events/EventAggregator.Notifications.cs +++ b/src/Umbraco.Core/Events/EventAggregator.Notifications.cs @@ -137,7 +137,7 @@ internal class NotificationAsyncHandlerWrapperImpl : Notification /// confusion. /// /// - public override Task HandleAsync( + public override async Task HandleAsync( INotification notification, CancellationToken cancellationToken, ServiceFactory serviceFactory, @@ -155,7 +155,7 @@ internal class NotificationAsyncHandlerWrapperImpl : Notification (theNotification, theToken) => x.HandleAsync((TNotification)theNotification, theToken))); - return publish(handlers, notification, cancellationToken); + await publish(handlers, notification, cancellationToken); } } diff --git a/src/Umbraco.Core/Events/EventAggregator.cs b/src/Umbraco.Core/Events/EventAggregator.cs index 277b24eb06..b5de38c66d 100644 --- a/src/Umbraco.Core/Events/EventAggregator.cs +++ b/src/Umbraco.Core/Events/EventAggregator.cs @@ -50,7 +50,7 @@ public partial class EventAggregator : IEventAggregator => _serviceFactory = serviceFactory; /// - public Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default) + public async Task PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification { // TODO: Introduce codegen efficient Guard classes to reduce noise. @@ -60,7 +60,7 @@ public partial class EventAggregator : IEventAggregator } PublishNotification(notification); - return PublishNotificationAsync(notification, cancellationToken); + await PublishNotificationAsync(notification, cancellationToken); } /// From 0d82258803ea15c6cd6252836c9e47beb356827e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 2 Aug 2023 12:39:06 +0200 Subject: [PATCH 11/23] Allow newlines in invite email and escape other html (#14618) --- src/Umbraco.Web.BackOffice/Controllers/UsersController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 3e14154b48..46e06dec6e 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -607,7 +607,7 @@ public class UsersController : BackOfficeNotificationsController var emailBody = _localizedTextService.Localize("user", "inviteEmailCopyFormat", // Ensure the culture of the found user is used for the email! UmbracoUserExtensions.GetUserCulture(to?.Language, _localizedTextService, _globalSettings), - new[] { userDisplay?.Name, from, message, inviteUri.ToString(), senderEmail }); + new[] { userDisplay?.Name, from, WebUtility.HtmlEncode(message)!.ReplaceLineEndings("
"), inviteUri.ToString(), senderEmail }); // This needs to be in the correct mailto format including the name, else // the name cannot be captured in the email sending notification. From f384e0c30c66e0987e76a1086886077e28affb6e Mon Sep 17 00:00:00 2001 From: Bjarke Berg Date: Wed, 2 Aug 2023 12:39:06 +0200 Subject: [PATCH 12/23] Allow newlines in invite email and escape other html (#14618) --- src/Umbraco.Web.BackOffice/Controllers/UsersController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs index 04e4c64645..1a8be10a5c 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/UsersController.cs @@ -597,7 +597,7 @@ public class UsersController : BackOfficeNotificationsController var emailBody = _localizedTextService.Localize("user", "inviteEmailCopyFormat", // Ensure the culture of the found user is used for the email! UmbracoUserExtensions.GetUserCulture(to?.Language, _localizedTextService, _globalSettings), - new[] { userDisplay?.Name, from, message, inviteUri.ToString(), senderEmail }); + new[] { userDisplay?.Name, from, WebUtility.HtmlEncode(message)!.ReplaceLineEndings("
"), inviteUri.ToString(), senderEmail }); // This needs to be in the correct mailto format including the name, else // the name cannot be captured in the email sending notification. From 772fa5e23b0bddcead4db035275d0950b280f6cb Mon Sep 17 00:00:00 2001 From: Sebastiaan Janssen Date: Wed, 2 Aug 2023 15:13:22 +0200 Subject: [PATCH 13/23] Revert "Fix for escaping markdown characters in property descriptions (#12345)" This reverts commit 4aa15971380060f37fcc3ab02ace5efce54ee77e. --- .../Mapping/ContentMapDefinition.cs | 49 +----- .../Umbraco.Web.BackOffice.csproj | 3 +- .../property/umbproperty.directive.js | 162 +++++++++--------- .../common/filters/simpleMarkdown.filter.js | 20 +++ .../filters/simpleMarkdown.filter.js.js | 20 +++ .../components/property/umb-property.html | 4 +- 6 files changed, 125 insertions(+), 133 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js create mode 100644 src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js.js diff --git a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs index 21961acb45..0bc4e9c1b7 100644 --- a/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs +++ b/src/Umbraco.Web.BackOffice/Mapping/ContentMapDefinition.cs @@ -1,6 +1,4 @@ using System.Globalization; -using System.Text.RegularExpressions; -using HeyRed.MarkdownSharp; using Microsoft.Extensions.Logging; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Actions; @@ -307,51 +305,6 @@ internal class ContentMapDefinition : IMapDefinition { Properties = context.MapEnumerable(source.Properties).WhereNotNull() }; - var markdown = new Markdown(); - var linkCheck = new Regex("]+>", RegexOptions.IgnoreCase); - var evaluator = new MatchEvaluator(AddRelNoReferrer); - foreach (TVariant variant in target.Variants) - { - foreach (Tab tab in variant.Tabs) - { - if (tab.Properties == null) - { - continue; - } - - foreach (ContentPropertyDisplay property in tab.Properties) - { - if (string.IsNullOrEmpty(property.Description)) - { - continue; - } - - var description = markdown.Transform(property.Description); - property.Description = linkCheck.Replace(description, evaluator); - } - } - } - } - - private string AddRelNoReferrer(Match m) - { - string result = m.Value; - if (!result.Contains("rel=", StringComparison.Ordinal)) - { - result = result.Replace(">", " rel=\"noreferrer\">"); - } - - if (!result.Contains("class=", StringComparison.Ordinal)) - { - result = result.Replace(">", " class=\"underline\">"); - } - - if (!result.Contains("target=", StringComparison.Ordinal)) - { - result = result.Replace(">", " target=\"_blank\">"); - } - - return result; } // Umbraco.Code.MapAll -Segment -Language -DisplayName -AdditionalPreviewUrls @@ -408,7 +361,7 @@ internal class ContentMapDefinition : IMapDefinition { currentUser = currentIUserBackofficeUser; } - else if (_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser is not null) + else if(_backOfficeSecurityAccessor.BackOfficeSecurity?.CurrentUser is not null) { currentUser = _backOfficeSecurityAccessor.BackOfficeSecurity.CurrentUser; } diff --git a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj index 0fe0a3784f..a64d0d2408 100644 --- a/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj +++ b/src/Umbraco.Web.BackOffice/Umbraco.Web.BackOffice.csproj @@ -11,7 +11,6 @@ - @@ -32,4 +31,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js index ccc9f92caf..5a30f81d4b 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/property/umbproperty.directive.js @@ -4,96 +4,96 @@ * @restrict E **/ (function () { - 'use strict'; + 'use strict'; - angular - .module("umbraco.directives") - .component('umbProperty', { - templateUrl: 'views/components/property/umb-property.html', - controller: UmbPropertyController, - controllerAs: 'vm', - transclude: true, - require: { - parentUmbProperty: '?^^umbProperty', - parentForm: '?^^form' - }, - bindings: { - property: "=", + angular + .module("umbraco.directives") + .component('umbProperty', { + templateUrl: 'views/components/property/umb-property.html', + controller: UmbPropertyController, + controllerAs: 'vm', + transclude: true, + require: { + parentUmbProperty: '?^^umbProperty', + parentForm: '?^^form' + }, + bindings: { + property: "=", node: "<", - elementKey: "@", - // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) - propertyAlias: "@", - showInherit: "<", + elementKey: "@", + // optional, if set this will be used for the property alias validation path (hack required because NC changes the actual property.alias :/) + propertyAlias: "@", + showInherit: "<", inheritsFrom: "<", hideLabel: " s && s.vm && s.vm.constructor.name === "UmbPropertyController"); - vm.parentUmbProperty = found ? found.vm : null; - } + - if (vm.property.description) { - // split by lines containing only '--' - var descriptionParts = vm.property.description.split(/^--$/gim); - if (descriptionParts.length > 1) { - // if more than one part, we have an extended description, - // combine to one extended description, and remove leading linebreak - vm.property.extendedDescription = descriptionParts.splice(1).join("--").substring(1); - vm.property.extendedDescriptionVisible = false; + function UmbPropertyController($scope, userService, serverValidationManager, udiService, angularHelper) { - // set propertydescription to first part, and remove trailing linebreak - vm.property.description = descriptionParts[0].slice(0, -1); + const vm = this; + + vm.$onInit = onInit; + + vm.setDirty = function () { + // NOTE: We need to use scope because we haven't changd it to vm.propertyForm in the html and that + // might mean a breaking change. + $scope.propertyForm.$setDirty(); } - } - } - } + vm.setPropertyError = function (errorMsg) { + vm.property.propertyErrorMessage = errorMsg; + }; + + vm.propertyActions = []; + vm.setPropertyActions = function (actions) { + vm.propertyActions = actions; + }; + + // returns the validation path for the property to be used as the validation key for server side validation logic + vm.getValidationPath = function () { + + var parentValidationPath = vm.parentUmbProperty ? vm.parentUmbProperty.getValidationPath() : null; + var propAlias = vm.propertyAlias ? vm.propertyAlias : vm.property.alias; + // the elementKey will be empty when this is not a nested property + var valPath = vm.elementKey ? vm.elementKey + "/" + propAlias : propAlias; + return serverValidationManager.createPropertyValidationKey(valPath, parentValidationPath); + } + + function onInit() { + vm.controlLabelTitle = null; + if (Umbraco.Sys.ServerVariables.isDebuggingEnabled) { + userService.getCurrentUser().then(function (u) { + if (u.allowedSections.indexOf("settings") !== -1 ? true : false) { + vm.controlLabelTitle = vm.property.alias; + } + }); + } + + if (!vm.parentUmbProperty) { + // not found, then fallback to searching the scope chain, this may be needed when DOM inheritance isn't maintained but scope + // inheritance is (i.e.infinite editing) + var found = angularHelper.traverseScopeChain($scope, s => s && s.vm && s.vm.constructor.name === "UmbPropertyController"); + vm.parentUmbProperty = found ? found.vm : null; + } + + if (vm.property.description) { + // split by lines containing only '--' + var descriptionParts = vm.property.description.split(/^--$/gim); + if (descriptionParts.length > 1) { + // if more than one part, we have an extended description, + // combine to one extended description, and remove leading linebreak + vm.property.extendedDescription = descriptionParts.splice(1).join("--").substring(1); + vm.property.extendedDescriptionVisible = false; + + // set propertydescription to first part, and remove trailing linebreak + vm.property.description = descriptionParts[0].slice(0, -1); + } + } + } + + } })(); diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js b/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js new file mode 100644 index 0000000000..58d5b0ed91 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js @@ -0,0 +1,20 @@ +/** +* @ngdoc filter +* @name umbraco.filters.simpleMarkdown +* @description +* Used when rendering a string as Markdown as HTML (i.e. with ng-bind-html). Allows use of **bold**, *italics*, ![images](url) and [links](url) +**/ +angular.module("umbraco.filters").filter('simpleMarkdown', function () { + return function (text) { + if (!text) { + return ''; + } + + return text + .replace(/\*\*(.*)\*\*/gim, '$1') + .replace(/\*(.*)\*/gim, '$1') + .replace(/!\[(.*?)\]\((.*?)\)/gim, "$1") + .replace(/\[(.*?)\]\((.*?)\)/gim, "$1") + .replace(/\n/g, '
').trim(); + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js.js b/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js.js new file mode 100644 index 0000000000..d33b96916a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/filters/simpleMarkdown.filter.js.js @@ -0,0 +1,20 @@ +/** +* @ngdoc filter +* @name umbraco.filters.simpleMarkdown +* @description +* Used when rendering a string as Markdown as HTML (i.e. with ng-bind-html). Allows use of **bold**, *italics*, ![images](url) and [links](url) +**/ +angular.module("umbraco.filters").filter('simpleMarkdown', function () { + return function (text) { + if (!text) { + return ''; + } + + return text + .replace(/\*\*(.*)\*\*/gim, '$1') + .replace(/\*(.*)\*/gim, '$1') + .replace(/!\[(.*?)\]\((.*?)\)/gim, "$1") + .replace(/\[(.*?)\]\((.*?)\)/gim, "$1") + .replace(/\n/g, '
').trim(); + }; +}); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 2c017afdba..cc41896a0f 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -13,12 +13,12 @@ - +
- +