Including Orchard.ContentPicker

--HG--
branch : 1.x
This commit is contained in:
Sebastien Ros
2012-08-22 15:35:45 -07:00
parent 969f89f2d2
commit e1d6a6cad4
28 changed files with 1181 additions and 0 deletions

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.Core.Common.Models;
using Orchard.Core.Contents.Settings;
using Orchard.Core.Contents.ViewModels;
using Orchard.DisplayManagement;
using Orchard.Mvc;
using Orchard.Settings;
using Orchard.Themes;
using Orchard.UI.Navigation;
namespace Orchard.ContentPicker.Controllers {
public class AdminController : Controller {
private readonly ISiteService _siteService;
private readonly IContentDefinitionManager _contentDefinitionManager;
public AdminController(
IOrchardServices orchardServices,
ISiteService siteService,
IContentDefinitionManager contentDefinitionManager) {
_siteService = siteService;
_contentDefinitionManager = contentDefinitionManager;
Services = orchardServices;
}
public IOrchardServices Services { get; set; }
[Themed(false)]
public ActionResult Index(ListContentsViewModel model, PagerParameters pagerParameters) {
var pager = new Pager(_siteService.GetSiteSettings(), pagerParameters);
var query = Services.ContentManager.Query(VersionOptions.Latest, GetCreatableTypes(false).Select(ctd => ctd.Name).ToArray());
if (!string.IsNullOrEmpty(model.Options.SelectedFilter)) {
var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(model.Options.SelectedFilter);
if (contentTypeDefinition == null)
return HttpNotFound();
model.TypeDisplayName = !string.IsNullOrWhiteSpace(contentTypeDefinition.DisplayName)
? contentTypeDefinition.DisplayName
: contentTypeDefinition.Name;
query = query.ForType(model.Options.SelectedFilter);
}
switch (model.Options.OrderBy) {
case ContentsOrder.Modified:
query = query.OrderByDescending<CommonPartRecord>(cr => cr.ModifiedUtc);
break;
case ContentsOrder.Published:
query = query.OrderByDescending<CommonPartRecord>(cr => cr.PublishedUtc);
break;
case ContentsOrder.Created:
query = query.OrderByDescending<CommonPartRecord>(cr => cr.CreatedUtc);
break;
}
model.Options.FilterOptions = GetCreatableTypes(false)
.Select(ctd => new KeyValuePair<string, string>(ctd.Name, ctd.DisplayName))
.ToList().OrderBy(kvp => kvp.Value);
var pagerShape = Services.New.Pager(pager).TotalItemCount(query.Count());
var pageOfContentItems = query.Slice(pager.GetStartIndex(), pager.PageSize).ToList();
var list = Services.New.List();
list.AddRange(pageOfContentItems.Select(ci => Services.ContentManager.BuildDisplay(ci, "SummaryAdmin")));
foreach(IShape item in list.Items) {
item.Metadata.Type = "ContentPicker";
}
dynamic tab = Services.New.RecentContentTab()
.ContentItems(list)
.Pager(pagerShape)
.Options(model.Options)
.TypeDisplayName(model.TypeDisplayName ?? "");
return new ShapeResult(this, Services.New.ContentPicker().Tab(tab));
}
private IEnumerable<ContentTypeDefinition> GetCreatableTypes(bool andContainable) {
return _contentDefinitionManager.ListTypeDefinitions().Where(ctd => ctd.Settings.GetModel<ContentTypeSettings>().Creatable && (!andContainable || ctd.Parts.Any(p => p.PartDefinition.Name == "ContainablePart")));
}
}
}

View File

