Merge pull request #6325 from umbraco/v8/feature/2438AB-RTE-Image-Config-MediaFolder

V8: AB 2438 - RTE Pasted/Dragged Images Media Folder Config
This commit is contained in:
Bjarke Berg
2019-09-17 11:16:26 +02:00
committed by GitHub
14 changed files with 196 additions and 31 deletions

View File

@@ -118,9 +118,11 @@ Use this directive to generate a thumbnail grid of media items.
var item = scope.items[i];
setItemData(item);
setOriginalSize(item, itemMaxHeight);
item.selectable = getSelectableState(item);
// remove non images when onlyImages is set to true
if(scope.onlyImages === "true" && !item.isFolder && !item.thumbnail){
if( scope.onlyImages === "true" && !item.isFolder && !item.thumbnail){
scope.items.splice(i, 1);
i--;
}
@@ -188,6 +190,22 @@ Use this directive to generate a thumbnail grid of media items.
}
}
}
/**
* Returns wether a item should be selectable or not.
*/
function getSelectableState(item) {
// check if item is a folder or image
if (item.isFolder === true) {
return scope.disableFolderSelect !== "true" && scope.onlyImages !== "true";
} else {
return scope.onlyFolders !== "true";
}
return false;
}
function setOriginalSize(item, maxHeight) {
@@ -331,7 +349,9 @@ Use this directive to generate a thumbnail grid of media items.
itemMaxHeight: "@",
itemMinWidth: "@",
itemMinHeight: "@",
disableFolderSelect: "@",
onlyImages: "@",
onlyFolders: "@",
includeSubFolders: "@",
currentFolderId: "@"
},

View File

