diff --git a/src/Orchard.Web/Core/Orchard.Core.csproj b/src/Orchard.Web/Core/Orchard.Core.csproj index fce0058b5..977ea92a6 100644 --- a/src/Orchard.Web/Core/Orchard.Core.csproj +++ b/src/Orchard.Web/Core/Orchard.Core.csproj @@ -266,6 +266,7 @@ + diff --git a/src/Orchard.Web/Core/Shapes/ResourceManifest.cs b/src/Orchard.Web/Core/Shapes/ResourceManifest.cs index 656acaf5c..694539c5f 100644 --- a/src/Orchard.Web/Core/Shapes/ResourceManifest.cs +++ b/src/Orchard.Web/Core/Shapes/ResourceManifest.cs @@ -5,6 +5,7 @@ namespace Orchard.Core.Shapes { public void BuildManifests(ResourceManifestBuilder builder) { var manifest = builder.Add(); manifest.DefineScript("ShapesBase").SetUrl("base.js").SetDependencies("jQuery"); + manifest.DefineScript("OpenAjax").SetUrl("OpenAjax.js"); manifest.DefineStyle("Shapes").SetUrl("site.css"); // todo: missing manifest.DefineStyle("ShapesSpecial").SetUrl("special.css"); diff --git a/src/Orchard.Web/Core/Shapes/Scripts/OpenAjax.js b/src/Orchard.Web/Core/Shapes/Scripts/OpenAjax.js new file mode 100644 index 000000000..07efb1e77 --- /dev/null +++ b/src/Orchard.Web/Core/Shapes/Scripts/OpenAjax.js @@ -0,0 +1,191 @@ +/******************************************************************************* + * OpenAjax.js + * + * Reference implementation of the OpenAjax Hub, as specified by OpenAjax Alliance. + * Specification is under development at: + * + * http://www.openajax.org/member/wiki/OpenAjax_Hub_Specification + * + * Copyright 2006-2009 OpenAjax Alliance + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 . Unless + * required by applicable law or agreed to in writing, software distributed + * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + * CONDITIONS OF ANY KIND, either express or implied. See the License for the + * specific language governing permissions and limitations under the License. + * + ******************************************************************************/ + +// prevent re-definition of the OpenAjax object +if(!window["OpenAjax"]){ + OpenAjax = new function(){ + var t = true; + var f = false; + var g = window; + var ooh = "org.openajax.hub."; + + var h = {}; + this.hub = h; + h.implementer = "http://openajax.org"; + h.implVersion = "2.0"; + h.specVersion = "2.0"; + h.implExtraData = {}; + var libs = {}; + h.libraries = libs; + + h.registerLibrary = function(prefix, nsURL, version, extra){ + libs[prefix] = { + prefix: prefix, + namespaceURI: nsURL, + version: version, + extraData: extra + }; + this.publish(ooh+"registerLibrary", libs[prefix]); + } + h.unregisterLibrary = function(prefix){ + this.publish(ooh+"unregisterLibrary", libs[prefix]); + delete libs[prefix]; + } + + h._subscriptions = { c:{}, s:[] }; + h._cleanup = []; + h._subIndex = 0; + h._pubDepth = 0; + + h.subscribe = function(name, callback, scope, subscriberData, filter) + { + if(!scope){ + scope = window; + } + var handle = name + "." + this._subIndex; + var sub = { scope: scope, cb: callback, fcb: filter, data: subscriberData, sid: this._subIndex++, hdl: handle }; + var path = name.split("."); + this._subscribe(this._subscriptions, path, 0, sub); + return handle; + } + + h.publish = function(name, message) + { + var path = name.split("."); + this._pubDepth++; + this._publish(this._subscriptions, path, 0, name, message); + this._pubDepth--; + if((this._cleanup.length > 0) && (this._pubDepth == 0)) { + for(var i = 0; i < this._cleanup.length; i++) + this.unsubscribe(this._cleanup[i].hdl); + delete(this._cleanup); + this._cleanup = []; + } + } + + h.unsubscribe = function(sub) + { + var path = sub.split("."); + var sid = path.pop(); + this._unsubscribe(this._subscriptions, path, 0, sid); + } + + h._subscribe = function(tree, path, index, sub) + { + var token = path[index]; + if(index == path.length) + tree.s.push(sub); + else { + if(typeof tree.c == "undefined") + tree.c = {}; + if(typeof tree.c[token] == "undefined") { + tree.c[token] = { c: {}, s: [] }; + this._subscribe(tree.c[token], path, index + 1, sub); + } + else + this._subscribe( tree.c[token], path, index + 1, sub); + } + } + + h._publish = function(tree, path, index, name, msg, pid) { + if(typeof tree != "undefined") { + var node; + if(index == path.length) { + node = tree; + } else { + this._publish(tree.c[path[index]], path, index + 1, name, msg, pid); + this._publish(tree.c["*"], path, index + 1, name, msg, pid); + node = tree.c["**"]; + } + if(typeof node != "undefined") { + var callbacks = node.s; + var max = callbacks.length; + for(var i = 0; i < max; i++) { + if(callbacks[i].cb) { + var sc = callbacks[i].scope; + var cb = callbacks[i].cb; + var fcb = callbacks[i].fcb; + var d = callbacks[i].data; + if(typeof cb == "string"){ + // get a function object + cb = sc[cb]; + } + if(typeof fcb == "string"){ + // get a function object + fcb = sc[fcb]; + } + if((!fcb) || (fcb.call(sc, name, msg, d))) { + cb.call(sc, name, msg, d, pid); + } + } + } + } + } + } + + h._unsubscribe = function(tree, path, index, sid) { + if(typeof tree != "undefined") { + if(index < path.length) { + var childNode = tree.c[path[index]]; + this._unsubscribe(childNode, path, index + 1, sid); + if(childNode.s.length == 0) { + for(var x in childNode.c) + return; + delete tree.c[path[index]]; + } + return; + } + else { + var callbacks = tree.s; + var max = callbacks.length; + for(var i = 0; i < max; i++) + if(sid == callbacks[i].sid) { + if(this._pubDepth > 0) { + callbacks[i].cb = null; + this._cleanup.push(callbacks[i]); + } + else + callbacks.splice(i, 1); + return; + } + } + } + } + // The following function is provided for automatic testing purposes. + // It is not expected to be deployed in run-time OpenAjax Hub implementations. + h.reinit = function() + { + for (var lib in OpenAjax.hub.libraries) { + delete OpenAjax.hub.libraries[lib]; + } + OpenAjax.hub.registerLibrary("OpenAjax", "http://openajax.org/hub", "1.0", {}); + + delete OpenAjax._subscriptions; + OpenAjax._subscriptions = {c:{},s:[]}; + delete OpenAjax._cleanup; + OpenAjax._cleanup = []; + OpenAjax._subIndex = 0; + OpenAjax._pubDepth = 0; + } + }; + // Register the OpenAjax Hub itself as a library. + OpenAjax.hub.registerLibrary("OpenAjax", "http://openajax.org/hub", "1.0", {}); + +} diff --git a/src/Orchard.Web/Modules/Orchard.MediaPicker/AdminFilter.cs b/src/Orchard.Web/Modules/Orchard.MediaPicker/AdminFilter.cs new file mode 100644 index 000000000..62b379218 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MediaPicker/AdminFilter.cs @@ -0,0 +1,26 @@ +using System; +using System.Web.Mvc; +using Orchard.Mvc.Filters; +using Orchard.UI.Resources; + +namespace Orchard.MediaPicker { + public class AdminFilter : FilterProvider, IResultFilter { + private readonly IResourceManager _resourceManager; + + public AdminFilter(IResourceManager resourceManager) { + _resourceManager = resourceManager; + } + + public void OnResultExecuting(ResultExecutingContext filterContext) { + // should only run on a full view rendering result + if (!(filterContext.Result is ViewResult) || !Orchard.UI.Admin.AdminFilter.IsApplied(filterContext.RequestContext)) + return; + _resourceManager.Require("script", "OpenAjax"); + _resourceManager.Require("script", "jQuery"); + _resourceManager.Include("script", "~/Modules/Orchard.MediaPicker/Scripts/MediaPicker.js", null); + } + + public void OnResultExecuted(ResultExecutedContext filterContext) { + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MediaPicker/Controllers/HomeController.cs b/src/Orchard.Web/Modules/Orchard.MediaPicker/Controllers/HomeController.cs new file mode 100644 index 000000000..f65e43524 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MediaPicker/Controllers/HomeController.cs @@ -0,0 +1,46 @@ +using System.Web.Mvc; +using Orchard.Localization; +using Orchard; +using System.Collections.Generic; +using Orchard.Media.Models; +using Orchard.Media.ViewModels; +using Orchard.Media.Services; +using Orchard.Security; +using Orchard.UI.Admin; +using Orchard.Themes; + +namespace Orchard.MediaPicker.Controllers { + [Themed(false)] + public class HomeController : Controller { + private readonly IMediaService _mediaService; + private readonly IAuthorizer _authorizer; + + public IOrchardServices Services { get; set; } + + public HomeController(IOrchardServices services, IMediaService mediaService, IAuthorizer authorizer) { + _authorizer = authorizer; + + Services = services; + _mediaService = mediaService; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + public ActionResult Index(string name, string mediaPath) { + // this controller should only be used from the admin panel. + // it is not itself an admincontroller, however, because it needs to be 'unthemed', + // which admincontrollers currently cannot be. + if (!_authorizer.Authorize(StandardPermissions.AccessAdminPanel, T("Can't access the admin"))) { + return new HttpUnauthorizedResult(); + } + + IEnumerable mediaFiles = _mediaService.GetMediaFiles(mediaPath); + IEnumerable mediaFolders = _mediaService.GetMediaFolders(mediaPath); + var model = new MediaFolderEditViewModel { FolderName = name, MediaFiles = mediaFiles, MediaFolders = mediaFolders, MediaPath = mediaPath }; + ViewData["Service"] = _mediaService; + return View(model); + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.MediaPicker/Module.txt b/src/Orchard.Web/Modules/Orchard.MediaPicker/Module.txt new file mode 100644 index 000000000..70b007878 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MediaPicker/Module.txt @@ -0,0 +1,13 @@ +Name: MediaPicker +AntiForgery: enabled +Author: The Orchard Team +Website: http://orchardproject.net +Version: 1.0 +OrchardVersion: 1.0 +Description: Description for the module +Features: + Orchard.MediaPicker: + Name: MediaPicker + Dependencies: Orchard.Media, Orchard.jQuery + Description: UI for browsing for, uploading, or selecting an image for an HTML editor. + Category: Input Editor \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MediaPicker/Orchard.MediaPicker.csproj b/src/Orchard.Web/Modules/Orchard.MediaPicker/Orchard.MediaPicker.csproj new file mode 100644 index 000000000..e2d3a3eaf --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MediaPicker/Orchard.MediaPicker.csproj @@ -0,0 +1,139 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {43D0EC0B-1955-4566-8D31-7B9102DA1703} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Orchard.MediaPicker + Orchard.MediaPicker + v4.0 + false + + + 3.5 + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + AllRules.ruleset + + + pdbonly + true + bin\ + TRACE + prompt + 4 + AllRules.ruleset + + + + + + + 3.5 + + + + False + ..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll + + + + + + + + + + + + + + + + + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + + + {D9A7B330-CD22-4DA1-A95A-8DE1982AD8EB} + Orchard.Media + + + + + + + + + + + + + + + + + + + + + + + + $(ProjectDir)\..\Manifests + + + + + + + + + + + + False + True + 45979 + / + + + False + True + http://orchard.codeplex.com + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.MediaPicker/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.MediaPicker/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..9dd1bc09b --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MediaPicker/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.MediaPicker")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Orchard")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("d33b592c-133c-440b-8841-17e55eb08306")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Orchard.Web/Modules/Orchard.MediaPicker/Scripts/MediaBrowser.js b/src/Orchard.Web/Modules/Orchard.MediaPicker/Scripts/MediaBrowser.js new file mode 100644 index 000000000..0469532f9 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.MediaPicker/Scripts/MediaBrowser.js @@ -0,0 +1,258 @@ +(function ($) { + var currentAspect = {}, + selectedItem, + suppressResize; + + $.extend({ + mediaPicker: { + init: function (data) { + // called by the opener to initiate dialog with existing image data. + // todo: data.img may contain existing image data, populate the fields + var img = data.img; + if (img) { + for (var name in img) { + $("#img-" + name).val(img[name]); + } + suppressResize = true; + $("#img-src").trigger("change"); + } + }, + uploadMedia: uploadMedia, + scalePreview: scalePreview + } + }); + + $("#img-cancel, #lib-cancel").live("click", function () { window.close(); }); + // when url changes, set the preview and loader src + $("#img-src").live("change", function () { + selectImage(getIdPrefix(this), this.value); + }); + $(".media-item").live("click", function () { + if (selectedItem) { + selectedItem.removeClass("selected"); + } + selectedItem = $(this); + selectedItem.addClass("selected"); + selectImage("#lib-", selectedItem.attr("data-imgsrc")); + }); + // maintain aspect ratio when width or height is changed + $("#img-width, #lib-width").live("change", fixAspectHeight); + $("#img-height, #lib-height").live("change", fixAspectWidth); + + $("#img-insert, #lib-insert").live("click", function () { + if ($(this).hasClass("disabled")) return; + publishInsertEvent(this); + }); + + $(function () { + $("#tabs").tabs({ selected: parseInt(location.hash.replace("#", "")) || 0 }); + + // populate width and height when image loads + // note: load event does not bubble so cannot be used with .live + $("#img-loader, #lib-loader").bind("load", syncImage); + + // edit mode has slightly different wording + // elements advertise this with data-edittext attributes, + // the edit text is the element's new val() unless -content is specified. + if (query("editmode") === "true") { + $("[data-edittext]").each(function () { + var self = $(this), + isContent = self.attr("data-edittext-content") === "true", + editText = self.attr("data-edittext"); + if (isContent) { + self.text(editText); + } + else { + self.attr("value", editText); + } + }); + } + + var data = window.mediaPickerData; + if (data) { + window.mediaPickerData = null; + $.mediaPicker.init(data); + } + }); + + function selectImage(prefix, src) { + $(prefix + "preview").width("").height("").attr("src", src); + $(prefix + "loader").attr("src", src); + $(prefix + "src").val(src); + + var disabled = src ? "" : "disabled"; + $(prefix + "insert").attr("disabled", disabled).toggleClass("disabled", !!disabled); + } + + function getIdPrefix(e) { + return "#" + e.id.substr(0, 4); + } + function publishInsertEvent(button) { + var prefix = getIdPrefix(button), + editorId = query("editorId"), + source = query("source"), + img = { + src: $(prefix + "src").val(), + alt: $(prefix + "alt").val(), + "class": $(prefix + "class").val(), + style: $(prefix + "style").val(), + align: $(prefix + "align").val(), + width: $(prefix + "width").val(), + height: $(prefix + "height").val() + }; + img.html = getImageHtml(img); + window.opener.OpenAjax.hub.publish("orchard.admin.pickimage-picked." + source, { + editorId: editorId, + img: img + }); + window.close(); + } + + function parseUnits(value) { + if (/\s*[0-9]+\s*(px)?\s*/i.test(value)) { + return parseInt(value); + } + return NaN; + } + + function fixAspectWidth() { + var prefix = getIdPrefix(this); + if (!$(prefix + "lock:checked").val()) return; + var height = parseUnits(this.value); + if (!isNaN(height)) { + $(prefix + "width").val(Math.round(height * currentAspect[prefix])); + } + } + + function fixAspectHeight() { + var prefix = getIdPrefix(this); + if (!$(prefix + "lock:checked").val()) return; + var width = parseUnits(this.value); + if (!isNaN(width)) { + $(prefix + "height").val(Math.round(width / currentAspect[prefix])); + } + } + + function scalePreview(img) { + // ensures the loaded image preview fits within the preview area + // by scaling it down if not. + var self = $(img), + width = self.width(), + height = self.height(), + aspect = width / height, + maxWidth = self.parent().width(), + maxHeight = self.parent().height(); + if (width > maxWidth) { + width = maxWidth; + height = Math.round(width / aspect); + } + if (height > maxHeight) { + height = maxHeight; + width = Math.round(width * aspect); + } + self.width(width).height(height); + } + + function syncImage() { + // when the image loader loads, we use it to calculate the current image + // aspect ratio, and update the width and height fields. + var prefix = getIdPrefix(this), + self = $(this), + width = self.width(), + height = self.height(); + currentAspect[prefix] = width / height; + // because we just loaded an edited image, leave the width/height + // at their configured values, not the natural size. + if (!suppressResize) { + $(prefix + "width").val(width); + $(prefix + "height").val(height); + } + suppressResize = false; + } + + function getAttr(name, value) { + // get an attribute value, escaping any necessary characters to html entities. + // not an exhastive list, but should cover all the necessary characters for this UI (e.g. you can't really put in newlines). + if (!value && name !== "alt") return ""; + return ' ' + name + '="' + value.replace(/&/g, "&").replace(/"/g, """).replace(//g, ">") + '"'; + } + + function getImageHtml(data) { + return html = '"; + } + + function uploadMedia(form) { + var name = "addmedia__" + (new Date()).getTime(); + $("