@@ -0,0 +1,100 @@
using System;
using System.Linq;
using Orchard.ContentPicker.Settings;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Drivers;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentPicker.ViewModels;
using Orchard.Localization;
using Orchard.Utility.Extensions;
namespace Orchard.ContentPicker.Drivers {
public class ContentPickerFieldDriver : ContentFieldDriver<Fields.ContentPickerField> {
private readonly IContentManager _contentManager;
public ContentPickerFieldDriver(IContentManager contentManager) {
_contentManager = contentManager;
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
private static string GetPrefix(Fields.ContentPickerField field, ContentPart part) {
return part.PartDefinition.Name + "." + field.Name;
}
private static string GetDifferentiator(Fields.ContentPickerField field, ContentPart part) {
return field.Name;
}
protected override DriverResult Display(ContentPart part, Fields.ContentPickerField field, string displayType, dynamic shapeHelper) {
return Combined(
ContentShape("Fields_ContentPicker", GetDifferentiator(field, part), () => shapeHelper.Fields_ContentPicker()),
ContentShape("Fields_ContentPicker_SummaryAdmin", GetDifferentiator(field, part), () => shapeHelper.Fields_ContentPicker_SummaryAdmin())
);
}
protected override DriverResult Editor(ContentPart part, Fields.ContentPickerField field, dynamic shapeHelper) {
return ContentShape("Fields_ContentPicker_Edit", GetDifferentiator(field, part),
() => {
var model = new ContentPickerFieldViewModel {
Field = field,
ContentItems = _contentManager.GetMany<ContentItem>(field.Ids, VersionOptions.Published, QueryHints.Empty).ToList(),
};
model.SelectedIds = string.Concat(",", field.Ids);
return shapeHelper.EditorTemplate(TemplateName: "Fields/ContentPicker.Edit", Model: model, Prefix: GetPrefix(field, part));
});
}
protected override DriverResult Editor(ContentPart part, Fields.ContentPickerField field, IUpdateModel updater, dynamic shapeHelper) {
var model = new ContentPickerFieldViewModel();
updater.TryUpdateModel(model, GetPrefix(field, part), null, null);
var settings = field.PartFieldDefinition.Settings.GetModel<ContentPickerFieldSettings>();
if (String.IsNullOrEmpty(model.SelectedIds)) {
field.Ids = new int[0];
}
else {
field.Ids = model.SelectedIds.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
}
if (settings.Required && field.Ids.Length == 0) {
updater.AddModelError("Id", T("The field {0} is mandatory", field.Name.CamelFriendly()));
}
return Editor(part, field, shapeHelper);
}
protected override void Importing(ContentPart part, Fields.ContentPickerField field, ImportContentContext context) {
var contentItemIds = context.Attribute(field.FieldDefinition.Name + "." + field.Name, "ContentItems");
if (contentItemIds != null) {
field.Ids = contentItemIds.Split(',')
.Select(context.GetItemFromSession)
.Select(contentItem => contentItem.Id).ToArray();
}
else {
field.Ids = new int[0];
}
}
protected override void Exporting(ContentPart part, Fields.ContentPickerField field, ExportContentContext context) {
if (field.Ids.Any()) {
var contentItemIds = field.Ids
.Select(x => _contentManager.Get(x))
.Select(x => _contentManager.GetItemMetadata(x).Identity.ToString())
.ToArray();
context.Element(field.FieldDefinition.Name + "." + field.Name).SetAttributeValue("ContentItems", string.Join(",", contentItemIds));
}
}
protected override void Describe(DescribeMembersContext context) {
context
.Member(null, typeof(string), T("Ids"), T("A formatted list of the ids, e.g., {1},{42}"));
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.FieldStorage;
using Orchard.ContentManagement.Utilities;
namespace Orchard.ContentPicker.Fields {
public class ContentPickerField : ContentField {
private static readonly char[] separator = new [] {'{', '}', ','};
internal LazyField<IEnumerable<ContentItem>> _contentItems = new LazyField<IEnumerable<ContentItem>>();
public int[] Ids {
get { return DecodeIds(Storage.Get<string>()); }
set { Storage.Set(EncodeIds(value)); }
}
public IEnumerable<ContentItem> ContentItems {
get {
return _contentItems.Value;
}
}
private string EncodeIds(ICollection<int> ids) {
if (ids == null || !ids.Any()) {
return string.Empty;
}
// use {1},{2} format so it can be filtered with delimiters
return "{" + string.Join("},{", ids.ToArray()) + "}";
}
private int[] DecodeIds(string ids) {
if(String.IsNullOrWhiteSpace(ids)) {
return new int[0];
}
return ids.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(int.Parse).ToArray();
}
}
}

View File

@@ -0,0 +1,37 @@
using System.Linq;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Handlers;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentPicker.Fields;
namespace Orchard.ContentPicker.Handlers {
public class ContentPickerFieldHandler : ContentHandler {
private readonly IContentManager _contentManager;
private readonly IContentDefinitionManager _contentDefinitionManager;
public ContentPickerFieldHandler(
IContentManager contentManager,
IContentDefinitionManager contentDefinitionManager) {
_contentManager = contentManager;
_contentDefinitionManager = contentDefinitionManager;
}
protected override void Loading(LoadContentContext context) {
base.Loading(context);
var fields = context.ContentItem.Parts.SelectMany(x => x.Fields.Where(f => f.FieldDefinition.Name == typeof (ContentPickerField).Name)).Cast<ContentPickerField>();
// define lazy initializer for ContentPickerField.ContentItems
var contentTypeDefinition = _contentDefinitionManager.GetTypeDefinition(context.ContentType);
if (contentTypeDefinition == null) {
return;
}
foreach (var field in fields) {
var localField = field;
field._contentItems.Loader(x => _contentManager.GetMany<ContentItem>(localField.Ids, VersionOptions.Published, QueryHints.Empty));
}
}
}
}

View File

@@ -0,0 +1,12 @@
Name: Orchard.ContentPicker
AntiForgery: enabled
Author: The Orchard Team
Website: http://orchardcontentpicker.codeplex.com
Version: 1.5.1
OrchardVersion: 1.4
Description: UI for selecting Content Items.
Features:
Orchard.ContentPicker:
Description: UI for selecting Content Items.
Dependencies: Contents
Category: Input Editor

View File

@@ -0,0 +1,170 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.30729</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{F301EF7D-F19C-4D83-AA94-CB64F29C037D}</ProjectGuid>
<ProjectTypeGuids>{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Orchard.ContentPicker</RootNamespace>
<AssemblyName>Orchard.ContentPicker</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<MvcBuildViews>false</MvcBuildViews>
<FileUpgradeFlags>
</FileUpgradeFlags>
<OldToolsVersion>4.0</OldToolsVersion>
<UpgradeBackupLocation>
</UpgradeBackupLocation>
<TargetFrameworkProfile />
<UseIISExpress>false</UseIISExpress>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.ComponentModel.DataAnnotations">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Web.DynamicData" />
<Reference Include="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\..\lib\aspnetmvc\System.Web.Mvc.dll</HintPath>
</Reference>
<Reference Include="System.Web" />
<Reference Include="System.Web.Abstractions" />
<Reference Include="System.Web.Routing" />
<Reference Include="System.Xml" />
<Reference Include="System.Configuration" />
<Reference Include="System.Xml.Linq" />
</ItemGroup>
<ItemGroup>
<Content Include="Scripts\ContentPicker.js" />
<Content Include="Styles\ContentPicker.css" />
<Content Include="Web.config" />
<Content Include="Views\Web.config" />
<Content Include="Scripts\Web.config" />
<Content Include="Styles\Web.config" />
<Content Include="Properties\AssemblyInfo.cs" />
<Content Include="Module.txt" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Orchard\Orchard.Framework.csproj">
<Project>{2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6}</Project>
<Name>Orchard.Framework</Name>
</ProjectReference>
<ProjectReference Include="..\..\Core\Orchard.Core.csproj">
<Project>{9916839C-39FC-4CEB-A5AF-89CA7E87119F}</Project>
<Name>Orchard.Core</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Folder Include="Models\" />
<Folder Include="Views\Admin\" />
</ItemGroup>
<ItemGroup>
<Compile Include="Controllers\AdminController.cs" />
<Compile Include="Drivers\ContentPickerFieldDriver.cs" />
<Compile Include="Fields\ContentPickerField.cs" />
<Compile Include="Handlers\ContentPickerFieldHandler.cs" />
<Compile Include="ResourceManifest.cs" />
<Compile Include="Services\ContentPickerNavigationProvider.cs" />
<Compile Include="Services\ContentPickerShapes.cs" />
<Compile Include="Settings\ContentPickerFieldEditorEvents.cs" />
<Compile Include="Settings\ContentPickerFieldSettings.cs" />
<Compile Include="Tokens\FieldTokens.cs" />
<Compile Include="ViewModels\ContentPickerFieldViewModel.cs" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\ContentPicker.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\RecentContentTab.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\ContentPicker.SummaryAdmin.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\DefinitionTemplates\ContentPickerFieldSettings.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\EditorTemplates\Fields\ContentPicker.Edit.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Fields\ContentPicker.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Views\Fields\ContentPicker.SummaryAdmin.cshtml" />
</ItemGroup>
<ItemGroup>
<Content Include="Placement.info">
<SubType>Designer</SubType>
</Content>
</ItemGroup>
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">10.0</VisualStudioVersion>
<VSToolsPath Condition="'$(VSToolsPath)' == ''">$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)</VSToolsPath>
</PropertyGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\WebApplications\Microsoft.WebApplication.targets" Condition="'$(VSToolsPath)' != ''" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v10.0\WebApplications\Microsoft.WebApplication.targets" Condition="false" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target> -->
<Target Name="AfterBuild" DependsOnTargets="AfterBuildCompiler">
<PropertyGroup>
<AreasManifestDir>$(ProjectDir)\..\Manifests</AreasManifestDir>
</PropertyGroup>
<!-- If this is an area child project, uncomment the following line:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Child" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
-->
<!-- If this is an area parent project, uncomment the following lines:
<CreateAreaManifest AreaName="$(AssemblyName)" AreaType="Parent" AreaPath="$(ProjectDir)" ManifestPath="$(AreasManifestDir)" ContentFiles="@(Content)" />
<CopyAreaManifests ManifestPath="$(AreasManifestDir)" CrossCopy="false" RenameViews="true" />
-->
</Target>
<Target Name="AfterBuildCompiler" Condition="'$(MvcBuildViews)'=='true'">
<AspNetCompiler VirtualPath="temp" PhysicalPath="$(ProjectDir)\..\$(ProjectName)" />
</Target>
<ProjectExtensions>
<VisualStudio>
<FlavorProperties GUID="{349c5851-65df-11da-9384-00065b846f21}">
<WebProjectProperties>
<UseIIS>False</UseIIS>
<AutoAssignPort>True</AutoAssignPort>
<DevelopmentServerPort>45979</DevelopmentServerPort>
<DevelopmentServerVPath>/</DevelopmentServerVPath>
<IISUrl>
</IISUrl>
<NTLMAuthentication>False</NTLMAuthentication>
<UseCustomServer>True</UseCustomServer>
<CustomServerUrl>http://orchard.codeplex.com</CustomServerUrl>
<SaveServerSettingsInUserFile>False</SaveServerSettingsInUserFile>
</WebProjectProperties>
</FlavorProperties>
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@@ -0,0 +1,12 @@
<Placement>
<Place Fields_ContentPicker_Edit="Content:2.3"/>
<Match DisplayType="Detail">
<Place Fields_ContentPicker="Content:after"/>
</Match>
<Match DisplayType="SummaryAdmin">
<Place Fields_ContentPicker_SummaryAdmin="Content:after"/>
</Match>
</Placement>

View File

@@ -0,0 +1,36 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Security;
// 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.ContentPicker")]
[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("a5bcfb31-0501-4f12-865d-b04acdc46d7d")]
// 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.5")]
[assembly: AssemblyFileVersion("1.5")]

View File

@@ -0,0 +1,10 @@
using Orchard.UI.Resources;
namespace Orchard.ContentPicker {
public class ResourceManifest : IResourceManifestProvider {
public void BuildManifests(ResourceManifestBuilder builder) {
var manifest = builder.Add();
manifest.DefineScript("ContentPicker").SetUrl("ContentPicker.js", "ContentPicker.js").SetDependencies("jQuery");
}
}
}

View File

@@ -0,0 +1,26 @@
jQuery(function ($) {
$("form").bind("orchard-admin-contentpicker-open", function (ev, data) {
data = data || {};
// the popup will be doing full page reloads, so will not be able to retain
// a pointer to the callback. We will generate a temporary callback
// with a known/unique name and pass that in on the querystring so it
// is remembers across reloads. Once executed, it calls the real callback
// and removes itself.
var callbackName = "_contentpicker_" + new Date().getTime();
data.callbackName = callbackName;
$[callbackName] = function (returnData) {
delete $[callbackName];
data.callback(returnData);
};
$[callbackName].data = data;
var adminIndex = location.href.toLowerCase().indexOf("/admin/");
if (adminIndex === -1) return;
var url = location.href.substr(0, adminIndex)
+ "/Admin/Orchard.ContentPicker?"
+ "callback=" + callbackName
+ "&" + (new Date() - 0);
var w = window.open(url, "_blank", data.windowFeatures || "width=685,height=700,status=no,toolbar=no,location=no,menubar=no,resizable=no,scrollbars=yes");
});
});

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<httpHandlers>
<!-- iis6 - for any request in this location, return via managed static file handler -->
<add path="*" verb="*" type="System.Web.StaticFileHandler" />
</httpHandlers>
</system.web>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
</staticContent>
<handlers accessPolicy="Script,Read">
<!--
iis7 - for any request to a file exists on disk, return it via native http module.
accessPolicy 'Script' is to allow for a managed 404 page.
-->
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
</handlers>
</system.webServer>
</configuration>

View File

@@ -0,0 +1,22 @@
using Orchard.Localization;
using Orchard.UI.Navigation;
namespace Orchard.ContentPicker.Services {
public class ContentPickerNavigationProvider : INavigationProvider {
public ContentPickerNavigationProvider() {
T = NullLocalizer.Instance;
}
public Localizer T { get; set; }
public string MenuName {
get { return "content-picker"; }
}
public void GetNavigation(NavigationBuilder builder) {
builder.Add(T("Content Picker"),
menu => menu
.Add(T("Recent Content"), "5", item => item.Action("Index", "Admin", new {area = "Orchard.ContentPicker"}).LocalNav()));
}
}
}

View File

@@ -0,0 +1,52 @@
using System.Collections.Generic;
using System.Web;
using Orchard.ContentManagement;
using Orchard.DisplayManagement;
using Orchard.DisplayManagement.Descriptors;
using Orchard.Environment;
using Orchard.UI.Navigation;
// ReSharper disable InconsistentNaming
namespace Orchard.ContentPicker.Services {
public class ContentPickerShapes : IShapeTableProvider {
private readonly Work<INavigationManager> _navigationManager;
private readonly Work<WorkContext> _workContext;
private readonly Work<IShapeFactory> _shapeFactory;
public ContentPickerShapes(
Work<INavigationManager> navigationManager,
Work<WorkContext> workContext,
Work<IShapeFactory> shapeFactory) {
_navigationManager = navigationManager;
_workContext = workContext;
_shapeFactory = shapeFactory;
}
public void Discover(ShapeTableBuilder builder) {
builder.Describe("ContentPicker")
.OnDisplaying(displaying => {
ContentItem contentItem = displaying.Shape.ContentItem;
if (contentItem != null) {
displaying.ShapeMetadata.Alternates.Add("ContentPicker_" + displaying.ShapeMetadata.DisplayType);
}
});
}
[Shape]
public IHtmlString ContentPickerNavigation(dynamic Display) {
IEnumerable<MenuItem> menuItems = _navigationManager.Value.BuildMenu("content-picker");
// Set the currently selected path
Stack<MenuItem> selectedPath = NavigationHelper.SetSelectedPath(menuItems, _workContext.Value.HttpContext.Request.RequestContext.RouteData);
dynamic shapeFactory = _shapeFactory.Value;
// Populate local nav
dynamic localMenuShape = shapeFactory.LocalMenu().MenuName("content-picker");
NavigationHelper.PopulateLocalMenu(shapeFactory, localMenuShape, localMenuShape, selectedPath);
return Display(localMenuShape);
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Collections.Generic;
using System.Globalization;
using Orchard.ContentManagement;
using Orchard.ContentManagement.MetaData;
using Orchard.ContentManagement.MetaData.Builders;
using Orchard.ContentManagement.MetaData.Models;
using Orchard.ContentManagement.ViewModels;
namespace Orchard.ContentPicker.Settings {
public class ContentPickerFieldEditorEvents : ContentDefinitionEditorEventsBase {
public override IEnumerable<TemplateViewModel> PartFieldEditor(ContentPartFieldDefinition definition) {
if (definition.FieldDefinition.Name == "ContentPickerField") {
var model = definition.Settings.GetModel<ContentPickerFieldSettings>();
yield return DefinitionTemplate(model);
}
}
public override IEnumerable<TemplateViewModel> PartFieldEditorUpdate(ContentPartFieldDefinitionBuilder builder, IUpdateModel updateModel) {
if (builder.FieldType != "ContentPickerField") {
yield break;
}
var model = new ContentPickerFieldSettings();
if (updateModel.TryUpdateModel(model, "ContentPickerFieldSettings", null, null)) {
builder.WithSetting("ContentPickerFieldSettings.Hint", model.Hint);
builder.WithSetting("ContentPickerFieldSettings.Required", model.Required.ToString(CultureInfo.InvariantCulture));
builder.WithSetting("ContentPickerFieldSettings.Multiple", model.Multiple.ToString(CultureInfo.InvariantCulture));
}
yield return DefinitionTemplate(model);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Orchard.ContentPicker.Settings {
public class ContentPickerFieldSettings {
public string Hint { get; set; }
public bool Required { get; set; }
public bool Multiple { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
html { background:none !important }
body {
color: #333;
background:#f3f4f5;
font-family: Segoe UI,Trebuchet,Arial,Sans-Serif;
line-height:1.6em;
padding:12px;
width:640px;
min-width:640px;
margin: 0 auto;
}
#orchard-content-picker * { font:100% normal Segoe UI,Trebuchet,Arial,Sans-Serif; }
.content-picker-tab
{
margin: 6px;
padding: 18px;
background: white;
border: 1px solid #E4E5E6;
}

View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<httpHandlers>
<!-- iis6 - for any request in this location, return via managed static file handler -->
<add path="*" verb="*" type="System.Web.StaticFileHandler" />
</httpHandlers>
</system.web>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
</staticContent>
<handlers accessPolicy="Script,Read">
<!--
iis7 - for any request to a file exists on disk, return it via native http module.
accessPolicy 'Script' is to allow for a managed 404 page.
-->
<add name="StaticFile" path="*" verb="*" modules="StaticFileModule" preCondition="integratedMode" resourceType="File" requireAccess="Read" />
</handlers>
</system.webServer>
</configuration>

View File

@@ -0,0 +1,35 @@
using System;
using Orchard.ContentManagement;
using Orchard.Events;
using Orchard.ContentPicker.Fields;
using Orchard.Localization;
namespace Orchard.ContentPicker.Tokens {
public interface ITokenProvider : IEventHandler {
void Describe(dynamic context);
void Evaluate(dynamic context);
}
public class ContentPickerFieldTokens : ITokenProvider {
private readonly IContentManager _contentManager;
public ContentPickerFieldTokens(IContentManager contentManager) {
_contentManager = contentManager;
}
public Localizer T { get; set; }
public void Describe(dynamic context) {
context.For("ContentPickerField", T("Content Picker Field"), T("Tokens for Content Picker Fields"))
.Token("Content", T("Content Item"), T("The content item."))
;
}
public void Evaluate(dynamic context) {
context.For<ContentPickerField>("ContentPickerField")
.Token("Content", (Func<ContentPickerField, object>)(field => field.Ids[0]))
.Chain("Content", "Content", (Func<ContentPickerField, object>)(field => _contentManager.Get(field.Ids[0])))
;
}
}
}

View File

@@ -0,0 +1,13 @@
using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.ContentPicker.Fields;
namespace Orchard.ContentPicker.ViewModels {
public class ContentPickerFieldViewModel {
public ICollection<ContentItem> ContentItems { get; set; }
public string SelectedIds { get; set; }
public ContentPickerField Field { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
@using Orchard.ContentManagement;
@{
ContentItem contentItem = Model.ContentItem;
}
<div class="summary" itemscope="itemscope" itemid="@contentItem.Id" itemtype="http://orchardproject.net/data/ContentItem">
<div class="properties">
@*<input type="checkbox" value="@contentItem.Id" name="itemIds"/>*@
<h3>@Html.ItemDisplayText(contentItem)</h3> - <div class="contentType">@contentItem.TypeDefinition.DisplayName</div>
@if (Model.Header != null) {
<div class="header">@Display(Model.Header)</div>
}
@if (Model.Meta != null) {
<div class="metadata">@Display(Model.Meta)</div>
}
</div>
<div class="related" data-display-text="@Html.ItemDisplayText(contentItem)" data-id="@contentItem.Id" data-edit-link="@Html.Encode(Html.ItemEditLink(contentItem))" data-display-link="@Html.Encode(Html.ItemDisplayLink(contentItem))" >
@Html.Link(T("Select").Text, "#", new { @class = "button select"})
</div>
@if (Model.Content != null) {
<div class="primary">@Display(Model.Content)</div>
}
</div>

View File

@@ -0,0 +1,95 @@
@{
// these need to be in the head because MediaBrowser.js defines a callback that the thumbnail images call when they load,
// which could happen as soon as they render.
Style.Require("jQueryUI_Orchard").AtHead();
Script.Require("jQueryUI_Tabs").AtHead();
SetMeta("X-UA-Compatible", "IE=edge,chrome=1");
Style.Include("~/themes/theadmin/styles/site.css");
Style.Include("~/themes/theadmin/styles/ie.css").UseCondition("lte IE 8").SetAttribute("media", "screen, projection");
Style.Include("~/themes/theadmin/styles/ie6.css").UseCondition("lte IE 6").SetAttribute("media", "screen, projection");
Script.Include("ContentPicker.js");
Style.Include("ContentPicker.css");
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>@T("Select Content Item")</title>
@Display.Metas()
@Display.HeadScripts()
@Display.HeadLinks()
@Display.StyleSheetLinks()
</head>
<body id="orchard-content-picker">
<script type="text/javascript">
$.extend({
contentPicker: {}
});
$.contentPicker.cannotPerformMsg="@T("Cannot perform requested operation")";
$.contentPicker.accessDeniedMsg="@T("Authentication timed-out: please log-on again")";
$.contentPicker.logonUrl="@Url.Action("LogOn", new { Controller = "Account", Area = "Orchard.Users" })";
</script>
@Display.ContentPickerNavigation()
<div class="content-picker-tab">
@Display(Model.Tab)
</div>
@using(Script.Foot()) {
<script type="text/javascript">
//<![CDATA[
$(".select").on("click", function () {
try {
var container = $(this).closest("[data-id]");
var result = {
id: container.attr("data-id"),
displayText: container.attr("data-display-text"),
editLink: container.attr("data-edit-link"),
displayLink: container.attr("data-display-link")
};
window.opener.jQuery[query("callback")](result);
} catch (ex) {
alert($.contentPicker.cannotPerformMsg);
alert(ex);
}
window.close();
});
$("ul a").each(function () {
var self = $(this);
var link = self.attr("href");
// if the link doesn't define the "callback" query string, then append it
// n.b., pagers already append it
if (query("callback", link).length == 0) {
link = link + "?callback=" + query("callback");
self.attr("href", link);
}
});
// get a querystring value
function query(name, querystring) {
name = name.toLowerCase();
var search = querystring || location.search;
var parts = search.replace("?", "").replace("#", "").split("&");
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
var eqIndex = part.indexOf("=");
if (eqIndex !== -1 && part.substr(0, eqIndex).toLowerCase() === name) {
return part.substr(eqIndex + 1);
}
}
return null;
}
//]]>
</script>
}
@Display.FootScripts()
</body>
</html>

View File

@@ -0,0 +1,20 @@
@model Orchard.ContentPicker.Settings.ContentPickerFieldSettings
<fieldset>
<div>
@Html.CheckBoxFor(m => m.Required) <label for="@Html.FieldIdFor(m => m.Required)" class="forcheckbox">@T("A content item is required")</label>
<span class="hint">@T("Check to ensure the user is providing at least one content item.")</span>
</div>
</fieldset>
<fieldset>
<div>
@Html.CheckBoxFor(m => m.Multiple) <label for="@Html.FieldIdFor(m => m.Multiple)" class="forcheckbox">@T("Allow multiple content items")</label>
<span class="hint">@T("Check to allow user the to select multiple content items.")</span>
</div>
</fieldset>
<fieldset>
<label for="@Html.FieldIdFor(m => m.Hint)">@T("Help text")</label>
@Html.TextAreaFor(m => m.Hint, new { @class = "textMedium", rows = "5" } )
<span class="hint">@T("The help text is written under the field when authors are selecting content items.")</span>
@Html.ValidationMessageFor(m => m.Hint)
</fieldset>

View File

@@ -0,0 +1,110 @@
@model Orchard.ContentPicker.ViewModels.ContentPickerFieldViewModel
@using Orchard.Utility.Extensions;
@using Orchard.ContentPicker.Settings;
@{
Script.Require("ContentPicker").AtFoot();
Script.Require("jQueryUI_Sortable").AtFoot();
Style.Include("content-picker-admin.css");
var settings = Model.Field.PartFieldDefinition.Settings.GetModel<ContentPickerFieldSettings>();
}
<fieldset>
<label @if(settings.Required) { <text>class="required"</text> }>@Model.Field.DisplayName</label>
<div id="save-message-@Html.FieldIdFor(m => m.Field.Ids)" class="message message-Warning content-picker-message">@T("You need to save your changes.")</div>
<table id="content-picker-@Html.FieldIdFor(m => m.Field.Ids)" class="items content-picker" summary="@Model.Field.DisplayName">
<colgroup>
<col id="Col1" style="width:20px" />
<col id="Col2" />
<col id="Col3" />
</colgroup>
<thead>
<tr>
<th scope="col" >&nbsp;&darr;</th>
<th scope="col">@T("Content Item")</th>
<th scope="col">&nbsp;</th>
</tr>
</thead>
<tbody>
@foreach (var contentItem in Model.ContentItems) {
<tr>
<td>&nbsp;</td>
<td>
<span data-id="@contentItem.Id" data-fieldid="@Html.FieldIdFor(m => m.Field.Ids)" class="content-picker-item">@Html.ItemEditLink(contentItem)</span>
</td>
<td>
<span data-id="@contentItem.Id" class="content-picker-remove button grey">@T("Remove")</span>
</td>
</tr>
}
</tbody>
</table>
<span id="btn-@Html.FieldIdFor(m => m.Field.Ids)" class="button">@T("Add")</span>
@Html.HiddenFor(m => m.SelectedIds)
<span class="hint">@settings.Hint</span>
</fieldset>
@using (Script.Foot()) {
<script type="text/javascript">
//<![CDATA[
(function($) {
var required = @(settings.Required ? "true" : "false");
var multiple = @(settings.Multiple ? "true" : "false");
var addButton = $('#btn-@Html.FieldIdFor(m => m.Field.Ids)');
var @Html.FieldIdFor(m => m.Field.Ids)_Template = '<tr><td>&nbsp;</td><td><span data-id="{contentItemId}" data-fieldid="@Html.FieldIdFor(m => m.Field.Ids)" class="content-picker-item">{edit-link}</span></td><td><span data-id="{contentItemId}" class="content-picker-remove button grey">@T("Remove")</span></td></tr>';
var refreshIds = function() {
var id = $('#@Html.FieldIdFor(m => m.SelectedIds)');
id.val('');
$("span[data-fieldid = @Html.FieldIdFor(m => m.Field.Ids)]").each(function() {
id.val(id.val() + "," + $(this).attr("data-id"));
});
var itemsCount = $("span[data-fieldid = @Html.FieldIdFor(m => m.Field.Ids)]").length;
if(!multiple && itemsCount > 0) {
addButton.hide();
}
else {
addButton.show();
}
};
refreshIds();
addButton.click(function() {
addButton.trigger("orchard-admin-contentpicker-open", {
callback: function(data) {
var template = @Html.FieldIdFor(m => m.Field.Ids)_Template.replace( /\{contentItemId\}/g , data.id).replace( /\{edit-link\}/g , data.editLink);
var content = $(template);
$('#content-picker-@Html.FieldIdFor(m => m.Field.Ids) tbody').append(content);
refreshIds();
$('#save-message-@Html.FieldIdFor(m => m.Field.Ids)').show();
}
});
});
$('#content-picker-@Html.FieldIdFor(m => m.Field.Ids) .content-picker-remove').live("click", function() {
$(this).closest('tr').remove();
refreshIds();
$('#save-message-@Html.FieldIdFor(m => m.Field.Ids)').show();
});
$("#content-picker-@Html.FieldIdFor(m => m.Field.Ids) tbody").sortable({
handle: 'td:first',
stop: function(event, ui) {
refreshIds();
$('#save-message-@Html.FieldIdFor(m => m.Field.Ids)').show();
}
}).disableSelection();
})(jQuery);
//]]>
</script>
}

View File

@@ -0,0 +1,23 @@
@using Orchard.ContentPicker.Fields
@using Orchard.Utility.Extensions;
@{
var field = (ContentPickerField) Model.ContentField;
string name = field.DisplayName;
var contentItems = field.ContentItems;
}
<p class="content-picker-field content-picker-field-@name.HtmlClassify()">
<span class="name">@name:</span>
@if(contentItems.Any()) {
foreach(var contentItem in contentItems) {
<span class="value">@Html.ItemEditLink(contentItem)</span>
if(contentItem != contentItems.Last()) {
<span>,</span>
}
}
}
else {
<span class="value">@T("No content items.")</span>
}
</p>

View File

@@ -0,0 +1,23 @@
@using Orchard.ContentPicker.Fields
@using Orchard.Utility.Extensions;
@{
var field = (ContentPickerField) Model.ContentField;
string name = field.DisplayName;
var contentItems = field.ContentItems;
}
<p class="content-picker-field content-picker-field-@name.HtmlClassify()">
<span class="name">@name:</span>
@if(contentItems.Any()) {
foreach(var contentItem in contentItems) {
<span class="value">@Html.ItemDisplayLink(contentItem)</span>
if(contentItem != contentItems.Last()) {
<span>,</span>
}
}
}
else {
<span class="value">@T("No content items.")</span>
}
</p>

View File

@@ -0,0 +1,38 @@
@using Orchard.Core.Contents.ViewModels;
@{
var typeDisplayName = Model.TypeDisplayName;
var pageTitle = T("Recent Content");
if (!string.IsNullOrWhiteSpace(typeDisplayName)) {
pageTitle = T("Manage {0} Content", typeDisplayName);
}
Layout.Title = pageTitle;
}
<div class="manage">
</div>
@using (Html.BeginFormAntiForgeryPost()) {
<fieldset class="bulk-actions">
<label for="filterResults" class="bulk-filter">@T("Show")</label>
<select id="filterResults" name="Options.SelectedFilter">
@Html.SelectOption((string)Model.Options.SelectedFilter, "", T("any (show all)").ToString())
@foreach(var filterOption in Model.Options.FilterOptions) {
@Html.SelectOption((string)Model.Options.SelectedFilter, (string)filterOption.Key, (string)filterOption.Value)
}
</select>
<label for="orderResults" class="bulk-order">@T("Ordered by")</label>
<select id="orderResults" name="Options.OrderBy">
@Html.SelectOption((ContentsOrder)Model.Options.OrderBy, ContentsOrder.Created, T("recently created").ToString())
@Html.SelectOption((ContentsOrder)Model.Options.OrderBy, ContentsOrder.Modified, T("recently modified").ToString())
@Html.SelectOption((ContentsOrder)Model.Options.OrderBy, ContentsOrder.Published, T("recently published").ToString())
</select>
<button type="submit" name="submit.Filter" value="yes please">@T("Apply")</button>
</fieldset>
<fieldset class="contentItems bulk-items">
@Display(Model.ContentItems)
</fieldset>
@Display(Model.Pager)
}

View File

@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="webpages:Enabled" value="false" />
</appSettings>
<system.web>
<httpHandlers>
</httpHandlers>
<!--
Enabling request validation in view pages would cause validation to occur
after the input has already been processed by the controller. By default
MVC performs request validation before a controller processes the input.
To change this behavior apply the ValidateInputAttribute to a
controller or action.
-->
<pages
validateRequest="false"
pageParserFilterType="System.Web.Mvc.ViewTypeParserFilter, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
pageBaseType="System.Web.Mvc.ViewPage, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL"
userControlBaseType="System.Web.Mvc.ViewUserControl, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL">
<controls>
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35, processorArchitecture=MSIL" namespace="System.Web.Mvc" tagPrefix="mvc" />
</controls>
</pages>
</system.web>
<system.webServer>
<validation validateIntegratedModeConfiguration="false"/>
<handlers>
</handlers>
</system.webServer>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
<bindingRedirect oldVersion="2.0.0.0" newVersion="3.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -0,0 +1,41 @@
<?xml version="1.0"?>
<configuration>
<configSections>
<sectionGroup name="system.web.webPages.razor" type="System.Web.WebPages.Razor.Configuration.RazorWebSectionGroup, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35">
<remove name="host" />
<remove name="pages" />
<section name="host" type="System.Web.WebPages.Razor.Configuration.HostSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
<section name="pages" type="System.Web.WebPages.Razor.Configuration.RazorPagesSection, System.Web.WebPages.Razor, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" requirePermission="false" />
</sectionGroup>
</configSections>
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<pages pageBaseType="Orchard.Mvc.ViewEngines.Razor.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Routing" />
<add namespace="System.Web.WebPages" />
<add namespace="System.Linq"/>
<add namespace="System.Collections.Generic"/>
<add namespace="Orchard.Mvc.Html"/>
</namespaces>
</pages>
</system.web.webPages.razor>
<system.web>
<compilation targetFramework="4.0">
<assemblies>
<add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
<add assembly="System.Data.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/>
<add assembly="System.Web.Mvc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add assembly="System.Web.WebPages, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
</assemblies>
</compilation>
</system.web>
</configuration>