@@ -21,12 +21,29 @@
margin: 10px;
position: relative;
user-select: none;
cursor: pointer;
user-select: none;
box-shadow: 0 1px 1px 0 rgba(0,0,0,.2);
transition: box-shadow 150ms ease-in-out;
}
.umb-media-grid__item.-unselectable {
&::before {
content: "";
position: absolute;
z-index: 1;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(230, 230, 230, .5);
pointer-events: none;
}
}
.umb-media-grid__item.-selectable {
cursor: pointer;
}
.umb-media-grid__item.-file {
background-color: @white;
}
@@ -37,7 +54,8 @@
color: @ui-selected-type;
}
}
.umb-media-grid__item.-selected, .umb-media-grid__item:hover {
.umb-media-grid__item.-selected,
.umb-media-grid__item.-selectable:hover {
&::before {
content: "";
position: absolute;
@@ -52,7 +70,7 @@
pointer-events: none;
}
}
.umb-media-grid__item:hover {
.umb-media-grid__item.-selectable:hover {
&::before {
opacity: .33;
}
@@ -159,6 +177,10 @@
}
}
.umb-media-grid__item-name {
cursor: pointer;
}
.umb-media-grid__item-name {
white-space: nowrap;
overflow: hidden;

View File

@@ -2,7 +2,8 @@
angular.module("umbraco")
.controller("Umbraco.Editors.MediaPickerController",
function ($scope, mediaResource, entityResource, userService, mediaHelper, mediaTypeHelper, eventsService, treeService, localStorageService, localizationService, editorService) {
if (!$scope.model.title) {
localizationService.localizeMany(["defaultdialogs_selectMedia", "general_includeFromsubFolders"])
.then(function (data) {
@@ -14,10 +15,11 @@ angular.module("umbraco")
}
var dialogOptions = $scope.model;
$scope.disableFolderSelect = dialogOptions.disableFolderSelect;
$scope.onlyImages = dialogOptions.onlyImages;
$scope.showDetails = dialogOptions.showDetails;
$scope.disableFolderSelect = (dialogOptions.disableFolderSelect && dialogOptions.disableFolderSelect !== "0") ? true : false;
$scope.onlyImages = (dialogOptions.onlyImages && dialogOptions.onlyImages !== "0") ? true : false;
$scope.onlyFolders = (dialogOptions.onlyFolders && dialogOptions.onlyFolders !== "0") ? true : false;
$scope.showDetails = (dialogOptions.showDetails && dialogOptions.showDetails !== "0") ? true : false;
$scope.multiPicker = (dialogOptions.multiPicker && dialogOptions.multiPicker !== "0") ? true : false;
$scope.startNodeId = dialogOptions.startNodeId ? dialogOptions.startNodeId : -1;
$scope.cropSize = dialogOptions.cropSize;
@@ -188,26 +190,25 @@ angular.module("umbraco")
};
$scope.clickHandler = function (image, event, index) {
if (image.isFolder) {
if ($scope.disableFolderSelect) {
$scope.gotoFolder(image);
} else {
eventsService.emit("dialogs.mediaPicker.select", image);
selectImage(image);
}
} else {
eventsService.emit("dialogs.mediaPicker.select", image);
if ($scope.showDetails) {
$scope.target = image;
// handle both entity and full media object
if (image.image) {
$scope.target.url = image.image;
} else {
$scope.target.url = mediaHelper.resolveFile(image);
}
$scope.openDetailsDialog();
} else {
selectImage(image);
@@ -222,6 +223,9 @@ angular.module("umbraco")
};
function selectImage(image) {
if(!image.selectable) {
return;
}
if (image.selected) {
for (var i = 0; $scope.model.selection.length > i; i++) {
var imageInSelection = $scope.model.selection[i];
@@ -234,6 +238,7 @@ angular.module("umbraco")
if (!$scope.multiPicker) {
deselectAllImages($scope.model.selection);
}
eventsService.emit("dialogs.mediaPicker.select", image);
image.selected = true;
$scope.model.selection.push(image);
}

View File

@@ -98,7 +98,9 @@
item-max-height="150"
item-min-width="100"
item-min-height="100"
disable-folder-select={{disableFolderSelect}}
only-images={{onlyImages}}
only-folders={{onlyFolders}}
include-sub-folders={{showChilds}}
current-Folder-id="{{currentFolder.id}}">
</umb-media-grid>

View File

@@ -1,5 +1,5 @@
<div data-element="media-grid" class="umb-media-grid">
<div data-element="media-grid-item-{{$index}}" class="umb-media-grid__item" title="{{item.name}}" ng-click="clickItem(item, $event, $index)" ng-repeat="item in items | filter:filterBy" ng-style="item.flexStyle" ng-class="{'-selected': item.selected, '-file': !item.thumbnail, '-svg': item.extension == 'svg'}">
<div data-element="media-grid-item-{{$index}}" class="umb-media-grid__item" title="{{item.name}}" ng-click="clickItem(item, $event, $index)" ng-repeat="item in items | filter:filterBy" ng-style="item.flexStyle" ng-class="{'-selected': item.selected, '-file': !item.thumbnail, '-svg': item.extension == 'svg', '-selectable': item.selectable, '-unselectable': !item.selectable}">
<div>
<!--<i ng-show="item.selected" class="icon-check umb-media-grid__checkmark"></i>-->
<a ng-if="allowOnClickEdit === 'true'" ng-click="clickEdit(item, $event)" ng-href="" class="icon-edit umb-media-grid__edit"></a>

View File

@@ -0,0 +1,56 @@
function mediaFolderPickerController($scope, editorService, entityResource) {
$scope.folderName = "";
function retriveFolderData() {
var id = $scope.model.value;
if (id == null) {
$scope.folderName = "";
return;
}
entityResource.getById(id, "Media").then(
function (media) {
$scope.media = media;
}
);
}
retriveFolderData();
$scope.add = function() {
var mediaPickerOptions = {
view: "mediapicker",
multiPicker: true,
disableFolderSelect: false,
onlyImages: false,
onlyFolders: true,
submit: function (model) {
$scope.model.value = model.selection[0].udi;
retriveFolderData();
editorService.close();
},
close: function () {
editorService.close();
}
};
editorService.mediaPicker(mediaPickerOptions);
};
$scope.remove = function () {
$scope.model.value = null;
retriveFolderData();
};
}
angular.module('umbraco').controller("Umbraco.PrevalueEditors.MediaFolderPickerController", mediaFolderPickerController);

View File

@@ -0,0 +1,37 @@
<div ng-controller="Umbraco.PrevalueEditors.MediaFolderPickerController" class="umb-property-editor umb-mediapicker umb-mediapicker-single">
<div class="flex flex-wrap error">
<ul class="umb-sortable-thumbnails">
<li ng-if="model.value" class="umb-sortable-thumbnails__wrapper">
<p class="label label__trashed" ng-if="media.trashed">
<localize key="mediaPicker_trashed"></localize>
<i class="icon-trash" aria-hidden="true"></i>
</p>
<!-- FILE -->
<div ng-class="{'trashed': media.trashed}" class="umb-icon-holder" ng-hide="media.thumbnail">
<span class="file-icon">
<i class="icon {{media.icon}}"></i>
<span ng-if="media.extension">.{{media.extension}}</span>
</span>
<small>{{media.name}}</small>
</div>
<div class="umb-sortable-thumbnails__actions" data-element="sortable-thumbnail-actions">
<a class="umb-sortable-thumbnails__action -red" data-element="action-remove" href="" ng-click="remove()">
<i class="icon icon-delete"></i>
</a>
</div>
</li>
<li style="border: none;" class="add-wrapper unsortable" ng-hide="model.value">
<a data-element="sortable-thumbnails-add" href="#" class="add-link add-link-square" ng-click="add()" prevent-default>
<i class="icon icon-add large"></i>
</a>
</li>
</ul>
</div>
</div>

View File

@@ -6,7 +6,7 @@
<umb-checkbox model="cmd.selected"
on-change="selectCommand(cmd)"
class="mce-cmd" />
<!--<img ng-src="{{cmd.icon}}" />-->
<i class="mce-ico" ng-class="(cmd.isCustom ? ' mce-i-custom ' : ' mce-i-') + cmd.fontIcon"></i>
@@ -24,7 +24,6 @@
text="{{css.name}}"/>
</div>
</umb-control-group>
</umb-control-group>
<umb-control-group label="Dimensions" description="Set the editor dimensions">
<div class="vertical-align-items">

View File

@@ -106,7 +106,7 @@ namespace Umbraco.Web.Editors
// Now remove all old files so that the temp folder(s) never grow
// Anything older than one day gets deleted
var tempFiles = Directory.GetFiles(SystemDirectories.TempFileUploads, "*", SearchOption.AllDirectories);
var tempFiles = Directory.GetFiles(IOHelper.MapPath(SystemDirectories.TempFileUploads), "*", SearchOption.AllDirectories);
foreach (var tempFile in tempFiles)
{
if (DateTime.UtcNow - File.GetLastWriteTimeUtc(tempFile) > TimeSpan.FromDays(1))

View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
@@ -20,5 +21,9 @@ namespace Umbraco.Web.PropertyEditors
"Ignore User Start Nodes", "boolean",
Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
public bool IgnoreUserStartNodes { get; set; }
[ConfigurationField("mediaParentId", "Image Upload Folder", "MediaFolderPicker",
Description = "Choose the upload location of pasted images")]
public GuidUdi MediaParentId { get; set; }
}
}

View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System;
using System.Linq;
using Umbraco.Core;
using Umbraco.Core.Logging;
@@ -76,6 +77,10 @@ namespace Umbraco.Web.PropertyEditors
if (editorValue.Value == null)
return null;
var config = editorValue.DataTypeConfiguration as GridConfiguration;
var mediaParent = config?.MediaParentId;
var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid;
// editorValue.Value is a JSON string of the grid
var rawJson = editorValue.Value.ToString();
var grid = JsonConvert.DeserializeObject<GridValue>(rawJson);
@@ -89,10 +94,9 @@ namespace Umbraco.Web.PropertyEditors
// Parse the HTML
var html = rte.Value?.ToString();
var userId = _umbracoContextAccessor.UmbracoContext?.Security.CurrentUser.Id ?? -1;
var userId = _umbracoContextAccessor.UmbracoContext?.Security.CurrentUser.Id ?? Constants.Security.SuperUserId;
// TODO: In future task(get the parent folder from this config) to save the media into
var parsedHtml = TemplateUtilities.FindAndPersistPastedTempImages(html, Constants.System.Root, userId, _mediaService, _contentTypeBaseServiceProvider, _logger);
var parsedHtml = TemplateUtilities.FindAndPersistPastedTempImages(html, mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger);
rte.Value = parsedHtml;
}

View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json.Linq;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
@@ -19,5 +20,9 @@ namespace Umbraco.Web.PropertyEditors
"Ignore User Start Nodes", "boolean",
Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
public bool IgnoreUserStartNodes { get; set; }
[ConfigurationField("mediaParentId", "Image Upload Folder", "MediaFolderPicker",
Description = "Choose the upload location of pasted images")]
public GuidUdi MediaParentId { get; set; }
}
}

View File

@@ -117,10 +117,13 @@ namespace Umbraco.Web.PropertyEditors
var editorValueWithMediaUrlsRemoved = TemplateUtilities.RemoveMediaUrlsFromTextString(editorValue.Value.ToString());
var parsed = MacroTagParser.FormatRichTextContentForPersistence(editorValueWithMediaUrlsRemoved);
var userId = _umbracoContextAccessor.UmbracoContext?.Security.CurrentUser.Id ?? -1;
var userId = _umbracoContextAccessor.UmbracoContext?.Security.CurrentUser.Id ?? Constants.Security.SuperUserId;
// TODO: In future task(get the parent folder from this config) to save the media into
parsed = TemplateUtilities.FindAndPersistPastedTempImages(parsed, Constants.System.Root, userId, _mediaService, _contentTypeBaseServiceProvider, _logger);
var config = editorValue.DataTypeConfiguration as RichTextConfiguration;
var mediaParent = config?.MediaParentId;
var mediaParentId = mediaParent == null ? Guid.Empty : mediaParent.Guid;
parsed = TemplateUtilities.FindAndPersistPastedTempImages(parsed, mediaParentId, userId, _mediaService, _contentTypeBaseServiceProvider, _logger);
return parsed;
}
}

View File

@@ -5,10 +5,12 @@ using System.Text.RegularExpressions;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Composing;
using Umbraco.Web.PublishedCache;
using Umbraco.Web.Routing;
using File = System.IO.File;
namespace Umbraco.Web.Templates
{
@@ -189,7 +191,7 @@ namespace Umbraco.Web.Templates
// see comment in ResolveMediaFromTextString for group reference
=> ResolveImgPattern.Replace(text, "$1$3$4$5");
internal static string FindAndPersistPastedTempImages(string html, int mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ILogger logger)
internal static string FindAndPersistPastedTempImages(string html, Guid mediaParentFolder, int userId, IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider, ILogger logger)
{
// Find all img's that has data-tmpimg attribute
// Use HTML Agility Pack - https://html-agility-pack.net
@@ -213,7 +215,13 @@ namespace Umbraco.Web.Templates
var safeFileName = fileName.ToSafeFileName();
var mediaItemName = safeFileName.ToFriendlyName();
var mediaFile = mediaService.CreateMedia(mediaItemName, mediaParentFolder, Constants.Conventions.MediaTypes.Image, userId);
IMedia mediaFile;
if(mediaParentFolder == Guid.Empty)
mediaFile = mediaService.CreateMedia(mediaItemName, Constants.System.Root, Constants.Conventions.MediaTypes.Image, userId);
else
mediaFile = mediaService.CreateMedia(mediaItemName, mediaParentFolder, Constants.Conventions.MediaTypes.Image, userId);
var fileInfo = new FileInfo(absoluteTempImagePath);
var fileStream = fileInfo.OpenReadWithRetry();
@@ -242,9 +250,8 @@ namespace Umbraco.Web.Templates
// for each image uploaded from TinyMceController
var folderName = Path.GetDirectoryName(absoluteTempImagePath);
try
{
File.Delete(absoluteTempImagePath);
Directory.Delete(folderName);
{
Directory.Delete(folderName, true);
}
catch (Exception ex)
{