Add new API controller for uploading images dragged/pasted into a TinyMCE editor

This commit is contained in:
Warren Buckley
2019-08-29 14:06:29 +01:00
parent 413b266335
commit 1ec88a6647
4 changed files with 174 additions and 8 deletions

View File

@@ -156,6 +156,55 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
}
}
function uploadImageHandler(blobInfo, success, failure, progress){
//TODO: Worth refactoring to Angular $http calls
//Rather than XHR??
let xhr, formData;
xhr = new XMLHttpRequest();
xhr.open('POST', Umbraco.Sys.ServerVariables.umbracoUrls.tinyMceApiBaseUrl + 'UploadImage');
xhr.upload.onprogress = function (e) {
progress(e.loaded / e.total * 100);
};
xhr.onerror = function () {
failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
};
xhr.onload = function () {
let json;
if (xhr.status < 200 || xhr.status >= 300) {
failure('HTTP Error: ' + xhr.status);
return;
}
json = JSON.parse(xhr.responseText);
if (!json || typeof json.location !== 'string') {
failure('Invalid JSON: ' + xhr.responseText);
return;
}
// TODO: The JSON contains location & udi
// Need to add UDI as attr
success(json.location);
};
formData = new FormData();
formData.append('file', blobInfo.blob(), blobInfo.blob().name);
//TODO: Send Media Parent ID from config
//TODO: How will each RTE pass in this value to this function?!
formData.append('mediaParent', -1);
xhr.send(formData);
}
return {
/**
@@ -237,13 +286,19 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
insert_toolbar: toolbars.insertToolbar,
selection_toolbar: toolbars.selectionToolbar,
body_class: 'umb-rte',
body_class: "umb-rte",
//see http://archive.tinymce.com/wiki.php/Configuration:cache_suffix
cache_suffix: "?umb__rnd=" + Umbraco.Sys.ServerVariables.application.cacheBuster,
//this is used to style the inline macro bits, sorry hard coding this form now since we don't have a standalone
//stylesheet to load in for this with only these styles (the color is @pinkLight)
content_style: ".mce-content-body .umb-macro-holder { border: 3px dotted #f5c1bc; padding: 7px; display: block; margin: 3px; } .umb-rte .mce-content-body .umb-macro-holder.loading {background: url(assets/img/loader.gif) right no-repeat; background-size: 18px; background-position-x: 99%;}"
// This allows images to be pasted in & stored as Base64 until they get uploaded to server
paste_data_images: true
images_upload_handler: uploadImageHandler,
automatic_uploads: false,
};
if (tinyMceConfig.customConfig) {
@@ -281,7 +336,6 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
angular.extend(config, tinyMceConfig.customConfig);
}
return $q.when(config);
});
@@ -428,7 +482,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
id: '__mcenew',
'data-udi': img.udi
};
editor.selection.setContent(editor.dom.createHTML('img', data));
$timeout(function () {
@@ -447,9 +501,9 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
}
}
editor.dom.setAttrib(imgElm, 'id', null);
editor.fire('Change');
}, 500);
}
},
@@ -521,7 +575,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
onPostRender: function () {
let ctrl = this;
/**
* Check if the macro is currently selected and toggle the menu button
*/
@@ -1134,7 +1188,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
};
editorService.linkPicker(linkPicker);
});
});
//Create the insert media plugin

View File

@@ -309,7 +309,11 @@ namespace Umbraco.Web.Editors
{
"webProfilingBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<WebProfilingController>(
controller => controller.GetStatus())
}
},
{
"tinyMceApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl<TinyMceController>(
controller => controller.UploadImage())
},
}
},
{

View File

@@ -0,0 +1,107 @@
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using Umbraco.Core.Services;
using Umbraco.Web.WebApi;
using Constants = Umbraco.Core.Constants;
using Umbraco.Core;
using Umbraco.Web.Mvc;
using Umbraco.Core.IO;
using System.IO;
using System.Threading.Tasks;
using Umbraco.Web.Composing;
using Umbraco.Core.Configuration.UmbracoSettings;
using System.Linq;
using System;
using Umbraco.Core.Models.PublishedContent;
namespace Umbraco.Web.Editors
{
[PluginController("UmbracoApi")]
public class TinyMceController : UmbracoAuthorizedApiController
{
private IMediaService _mediaService;
private IContentTypeBaseServiceProvider _contentTypeBaseServiceProvider;
public TinyMceController(IMediaService mediaService, IContentTypeBaseServiceProvider contentTypeBaseServiceProvider)
{
_mediaService = mediaService;
_contentTypeBaseServiceProvider = contentTypeBaseServiceProvider;
}
[HttpPost]
public async Task<HttpResponseMessage> UploadImage()
{
if (Request.Content.IsMimeMultipartContent() == false)
{
throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
}
var root = IOHelper.MapPath(SystemDirectories.TempFileUploads);
// Ensure it exists
Directory.CreateDirectory(root);
var provider = new MultipartFormDataStreamProvider(root);
var result = await Request.Content.ReadAsMultipartAsync(provider);
// Must have a file
if (result.FileData.Count == 0)
{
return Request.CreateResponse(HttpStatusCode.NotFound);
}
// Should only have one file
if (result.FileData.Count > 1)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Only one file can be uploaded at a time");
}
// Check we have mediaParent as posted data
if (string.IsNullOrEmpty(result.FormData["mediaParent"]))
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Missing the Media Parent folder to save this image");
}
// Really we should only have one file per request to this endpoint
var file = result.FileData[0];
var parentFolder = Convert.ToInt32(result.FormData["mediaParent"]);
var fileName = file.Headers.ContentDisposition.FileName.Trim(new[] { '\"' }).TrimEnd();
var safeFileName = fileName.ToSafeFileName();
var ext = safeFileName.Substring(safeFileName.LastIndexOf('.') + 1).ToLower();
if (Current.Configs.Settings().Content.IsFileAllowedForUpload(ext) == false || Current.Configs.Settings().Content.ImageFileTypes.Contains(ext) == false)
{
// Throw some error - to say can't upload this IMG type
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, "This is not an image filetype extension that is approved");
}
var mediaItemName = fileName.ToFriendlyName();
var f = _mediaService.CreateMedia(mediaItemName, parentFolder, Constants.Conventions.MediaTypes.Image, Security.CurrentUser.Id);
var fileInfo = new FileInfo(file.LocalFileName);
var fs = fileInfo.OpenReadWithRetry();
if (fs == null) throw new InvalidOperationException("Could not acquire file stream");
using (fs)
{
f.SetValue(Services.ContentTypeBaseServices, Constants.Conventions.Media.File, fileName, fs);
}
_mediaService.Save(f, Security.CurrentUser.Id);
// Need to get URL to the media item and its UDI
var udi = f.GetUdi();
// TODO: Check this is the BEST way to get the URL
// Ensuring if they use some CDN & blob storage?!
var mediaTyped = Umbraco.Media(f.Id);
var location = mediaTyped.Url;
return Request.CreateResponse(HttpStatusCode.OK, new { location = location, udi = udi });
}
}
}

View File

@@ -148,6 +148,7 @@
<Compile Include="Editors\KeepAliveController.cs" />
<Compile Include="Editors\MacrosController.cs" />
<Compile Include="Editors\RelationTypeController.cs" />
<Compile Include="Editors\TinyMceController.cs" />
<Compile Include="IUmbracoContextFactory.cs" />
<Compile Include="Install\ChangesMonitor.cs" />
<Compile Include="Logging\WebProfiler.cs" />