Compare commits

..

48 Commits

Author SHA1 Message Date
Claus f3b364022a bumping version for deploy. 2017-01-31 10:33:57 +01:00
Claus 56ae2b709d adding deploy entity types for forms. 2017-01-31 10:31:45 +01:00
Mads Rasmussen 0acc202740 Merge pull request #1712 from umbraco/temp-U4-9410
U4-9410 Hook up delete dialog for Code Files
2017-01-30 20:50:36 +01:00
Claus 4ad0c1bb89 cleaning usings. 2017-01-30 13:14:19 +01:00
Claus 67da15330e U4-9410 Hook up delete dialog for Code Files
delete dialogs for partialviews, partialviewmacros and scripts.
+ fixed codefileResource.
+ fixed comments in MemberTypesDeleteController.
2017-01-30 11:56:30 +01:00
Shannon a458e7daf0 Merge remote-tracking branch 'refs/remotes/origin/dev-v7' into dev-v7.6
# Conflicts:
#	build/UmbracoVersion.txt
#	src/SolutionInfo.cs
#	src/Umbraco.Core/Configuration/UmbracoVersion.cs
#	src/Umbraco.Web.UI/Umbraco.Web.UI.csproj
#	src/Umbraco.Web/Umbraco.Web.csproj
2017-01-30 16:55:12 +11:00
Stephan d85cb9e15e Fix FileSystemProviderManagerTests 2017-01-28 10:04:43 +01:00
Mads Rasmussen 19a4cf774b Merge pull request #1680 from umbraco/temp-U4-9352
Implements CodeFileController
2017-01-27 10:49:07 +01:00
Mads Rasmussen ab00ba8a12 Merge branch 'dev-v7.6' into temp-U4-9352
# Conflicts:
#	src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js
2017-01-27 10:39:31 +01:00
Claus 57e2a19023 changing parameter name for IGridCellValueConnector.IsConnector. 2017-01-27 10:16:01 +01:00
Claus daa80a7bf6 Merge pull request #1709 from umbraco/temp-U4-9435-2
U4-9435 Fix breaking change made in U4-9312
2017-01-27 09:05:47 +01:00
Shannon 8152e7cb92 Doh, forgot a file change for tests - fixes build 2017-01-27 16:55:29 +11:00
Shannon Deminick adccb4161e Merge pull request #1707 from umbraco/temp-u4-9438
U4-9438 - fix nasty issue with reading properties in VersionableRepos…
2017-01-27 16:52:10 +11:00
Shannon 72a0187295 Writes unit test to confirm the correct property data iteration in GetPropertyCollection 2017-01-27 16:50:44 +11:00
Shannon 24d6287918 Adds string overloads just in case 2017-01-27 16:21:01 +11:00
Shannon 55900a3464 Updates MediaController and MediaTypeController to have the same method signature supporting both INT and GUID with a custom action selector to handle the ambiguity 2017-01-27 16:09:35 +11:00
Sebastiaan Janssen 36e9bb4d78 Merge pull request #1705 from umbraco/temp-v758-recyclebin-fix
Fixes U4-9437
2017-01-26 17:31:31 +01:00
Sebastiaan Janssen 639d04381e Bump version 2017-01-26 14:48:19 +01:00
Warren Buckley 2465eb233e Adds in same workaround as other trees like DataTypes & DocTypes where Folders/Containers set an inline piece of JS that does nothing & returns 2017-01-26 11:32:13 +00:00
Stephan 7f905bce0e U4-9438 - fix nasty issue with reading properties in VersionableRepositoryBase 2017-01-26 11:49:52 +01:00
Niels Hartvig 205ea22daa Fixes U4-9437 2017-01-25 17:34:34 +01:00
Warren Buckley f3e78e990f Fix up failing unit test for the number of Trees found - this has gone down due this test is for implementing interface ITree which is the legacy type of Trees, where the new PartialViews & PartialView Macros now use TreeController 2017-01-20 09:52:23 +00:00
Warren Buckley 2f7b532209 Adds in code summaries for C# doc generation. Removes unused references & some tidying up (mostly sensible R# suggestions) 2017-01-19 15:43:29 +00:00
Warren Buckley abebb40461 Update some copy/paste typos in code comments 2017-01-19 15:18:12 +00:00
Warren Buckley 3b54db9b49 Update main templat editor to use a localised notification 2017-01-19 15:14:46 +00:00
Warren Buckley 1defc1e0e4 Changed key for saving scripts - as it mentioned 'script view' Whats that?! So changed to more generic file 2017-01-19 15:14:14 +00:00
Warren Buckley a7e626f4a7 Localisation notification message for saving scripts 2017-01-19 15:05:05 +00:00
Warren Buckley 04f6a01196 Forgot to remove the old notification - as was display two 2017-01-19 15:04:36 +00:00
Warren Buckley ef64fbb839 Same change for the partialview editor - to display a localized success notifcation 2017-01-19 15:01:14 +00:00
Warren Buckley 2424a1c04b Adds in localised sucess notification when saving partial view macros 2017-01-19 14:57:23 +00:00
Warren Buckley c8c7de065d Forgot to update the var name in codeFileResource 2017-01-19 14:56:25 +00:00
Warren Buckley 61d71d0552 Update codeFileResource docs 2017-01-19 14:40:10 +00:00
Mads Rasmussen a09755ef7f remove unused code in partial view editor 2017-01-19 11:19:34 +01:00
Niels Hartvig afae06a866 Adds support for icons and encoding of directories for Code files 2017-01-19 10:41:08 +01:00
Niels Hartvig 051af5c48e Wire up the editors and controllers 2017-01-19 09:49:27 +01:00
Niels Hartvig 1fb05fcca7 Merge remote-tracking branch 'refs/remotes/origin/temp-file-editors' into temp-U4-9352 2017-01-19 08:45:23 +01:00
Mads Rasmussen 49d24dada7 add partial views macros editor 2017-01-18 15:39:08 +01:00
Mads Rasmussen ef9dd6d803 add scripts editor 2017-01-18 15:38:40 +01:00
Niels Hartvig a162f21a70 Fixing missing mapping - let the green build lights appear 2017-01-16 19:49:57 +01:00
Niels Hartvig ace111aefb Adding support for get, save and rename to new Partial View editor 2017-01-16 16:50:22 +01:00
Niels Hartvig dfe3798aab More merge 2017-01-16 09:59:00 +01:00
Niels Hartvig 975535a9e2 Merge of insert overlay view 2017-01-13 13:33:55 +01:00
Niels Hartvig e7f7631d1e Fixes merge conflicts (needs updated ng-if in insert overlay) 2017-01-13 13:22:43 +01:00
Mads Rasmussen 23304aca0b add unit tests for template helper 2017-01-10 15:16:16 +01:00
Mads Rasmussen 845dd4673c Add client side file for partial view editor 2017-01-10 15:15:56 +01:00
Niels Hartvig 14e402878a Merge conflict fixes 2017-01-10 14:49:19 +01:00
Niels Hartvig 5a2687d520 Merge of temp-template-editor 2017-01-10 14:47:33 +01:00
Niels Hartvig 4c330c8654 Implements CodeFileController 2017-01-09 22:54:28 +01:00
58 changed files with 2509 additions and 840 deletions
+1 -1
View File
@@ -1,3 +1,3 @@
# Usage: on line 2 put the release version, on line 3 put the version comment (example: beta)
7.6.0
alpha055
alpha056
+1 -1
View File
@@ -12,4 +12,4 @@ using System.Resources;
[assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyFileVersion("7.6.0")]
[assembly: AssemblyInformationalVersion("7.6.0-alpha055")]
[assembly: AssemblyInformationalVersion("7.6.0-alpha056")]
@@ -24,7 +24,7 @@ namespace Umbraco.Core.Configuration
/// Gets the version comment (like beta or RC).
/// </summary>
/// <value>The version comment.</value>
public static string CurrentComment { get { return "alpha055"; } }
public static string CurrentComment { get { return "alpha056"; } }
// Get the version of the umbraco.dll by looking at a class in that dll
// Had to do it like this due to medium trust issues, see: http://haacked.com/archive/2010/11/04/assembly-location-and-medium-trust.aspx
+7 -1
View File
@@ -112,9 +112,15 @@
public const string Languages = "languages";
public const string PartialViews = "partialViews";
public const string PartialViewMacros = "partialViewMacros";
public const string Scripts = "scripts";
//TODO: Fill in the rest!
}
}
}
}
+18 -1
View File
@@ -4,7 +4,7 @@ using Umbraco.Core.Models;
namespace Umbraco.Core
{
public static partial class Constants
{
@@ -37,6 +37,11 @@ namespace Umbraco.Core
public const string MemberGroup = "member-group";
public const string RelationType = "relation-type";
// forms
public const string FormsForm = "forms-form";
public const string FormsWorkflow = "forms-workflow";
public const string FormsRecord = "forms-record";
// string entity types
@@ -82,6 +87,12 @@ namespace Umbraco.Core
return Stylesheet;
case UmbracoObjectTypes.RelationType:
return RelationType;
case UmbracoObjectTypes.FormsForm:
return FormsForm;
case UmbracoObjectTypes.FormsWorkflow:
return FormsWorkflow;
case UmbracoObjectTypes.FormsRecord:
return FormsRecord;
}
throw new NotSupportedException(string.Format("UmbracoObjectType \"{0}\" does not have a matching EntityType.", umbracoObjectType));
}
@@ -118,6 +129,12 @@ namespace Umbraco.Core
return UmbracoObjectTypes.Stylesheet;
case RelationType:
return UmbracoObjectTypes.RelationType;
case FormsForm:
return UmbracoObjectTypes.FormsForm;
case FormsWorkflow:
return UmbracoObjectTypes.FormsWorkflow;
case FormsRecord:
return UmbracoObjectTypes.FormsRecord;
}
throw new NotSupportedException(
string.Format("EntityType \"{0}\" does not have a matching UmbracoObjectType.", entityType));
+30
View File
@@ -162,6 +162,36 @@ namespace Umbraco.Core
/// Guid for a relation type.
/// </summary>
public static readonly Guid RelationTypeGuid = new Guid(RelationType);
/// <summary>
/// Guid for a Forms Form.
/// </summary>
public const string FormsForm = "F5A9F787-6593-46F0-B8FF-BFD9BCA9F6BB";
/// <summary>
/// Guid for a Forms Form.
/// </summary>
public static readonly Guid FormsFormGuid = new Guid(FormsForm);
/// <summary>
/// Guid for a Forms Workflow.
/// </summary>
public const string FormsWorkflow = "42D7BF9B-A362-4FEE-B45A-674D5C064B70";
/// <summary>
/// Guid for a Forms Workflow.
/// </summary>
public static readonly Guid FormsWorkflowGuid = new Guid(FormsWorkflow);
/// <summary>
/// Guid for a Forms Record.
/// </summary>
public const string FormsRecord = "CFED6CE4-9359-443E-9977-9956FEB1D867";
/// <summary>
/// Guid for a Forms Record.
/// </summary>
public static readonly Guid FormsRecordGuid = new Guid(FormsRecord);
}
}
}
@@ -11,12 +11,12 @@ namespace Umbraco.Core.Deploy
public interface IGridCellValueConnector
{
/// <summary>
/// Gets a value indicating whether the connector supports a specified grid editor alias.
/// Gets a value indicating whether the connector supports a specified grid editor view.
/// </summary>
/// <param name="alias">The grid editor alias.</param>
/// <remarks>A value indicating whether the connector supports the grid editor alias.</remarks>
/// <remarks>Note that <paramref name="alias" /> can be string.Empty to indicate the "default" connector.</remarks>
bool IsConnector(string alias);
/// <param name="view">The grid editor view. It needs to be the view instead of the alias as the view is really what identifies what kind of connector should be used. Alias can be anything and you can have multiple different aliases using the same kind of view.</param>
/// <remarks>A value indicating whether the connector supports the grid editor view.</remarks>
/// <remarks>Note that <paramref name="view" /> can be string.Empty to indicate the "default" connector.</remarks>
bool IsConnector(string view);
/// <summary>
/// Gets the value to be deployed from the control value as a string.
+22 -1
View File
@@ -143,6 +143,27 @@ namespace Umbraco.Core.Models
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.RelationType)]
[FriendlyName("Relation Type")]
RelationType
RelationType,
/// <summary>
/// Forms Form
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.FormsForm)]
[FriendlyName("Form")]
FormsForm,
/// <summary>
/// Forms Workflow
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.FormsWorkflow)]
[FriendlyName("Workflow")]
FormsWorkflow,
/// <summary>
/// Forms Record
/// </summary>
[UmbracoObjectType(Constants.ObjectTypes.FormsRecord)]
[FriendlyName("Record")]
FormsRecord
}
}
@@ -164,7 +164,7 @@ namespace Umbraco.Core.Persistence.Repositories
//each order by param needs to be in a bracket! see: https://github.com/toptensoftware/PetaPoco/issues/177
query.OrderBy(orderBy == null
? "(umbracoNode.id)"
? "(umbracoNode.id)"
: string.Join(",", orderBy.Select(x => string.Format("({0})", SqlSyntax.GetQuotedColumnName(x)))));
var pagedResult = Database.Page<ContentXmlDto>(pageIndex + 1, pageSize, query);
@@ -462,14 +462,14 @@ namespace Umbraco.Core.Persistence.Repositories
totalRecords = Convert.ToInt32(pagedResult.TotalItems);
//NOTE: We need to check the actual items returned, not the 'totalRecords', that is because if you request a page number
// that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in
// that doesn't actually have any data on it, the totalRecords will still indicate there are records but there are none in
// the pageResult, then the GetAll will actually return ALL records in the db.
if (pagedResult.Items.Any())
{
//Crete the inner paged query that was used above to get the paged result, we'll use that as the inner sub query
var args = sqlNodeIdsWithSort.Arguments;
string sqlStringCount, sqlStringPage;
Database.BuildPageQueries<TDto>(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage);
Database.BuildPageQueries<TDto>(pageIndex * pageSize, pageSize, sqlNodeIdsWithSort.SQL, ref args, out sqlStringCount, out sqlStringPage);
//We need to make this FULL query an inner join on the paged ID query
var splitQuery = sqlQueryFull.SQL.Split(new[] { "WHERE " }, StringSplitOptions.None);
@@ -503,7 +503,7 @@ namespace Umbraco.Core.Persistence.Repositories
{
if (documentDefs.Count == 0) return new Dictionary<int, PropertyCollection>();
//we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use
//we need to parse the original SQL statement and reduce the columns to just cmsContent.nodeId, cmsContentVersion.VersionId so that we can use
// the statement to go get the property data for all of the items by using an inner join
var parsedOriginalSql = "SELECT {0} " + docSql.SQL.Substring(docSql.SQL.IndexOf("FROM", StringComparison.Ordinal));
//now remove everything from an Orderby clause and beyond
@@ -514,7 +514,7 @@ namespace Umbraco.Core.Persistence.Repositories
//This retrieves all pre-values for all data types that are referenced for all property types
// that exist in the data set.
//Benchmarks show that eagerly loading these so that we can lazily read the property data
//Benchmarks show that eagerly loading these so that we can lazily read the property data
// below (with the use of Query intead of Fetch) go about 30% faster, so we'll eagerly load
// this now since we cannot execute another reader inside of reading the property data.
var preValsSql = new Sql(@"SELECT a.id, a.value, a.sortorder, a.alias, a.datatypeNodeId
@@ -533,11 +533,11 @@ WHERE EXISTS(
//It's Important with the sort order here! We require this to be sorted by node id,
// this is required because this data set can be huge depending on the page size. Due
// to it's size we need to be smart about iterating over the property values to build
// the document. Before we used to use Linq to get the property data for a given content node
// to it's size we need to be smart about iterating over the property values to build
// the document. Before we used to use Linq to get the property data for a given content node
// and perform a Distinct() call. This kills performance because that would mean if we had 7000 nodes
// and on each iteration we will perform a lookup on potentially 100,000 property rows against the node
// id which turns out to be a crazy amount of iterations. Instead we know it's sorted by this value we'll
// id which turns out to be a crazy amount of iterations. Instead we know it's sorted by this value we'll
// keep an index stored of the rows being read so we never have to re-iterate the entire data set
// on each document iteration.
var propSql = new Sql(@"SELECT cmsPropertyData.*
@@ -563,6 +563,7 @@ ORDER BY contentNodeId, propertytypeid
//keep track of the current property data item being enumerated
var propertyDataSetEnumerator = allPropertyData.GetEnumerator();
var hasCurrent = false; // initially there is no enumerator.Current
try
{
@@ -570,7 +571,7 @@ ORDER BY contentNodeId, propertytypeid
// which allows us to more efficiently iterate over the large data set of property values
foreach (var def in documentDefs.OrderBy(x => x.Id))
{
//get the resolved proeprties from our local cache, or resolve them and put them in cache
// get the resolved properties from our local cache, or resolve them and put them in cache
PropertyType[] compositionProperties;
if (resolvedCompositionProperties.ContainsKey(def.Composition.Id))
{
@@ -582,28 +583,20 @@ ORDER BY contentNodeId, propertytypeid
resolvedCompositionProperties[def.Composition.Id] = compositionProperties;
}
// assemble the dtos for this def
// use the available enumerator.Current if any else move to next
var propertyDataDtos = new List<PropertyDataDto>();
//Check if there is a current enumerated item and check if we match and add it
if (propertyDataSetEnumerator.Current != null)
{
if (propertyDataSetEnumerator.Current.NodeId == def.Id)
{
propertyDataDtos.Add(propertyDataSetEnumerator.Current);
}
}
//Move to the next position, see if we match and add it, if not exit
while (propertyDataSetEnumerator.MoveNext())
while (hasCurrent || propertyDataSetEnumerator.MoveNext())
{
if (propertyDataSetEnumerator.Current.NodeId == def.Id)
{
hasCurrent = false; // enumerator.Current is not available
propertyDataDtos.Add(propertyDataSetEnumerator.Current);
}
else
{
//the node id has changed so we need to exit the loop,
//the enumerator position will be maintained
break;
hasCurrent = true; // enumerator.Current is available for another def
break; // no more propertyDataDto for this def
}
}
@@ -5,7 +5,6 @@ using System.Web;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Umbraco.Core.Configuration;
using Umbraco.Core.Configuration.Grid;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Models.PublishedContent;
@@ -1382,6 +1382,7 @@ namespace Umbraco.Core.Services
_mediaFileSystem.GenerateThumbnails(filestream, filepath, propertyType);
}
}
#region Event Handlers
@@ -1,10 +1,10 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Moq;
using NUnit.Framework;
using Umbraco.Core;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Core.Profiling;
using Umbraco.Tests.TestHelpers;
namespace Umbraco.Tests.IO
@@ -18,6 +18,9 @@ namespace Umbraco.Tests.IO
//init the config singleton
var config = SettingsForTests.GetDefault();
SettingsForTests.ConfigureSettings(config);
// media fs wants this
ApplicationContext.Current = new ApplicationContext(CacheHelper.CreateDisabledCacheHelper(), new ProfilingLogger(Mock.Of<ILogger>(), Mock.Of<IProfiler>()));
}
[Test]
@@ -42,7 +45,6 @@ namespace Umbraco.Tests.IO
Assert.Throws<InvalidOperationException>(() => FileSystemProviderManager.Current.GetFileSystemProvider<InvalidTypedFileSystem>());
}
/// <summary>
/// Used in unit tests, for a typed file system we need to inherit from FileSystemWrapper and they MUST have a ctor
/// that only accepts a base IFileSystem object
@@ -67,6 +67,66 @@ namespace Umbraco.Tests.Persistence.Repositories
return repository;
}
/// <summary>
/// This tests the regression issue of U4-9438
/// </summary>
/// <remarks>
/// The problem was the iteration of the property data in VersionableRepositoryBase when a content item
/// in the list actually doesn't have any property types, it would still skip over a property row.
/// To test, we have 3 content items, the first has properties, the second doesn't and the third does.
/// </remarks>
[Test]
public void Property_Data_Assigned_Correctly()
{
// Arrange
var provider = new PetaPocoUnitOfWorkProvider(Logger);
var unitOfWork = provider.GetUnitOfWork();
ContentTypeRepository contentTypeRepository;
var allContent = new List<IContent>();
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
var emptyContentType = MockedContentTypes.CreateBasicContentType();
var hasPropertiesContentType = MockedContentTypes.CreateSimpleContentType("umbTextpage1", "Textpage");
var content1 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
var content2 = MockedContent.CreateBasicContent(emptyContentType);
var content3 = MockedContent.CreateSimpleContent(hasPropertiesContentType);
// Act
contentTypeRepository.AddOrUpdate(emptyContentType);
contentTypeRepository.AddOrUpdate(hasPropertiesContentType);
repository.AddOrUpdate(content1);
repository.AddOrUpdate(content2);
repository.AddOrUpdate(content3);
unitOfWork.Commit();
allContent.Add(content1);
allContent.Add(content2);
allContent.Add(content3);
}
// Assert
using (var repository = CreateRepository(unitOfWork, out contentTypeRepository))
{
//this will cause the GetPropertyCollection to execute and we need to ensure that
// all of the properties and property types are all correct
var result = repository.GetAll(allContent.Select(x => x.Id).ToArray()).ToArray();
foreach (var content in result)
{
foreach (var contentProperty in content.Properties)
{
//prior to the fix, the 2nd document iteration in the GetPropertyCollection would have caused
//the enumerator to move forward past the first property of the 3rd document which would have
//ended up not assiging a property to the 3rd document. This would have ended up with the 3rd
//document still having 3 properties but the last one would not have been assigned an identity
//because the property data would not have been assigned.
Assert.IsTrue(contentProperty.HasIdentity);
}
}
}
}
[Test]
public void Rebuild_Xml_Structures_With_Non_Latest_Version()
{
@@ -297,7 +297,7 @@ namespace Umbraco.Tests.Plugins
public void Resolves_Trees()
{
var trees = _manager.ResolveTrees();
Assert.AreEqual(39, trees.Count());
Assert.AreEqual(37, trees.Count());
}
[Test]
@@ -6,9 +6,18 @@ namespace Umbraco.Tests.TestHelpers.Entities
{
public class MockedContent
{
public static Content CreateBasicContent(IContentType contentType)
{
var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
content.ResetDirtyProperties(false);
return content;
}
public static Content CreateSimpleContent(IContentType contentType)
{
var content = new Content("Home", -1, contentType) { Language = "en-US", Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
var content = new Content("Home", -1, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
object obj =
new
{
@@ -26,7 +35,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateSimpleContent(IContentType contentType, string name, int parentId)
{
var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 };
var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 };
object obj =
new
{
@@ -44,7 +53,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateSimpleContent(IContentType contentType, string name, IContent parent)
{
var content = new Content(name, parent, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 };
var content = new Content(name, parent, contentType) { CreatorId = 0, WriterId = 0 };
object obj =
new
{
@@ -62,7 +71,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateTextpageContent(IContentType contentType, string name, int parentId)
{
var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0};
var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0};
object obj =
new
{
@@ -81,7 +90,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateSimpleContentWithSpecialDatabaseTypes(IContentType contentType, string name, int parentId, string decimalValue, string intValue, DateTime datetimeValue)
{
var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 };
var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 };
object obj = new
{
decimalProperty = decimalValue,
@@ -96,7 +105,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
public static Content CreateAllTypesContent(IContentType contentType, string name, int parentId)
{
var content = new Content("Random Content Name", parentId, contentType) { Language = "en-US", Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
var content = new Content("Random Content Name", parentId, contentType) { Level = 1, SortOrder = 1, CreatorId = 0, WriterId = 0 };
content.SetValue("isTrue", true);
content.SetValue("number", 42);
@@ -130,7 +139,7 @@ namespace Umbraco.Tests.TestHelpers.Entities
for (int i = 0; i < amount; i++)
{
var name = "Textpage No-" + i;
var content = new Content(name, parentId, contentType) { Language = "en-US", CreatorId = 0, WriterId = 0 };
var content = new Content(name, parentId, contentType) { CreatorId = 0, WriterId = 0 };
object obj =
new
{
@@ -7,6 +7,13 @@ namespace Umbraco.Tests.TestHelpers.Entities
{
public class MockedContentTypes
{
/// <summary>
/// Creates a content type without any properties
/// </summary>
/// <param name="alias"></param>
/// <param name="name"></param>
/// <param name="parent"></param>
/// <returns></returns>
public static ContentType CreateBasicContentType(string alias = "basePage", string name = "Base Page",
ContentType parent = null)
{
@@ -0,0 +1,183 @@
/**
* @ngdoc service
* @name umbraco.resources.codefileResource
* @description Loads in data for files that contain code such as js scripts, partial views and partial view macros
**/
function codefileResource($q, $http, umbDataFormatter, umbRequestHelper) {
return {
/**
* @ngdoc method
* @name umbraco.resources.codefileResource#getByPath
* @methodOf umbraco.resources.codefileResource
*
* @description
* Gets a codefile item with a given path
*
* ##usage
* <pre>
* codefileResource.getByPath('scripts', 'oooh-la-la.js')
* .then(function(codefile) {
* alert('its here!');
* });
* </pre>
*
* <pre>
* codefileResource.getByPath('partialView', 'Grid%2fEditors%2fBase.cshtml')
* .then(function(codefile) {
* alert('its here!');
* });
* </pre>
*
* @param {type} the type of script (partialView, partialViewMacro, script)
* @param {virtualpath} the virtual path of the script
* @returns {Promise} resourcePromise object.
*
*/
getByPath: function (type, virtualpath) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"codeFileApiBaseUrl",
"GetByPath",
[{ type: type }, {virtualPath: virtualpath }])),
"Failed to retrieve data for " + type + " from virtual path " + virtualpath);
},
/**
* @ngdoc method
* @name umbraco.resources.codefileResource#getByAlias
* @methodOf umbraco.resources.codefileResource
*
* @description
* Gets a template item with a given alias
*
* ##usage
* <pre>
* codefileResource.getByAlias("upload")
* .then(function(template) {
* alert('its here!');
* });
* </pre>
*
* @param {String} alias Alias of template to retrieve
* @returns {Promise} resourcePromise object.
*
*/
getByAlias: function (alias) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"templateApiBaseUrl",
"GetByAlias",
[{ alias: alias }])),
"Failed to retrieve data for template with alias: " + alias);
},
/**
* @ngdoc method
* @name umbraco.resources.codefileResource#getScaffold
* @methodOf umbraco.resources.codefileResource
*
* @description
* Returns a scaffold of an empty codefile item
*
* The scaffold is used to build editors for code file editors that has not yet been populated with data.
*
* ##usage
* <pre>
* codefileResource.getScaffold()
* .then(function(template) {
* alert('its here!');
* });
* </pre>
*
* @returns {Promise} resourcePromise object containing the codefile scaffold.
*
*/
getScaffold: function (id) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"templateApiBaseUrl",
"GetScaffold",
[{ id: id }])),
"Failed to retrieve data for empty template");
},
/**
* @ngdoc method
* @name umbraco.resources.codefileResource#deleteByPath
* @methodOf umbraco.resources.codefileResource
*
* @description
* Deletes a codefile with a given type & path
*
* ##usage
* <pre>
* codefileResource.deleteByPath('scripts', 'oooh-la-la.js')
* .then(function() {
* alert('its gone!');
* });
* </pre>
*
* <pre>
* codefileResource.deleteByPath('partialViews', 'Grid%2fEditors%2fBase.cshtml')
* .then(function() {
* alert('its gone!');
* });
* </pre>
*
* @param {type} the type of script (partialViews, partialViewMacros, scripts)
* @param {virtualpath} the virtual path of the script
* @returns {Promise} resourcePromise object.
*
*/
deleteByPath: function (type, virtualpath) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"codeFileApiBaseUrl",
"Delete",
[{ type: type }, { virtualPath: virtualpath}])),
"Failed to delete item: " + virtualpath);
},
/**
* @ngdoc method
* @name umbraco.resources.codefileResource#save
* @methodOf umbraco.resources.codefileResource
*
* @description
* Saves or update a codeFile
*
* ##usage
* <pre>
* codefileResource.save(codeFile)
* .then(function(codeFile) {
* alert('its saved!');
* });
* </pre>
*
* @param {Object} template object to save
* @returns {Promise} resourcePromise object.
*
*/
save: function (codeFile) {
return umbRequestHelper.resourcePromise(
$http.post(
umbRequestHelper.getApiUrl(
"codeFileApiBaseUrl",
"PostSave"),
codeFile),
"Failed to save data for code file " + codeFile.virtualPath);
}
};
}
angular.module("umbraco.resources").factory("codefileResource", codefileResource);
@@ -19,14 +19,14 @@ function memberTypeResource($q, $http, umbRequestHelper, umbDataFormatter) {
_.each(filterContentTypes, function (item) {
query += "filterContentTypes=" + item + "&";
});
// if filterContentTypes array is empty we need a empty variable in the querystring otherwise the service returns a error
// if filterContentTypes array is empty we need a empty variable in the querystring otherwise the service returns a error
if (filterContentTypes.length === 0) {
query += "filterContentTypes=&";
}
_.each(filterPropertyTypes, function (item) {
query += "filterPropertyTypes=" + item + "&";
});
// if filterPropertyTypes array is empty we need a empty variable in the querystring otherwise the service returns a error
// if filterPropertyTypes array is empty we need a empty variable in the querystring otherwise the service returns a error
if (filterPropertyTypes.length === 0) {
query += "filterPropertyTypes=&";
}
@@ -0,0 +1,57 @@
(function() {
'use strict';
function templateHelperService() {
//crappy hack due to dictionary items not in umbracoNode table
function getInsertDictionarySnippet(nodeName) {
return "@Umbraco.GetDictionaryValue(\"" + nodeName + "\")";
}
function getInsertPartialSnippet(nodeName) {
return "@Html.Partial(\"" + nodeName + "\")";
}
function getQuerySnippet(queryExpression) {
var code = "\n@{\n" + "\tvar selection = " + queryExpression + ";\n}\n";
code += "<ul>\n" +
"\t@foreach(var item in selection){\n" +
"\t\t<li>\n" +
"\t\t\t<a href=\"@item.Url\">@item.Name</a>\n" +
"\t\t</li>\n" +
"\t}\n" +
"</ul>\n\n";
return code;
}
function getRenderBodySnippet() {
return "@RenderBody()";
}
function getRenderSectionSnippet(sectionName, mandatory) {
return "@RenderSection(\"" + sectionName + "\", " + mandatory + ")";
}
function getAddSectionSnippet(sectionName) {
return "@section " + sectionName + "\r\n{\r\n\r\n\t{0}\r\n\r\n}\r\n";
}
////////////
var service = {
getInsertDictionarySnippet: getInsertDictionarySnippet,
getInsertPartialSnippet: getInsertPartialSnippet,
getQuerySnippet: getQuerySnippet,
getRenderBodySnippet: getRenderBodySnippet,
getRenderSectionSnippet: getRenderSectionSnippet,
getAddSectionSnippet: getAddSectionSnippet
};
return service;
}
angular.module('umbraco.services').factory('templateHelper', templateHelperService);
})();
@@ -1,61 +1,46 @@
<div ng-controller="Umbraco.Overlays.InsertOverlay as vm">
<div class="umb-insert-code-boxes">
<div class="umb-insert-code-box" ng-click="vm.openPageFieldOverlay()">
<div ng-if="model.allowedTypes.umbracoField" class="umb-insert-code-box" ng-click="vm.openPageFieldOverlay()">
<div class="umb-insert-code-box__title"><localize key="template_insertPageField" /></div>
<div class="umb-insert-code-box__description"><localize key="template_insertPageFieldDesc" /></div>
</div>
<div class="umb-insert-code-box" ng-click="vm.openPartialOverlay()">
<div ng-if="model.allowedTypes.partial" class="umb-insert-code-box" ng-click="vm.openPartialOverlay()">
<div class="umb-insert-code-box__title"><localize key="template_insertPartialView" /></div>
<div class="umb-insert-code-box__description">
<localize key="template_insertPartialViewDesc" />
<localize key="template_insertPartialViewDesc" />
</div>
</div>
<div class="umb-insert-code-box" ng-click="vm.openMacroPicker()">
<div ng-if="model.allowedTypes.macro" class="umb-insert-code-box" ng-click="vm.openMacroPicker()">
<div class="umb-insert-code-box__title"><localize key="template_insertMacro" /></div>
<div class="umb-insert-code-box__description">
<localize key="template_insertMacroDesc" />
</div>
</div>
<div class="umb-insert-code-box" ng-click="vm.openDictionaryItemOverlay()">
<div ng-if="model.allowedTypes.dictionary" class="umb-insert-code-box" ng-click="vm.openDictionaryItemOverlay()">
<div class="umb-insert-code-box__title"><localize key="template_insertDictionaryItem" /></div>
<div class="umb-insert-code-box__description"><localize key="template_insertDictionaryItemDesc" /></div>
</div>
</div>
<umb-overlay
ng-if="vm.macroPickerOverlay.show"
model="vm.macroPickerOverlay"
view="vm.macroPickerOverlay.view"
position="right">
</umb-overlay>
<umb-overlay
ng-if="vm.pageFieldOverlay.show"
model="vm.pageFieldOverlay"
position="right"
view="vm.pageFieldOverlay.view">
</umb-overlay>
<umb-overlay
ng-if="vm.dictionaryItemOverlay.show"
model="vm.dictionaryItemOverlay"
position="right"
view="vm.dictionaryItemOverlay.view">
</umb-overlay>
<umb-overlay
ng-if="vm.partialItemOverlay.show"
model="vm.partialItemOverlay"
view="vm.partialItemOverlay.view"
position="right">
</umb-overlay>
<umb-overlay ng-if="vm.macroPickerOverlay.show"
model="vm.macroPickerOverlay"
view="vm.macroPickerOverlay.view"
position="right">
</umb-overlay>
<umb-overlay ng-if="vm.pageFieldOverlay.show"
model="vm.pageFieldOverlay"
position="right"
view="vm.pageFieldOverlay.view">
</umb-overlay>
<umb-overlay ng-if="vm.dictionaryItemOverlay.show"
model="vm.dictionaryItemOverlay"
position="right"
view="vm.dictionaryItemOverlay.view">
</umb-overlay>
<umb-overlay ng-if="vm.partialItemOverlay.show"
model="vm.partialItemOverlay"
view="vm.partialItemOverlay.view"
position="right">
</umb-overlay>
</div>
@@ -1,10 +1,10 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.DocumentType.DeleteController
* @name Umbraco.Editors.MemberTypes.DeleteController
* @function
*
* @description
* The controller for deleting content
* The controller for deleting member types
*/
function MemberTypesDeleteController($scope, memberTypeResource, treeService, navigationService) {
@@ -0,0 +1,34 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.PartialViewMacros.DeleteController
* @function
*
* @description
* The controller for deleting partial view macros
*/
function PartialViewMacrosDeleteController($scope, codefileResource, treeService, navigationService) {
$scope.performDelete = function() {
//mark it for deletion (used in the UI)
$scope.currentNode.loading = true;
var virtualPath = $scope.currentNode.parentId + $scope.currentNode.name;
codefileResource.deleteByPath('partialViewMacros', virtualPath)
.then(function() {
$scope.currentNode.loading = false;
//get the root node before we remove it
var rootNode = treeService.getTreeRoot($scope.currentNode);
//TODO: Need to sync tree, etc...
treeService.removeNode($scope.currentNode);
navigationService.hideMenu();
});
};
$scope.cancel = function() {
navigationService.hideDialog();
};
}
angular.module("umbraco").controller("Umbraco.Editors.PartialViewMacros.DeleteController", PartialViewMacrosDeleteController);
@@ -0,0 +1,12 @@
<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.PartialViewMacros.DeleteController">
<div class="umb-dialog-body">
<p class="umb-abstract">
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong> ?
</p>
<umb-confirm on-confirm="performDelete" on-cancel="cancel">
</umb-confirm>
</div>
</div>
@@ -0,0 +1,314 @@
(function () {
"use strict";
function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper) {
var vm = this;
var localizeSaving = localizationService.localize("general_saving");
vm.page = {};
vm.page.loading = true;
vm.partialViewMacroFile = {};
//menu
vm.page.menu = {};
vm.page.menu.currentSection = appState.getSectionState("currentSection");
vm.page.menu.currentNode = null;
// bind functions to view model
vm.save = save;
vm.openPageFieldOverlay = openPageFieldOverlay;
vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
vm.openMacroOverlay = openMacroOverlay;
vm.openInsertOverlay = openInsertOverlay;
/* Functions bound to view model */
function save() {
vm.page.saveButtonState = "busy";
vm.partialViewMacro.content = vm.editor.getValue();
contentEditingHelper.contentEditorPerformSave({
statusMessage: localizeSaving,
saveMethod: codefileResource.save,
scope: $scope,
content: vm.partialViewMacro,
// We do not redirect on failure for partial view macros - this is because it is not possible to actually save the partial view
// when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {
localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) {
localizationService.localize("speechBubbles_partialViewSavedText").then(function(msgValue) {
notificationsService.success(headerValue, msgValue);
});
});
vm.page.saveButtonState = "success";
vm.partialViewMacro = saved;
//sync state
editorState.set(vm.partialViewMacro);
// normal tree sync
navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.path, forceReload: true }).then(function (syncArgs) {
vm.page.menu.currentNode = syncArgs.node;
});
// clear $dirty state on form
setFormState("pristine");
}, function (err) {
vm.page.saveButtonState = "error";
localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) {
notificationsService.error(headerValue, msgValue);
});
});
});
}
function openInsertOverlay() {
vm.insertOverlay = {
view: "insert",
allowedTypes: {
macro: true,
dictionary: true,
umbracoField: true
},
hideSubmitButton: true,
show: true,
submit: function(model) {
switch(model.insert.type) {
case "macro":
var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc");
insert(macroObject.syntax);
break;
case "dictionary":
var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
insert(code);
break;
case "umbracoField":
insert(model.insert.umbracoField);
break;
}
vm.insertOverlay.show = false;
vm.insertOverlay = null;
},
close: function(oldModel) {
// close the dialog
vm.insertOverlay.show = false;
vm.insertOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
function openMacroOverlay() {
vm.macroPickerOverlay = {
view: "macropicker",
dialogData: {},
show: true,
title: "Insert macro",
submit: function (model) {
var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc");
insert(macroObject.syntax);
vm.macroPickerOverlay.show = false;
vm.macroPickerOverlay = null;
},
close: function(oldModel) {
// close the dialog
vm.macroPickerOverlay.show = false;
vm.macroPickerOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
function openPageFieldOverlay() {
vm.pageFieldOverlay = {
submitButtonLabel: "Insert",
closeButtonlabel: "Cancel",
view: "insertfield",
show: true,
submit: function (model) {
insert(model.umbracoField);
vm.pageFieldOverlay.show = false;
vm.pageFieldOverlay = null;
},
close: function (model) {
// close the dialog
vm.pageFieldOverlay.show = false;
vm.pageFieldOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
function openDictionaryItemOverlay() {
vm.dictionaryItemOverlay = {
view: "treepicker",
section: "settings",
treeAlias: "dictionary",
entityType: "dictionary",
multiPicker: false,
show: true,
title: "Insert dictionary item",
select: function(node){
var code = templateHelper.getInsertDictionarySnippet(node.name);
insert(code);
vm.dictionaryItemOverlay.show = false;
vm.dictionaryItemOverlay = null;
},
close: function (model) {
// close dialog
vm.dictionaryItemOverlay.show = false;
vm.dictionaryItemOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
function openQueryBuilderOverlay() {
vm.queryBuilderOverlay = {
view: "querybuilder",
show: true,
title: "Query for content",
submit: function (model) {
var code = templateHelper.getQuerySnippet(model.result.queryExpression);
insert(code);
vm.queryBuilderOverlay.show = false;
vm.queryBuilderOverlay = null;
},
close: function (model) {
// close dialog
vm.queryBuilderOverlay.show = false;
vm.queryBuilderOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
/* Local functions */
function init() {
//we need to load this somewhere, for now its here.
assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css");
if ($routeParams.create) {
codefileResource.getScaffold().then(function (partialViewMacro) {
ready(partialViewMacro);
});
} else {
codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) {
ready(partialViewMacro);
});
}
}
function ready(partialViewMacro) {
vm.page.loading = false;
vm.partialViewMacro = partialViewMacro;
//sync state
editorState.set(vm.partialViewMacro);
navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.virtualPath, forceReload: true }).then(function (syncArgs) {
vm.page.menu.currentNode = syncArgs.node;
});
// ace configuration
vm.aceOption = {
mode: "razor",
theme: "chrome",
showPrintMargin: false,
advanced: {
fontSize: '14px'
},
onLoad: function(_editor) {
vm.editor = _editor;
// initial cursor placement
// Keep cursor in name field if we are create a new template
// else set the cursor at the bottom of the code editor
if(!$routeParams.create) {
$timeout(function(){
vm.editor.navigateFileEnd();
vm.editor.focus();
persistCurrentLocation();
});
}
//change on blur, focus
vm.editor.on("blur", persistCurrentLocation);
vm.editor.on("focus", persistCurrentLocation);
}
}
}
function insert(str) {
vm.editor.moveCursorToPosition(vm.currentPosition);
vm.editor.insert(str);
vm.editor.focus();
// set form state to $dirty
setFormState("dirty");
}
function persistCurrentLocation() {
vm.currentPosition = vm.editor.getCursorPosition();
}
function setFormState(state) {
// get the current form
var currentForm = angularHelper.getCurrentForm($scope);
// set state
if(state === "dirty") {
currentForm.$setDirty();
} else if(state === "pristine") {
currentForm.$setPristine();
}
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.PartialViewMacros.EditController", partialViewMacrosEditController);
})();
@@ -0,0 +1,123 @@
<div ng-controller="Umbraco.Editors.PartialViewMacros.EditController as vm">
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<form name="contentForm"
ng-submit="vm.save()"
novalidate
val-form-manager>
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-header
name="vm.partialViewMacro.name"
hide-alias="true"
description="vm.partialViewMacro.virtualPath"
menu="vm.page.menu"
description-locked="true"
hide-icon="true">
</umb-editor-header>
<umb-editor-container>
<div class="flex" style="margin-bottom: 30px;">
<div class="flex" style="margin-left: auto;">
<div class="btn-group umb-era-button-group dropdown" style="margin-right: 10px;">
<button
type="button"
class="umb-era-button umb-button--s"
ng-click="vm.openInsertOverlay()">
<i class="icon icon-add"></i> Insert
</button>
<a class="umb-era-button umb-button--s dropdown-toggle umb-button-group__toggle" data-toggle="dropdown">
<span class="caret"></span>
</a>
<ul aria-labelledby="dLabel" class="dropdown-menu bottom-up umb-button-group__sub-buttons" role="menu">
<li><a href="" ng-click="vm.openPageFieldOverlay()">Value</a></li>
<li><a href="" ng-click="vm.openMacroOverlay()">Macro</a></li>
<li><a href="" ng-click="vm.openDictionaryItemOverlay()">Dictionary</a></li>
</ul>
</div>
<button
type="button"
style="margin-right: 10px;"
class="umb-era-button umb-button--s"
ng-click="vm.openQueryBuilderOverlay()">
<i class="icon icon-wand"></i> Query builder
</button>
</div>
</div>
<div
auto-scale="85"
umb-ace-editor="vm.aceOption"
model="vm.partialViewMacro.content">
</div>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
type="submit"
button-style="success"
state="vm.page.saveButtonState"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
<umb-overlay
ng-if="vm.insertOverlay.show"
model="vm.insertOverlay"
view="vm.insertOverlay.view"
position="right">
</umb-overlay>
<umb-overlay
ng-if="vm.macroPickerOverlay.show"
model="vm.macroPickerOverlay"
view="vm.macroPickerOverlay.view"
position="right">
</umb-overlay>
<umb-overlay
ng-if="vm.pageFieldOverlay.show"
model="vm.pageFieldOverlay"
position="right"
view="vm.pageFieldOverlay.view">
</umb-overlay>
<umb-overlay
ng-if="vm.dictionaryItemOverlay.show"
model="vm.dictionaryItemOverlay"
position="right"
view="vm.dictionaryItemOverlay.view">
</umb-overlay>
<umb-overlay
ng-if="vm.queryBuilderOverlay.show"
model="vm.queryBuilderOverlay"
position="right"
view="vm.queryBuilderOverlay.view">
</umb-overlay>
</div>
@@ -0,0 +1,34 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.PartialViews.DeleteController
* @function
*
* @description
* The controller for deleting partial views
*/
function PartialViewsDeleteController($scope, codefileResource, treeService, navigationService) {
$scope.performDelete = function() {
//mark it for deletion (used in the UI)
$scope.currentNode.loading = true;
var virtualPath = $scope.currentNode.parentId + $scope.currentNode.name;
codefileResource.deleteByPath('partialViews', virtualPath)
.then(function() {
$scope.currentNode.loading = false;
//get the root node before we remove it
var rootNode = treeService.getTreeRoot($scope.currentNode);
//TODO: Need to sync tree, etc...
treeService.removeNode($scope.currentNode);
navigationService.hideMenu();
});
};
$scope.cancel = function() {
navigationService.hideDialog();
};
}
angular.module("umbraco").controller("Umbraco.Editors.PartialViews.DeleteController", PartialViewsDeleteController);
@@ -0,0 +1,12 @@
<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.PartialViews.DeleteController">
<div class="umb-dialog-body">
<p class="umb-abstract">
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong> ?
</p>
<umb-confirm on-confirm="performDelete" on-cancel="cancel">
</umb-confirm>
</div>
</div>
@@ -0,0 +1,314 @@
(function () {
"use strict";
function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper) {
var vm = this;
var localizeSaving = localizationService.localize("general_saving");
vm.page = {};
vm.page.loading = true;
vm.partialView = {};
//menu
vm.page.menu = {};
vm.page.menu.currentSection = appState.getSectionState("currentSection");
vm.page.menu.currentNode = null;
// bind functions to view model
vm.save = save;
vm.openPageFieldOverlay = openPageFieldOverlay;
vm.openDictionaryItemOverlay = openDictionaryItemOverlay;
vm.openQueryBuilderOverlay = openQueryBuilderOverlay;
vm.openMacroOverlay = openMacroOverlay;
vm.openInsertOverlay = openInsertOverlay;
/* Functions bound to view model */
function save() {
vm.page.saveButtonState = "busy";
vm.partialView.content = vm.editor.getValue();
contentEditingHelper.contentEditorPerformSave({
statusMessage: localizeSaving,
saveMethod: codefileResource.save,
scope: $scope,
content: vm.partialView,
//We do not redirect on failure for partialviews - this is because it is not possible to actually save the partialviews
// type when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {
localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) {
localizationService.localize("speechBubbles_partialViewSavedText").then(function(msgValue) {
notificationsService.success(headerValue, msgValue);
});
});
vm.page.saveButtonState = "success";
vm.partialView = saved;
//sync state
editorState.set(vm.partialView);
// normal tree sync
navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, forceReload: true }).then(function (syncArgs) {
vm.page.menu.currentNode = syncArgs.node;
});
// clear $dirty state on form
setFormState("pristine");
}, function (err) {
vm.page.saveButtonState = "error";
localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) {
notificationsService.error(headerValue, msgValue);
});
});
});
}
function openInsertOverlay() {
vm.insertOverlay = {
view: "insert",
allowedTypes: {
macro: true,
dictionary: true,
umbracoField: true
},
hideSubmitButton: true,
show: true,
submit: function(model) {
switch(model.insert.type) {
case "macro":
var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc");
insert(macroObject.syntax);
break;
case "dictionary":
var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
insert(code);
break;
case "umbracoField":
insert(model.insert.umbracoField);
break;
}
vm.insertOverlay.show = false;
vm.insertOverlay = null;
},
close: function(oldModel) {
// close the dialog
vm.insertOverlay.show = false;
vm.insertOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
function openMacroOverlay() {
vm.macroPickerOverlay = {
view: "macropicker",
dialogData: {},
show: true,
title: "Insert macro",
submit: function (model) {
var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc");
insert(macroObject.syntax);
vm.macroPickerOverlay.show = false;
vm.macroPickerOverlay = null;
},
close: function(oldModel) {
// close the dialog
vm.macroPickerOverlay.show = false;
vm.macroPickerOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
function openPageFieldOverlay() {
vm.pageFieldOverlay = {
submitButtonLabel: "Insert",
closeButtonlabel: "Cancel",
view: "insertfield",
show: true,
submit: function (model) {
insert(model.umbracoField);
vm.pageFieldOverlay.show = false;
vm.pageFieldOverlay = null;
},
close: function (model) {
// close the dialog
vm.pageFieldOverlay.show = false;
vm.pageFieldOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
function openDictionaryItemOverlay() {
vm.dictionaryItemOverlay = {
view: "treepicker",
section: "settings",
treeAlias: "dictionary",
entityType: "dictionary",
multiPicker: false,
show: true,
title: "Insert dictionary item",
select: function(node){
var code = templateHelper.getInsertDictionarySnippet(node.name);
insert(code);
vm.dictionaryItemOverlay.show = false;
vm.dictionaryItemOverlay = null;
},
close: function (model) {
// close dialog
vm.dictionaryItemOverlay.show = false;
vm.dictionaryItemOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
function openQueryBuilderOverlay() {
vm.queryBuilderOverlay = {
view: "querybuilder",
show: true,
title: "Query for content",
submit: function (model) {
var code = templateHelper.getQuerySnippet(model.result.queryExpression);
insert(code);
vm.queryBuilderOverlay.show = false;
vm.queryBuilderOverlay = null;
},
close: function (model) {
// close dialog
vm.queryBuilderOverlay.show = false;
vm.queryBuilderOverlay = null;
// focus editor
vm.editor.focus();
}
};
}
/* Local functions */
function init() {
//we need to load this somewhere, for now its here.
assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css");
if ($routeParams.create) {
codefileResource.getScaffold().then(function (partialView) {
ready(partialView);
});
} else {
codefileResource.getByPath('partialViews', $routeParams.id).then(function (partialView) {
ready(partialView);
});
}
}
function ready(partialView) {
vm.page.loading = false;
vm.partialView = partialView;
//sync state
editorState.set(vm.partialView);
navigationService.syncTree({ tree: "partialViews", path: vm.partialView.virtualPath, forceReload: true }).then(function (syncArgs) {
vm.page.menu.currentNode = syncArgs.node;
});
// ace configuration
vm.aceOption = {
mode: "razor",
theme: "chrome",
showPrintMargin: false,
advanced: {
fontSize: '14px'
},
onLoad: function(_editor) {
vm.editor = _editor;
// initial cursor placement
// Keep cursor in name field if we are create a new template
// else set the cursor at the bottom of the code editor
if(!$routeParams.create) {
$timeout(function(){
vm.editor.navigateFileEnd();
vm.editor.focus();
persistCurrentLocation();
});
}
//change on blur, focus
vm.editor.on("blur", persistCurrentLocation);
vm.editor.on("focus", persistCurrentLocation);
}
}
}
function insert(str) {
vm.editor.moveCursorToPosition(vm.currentPosition);
vm.editor.insert(str);
vm.editor.focus();
// set form state to $dirty
setFormState("dirty");
}
function persistCurrentLocation() {
vm.currentPosition = vm.editor.getCursorPosition();
}
function setFormState(state) {
// get the current form
var currentForm = angularHelper.getCurrentForm($scope);
// set state
if(state === "dirty") {
currentForm.$setDirty();
} else if(state === "pristine") {
currentForm.$setPristine();
}
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.PartialViews.EditController", PartialViewsEditController);
})();
@@ -0,0 +1,123 @@
<div ng-controller="Umbraco.Editors.PartialViews.EditController as vm">
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<form name="contentForm"
ng-submit="vm.save()"
novalidate
val-form-manager>
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-header
name="vm.partialView.name"
hide-alias="true"
description="vm.partialView.virtualPath"
menu="vm.page.menu"
description-locked="true"
hide-icon="true">
</umb-editor-header>
<umb-editor-container>
<div class="flex" style="margin-bottom: 30px;">
<div class="flex" style="margin-left: auto;">
<div class="btn-group umb-era-button-group dropdown" style="margin-right: 10px;">
<button
type="button"
class="umb-era-button umb-button--s"
ng-click="vm.openInsertOverlay()">
<i class="icon icon-add"></i> Insert
</button>
<a class="umb-era-button umb-button--s dropdown-toggle umb-button-group__toggle" data-toggle="dropdown">
<span class="caret"></span>
</a>
<ul aria-labelledby="dLabel" class="dropdown-menu bottom-up umb-button-group__sub-buttons" role="menu">
<li><a href="" ng-click="vm.openPageFieldOverlay()">Value</a></li>
<li><a href="" ng-click="vm.openMacroOverlay()">Macro</a></li>
<li><a href="" ng-click="vm.openDictionaryItemOverlay()">Dictionary</a></li>
</ul>
</div>
<button
type="button"
style="margin-right: 10px;"
class="umb-era-button umb-button--s"
ng-click="vm.openQueryBuilderOverlay()">
<i class="icon icon-wand"></i> Query builder
</button>
</div>
</div>
<div
auto-scale="85"
umb-ace-editor="vm.aceOption"
model="vm.partialView.content">
</div>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
type="submit"
button-style="success"
state="vm.page.saveButtonState"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
<umb-overlay
ng-if="vm.insertOverlay.show"
model="vm.insertOverlay"
view="vm.insertOverlay.view"
position="right">
</umb-overlay>
<umb-overlay
ng-if="vm.macroPickerOverlay.show"
model="vm.macroPickerOverlay"
view="vm.macroPickerOverlay.view"
position="right">
</umb-overlay>
<umb-overlay
ng-if="vm.pageFieldOverlay.show"
model="vm.pageFieldOverlay"
position="right"
view="vm.pageFieldOverlay.view">
</umb-overlay>
<umb-overlay
ng-if="vm.dictionaryItemOverlay.show"
model="vm.dictionaryItemOverlay"
position="right"
view="vm.dictionaryItemOverlay.view">
</umb-overlay>
<umb-overlay
ng-if="vm.queryBuilderOverlay.show"
model="vm.queryBuilderOverlay"
position="right"
view="vm.queryBuilderOverlay.view">
</umb-overlay>
</div>
@@ -0,0 +1,34 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.Scripts.DeleteController
* @function
*
* @description
* The controller for deleting scripts
*/
function ScriptsDeleteController($scope, codefileResource, treeService, navigationService) {
$scope.performDelete = function() {
//mark it for deletion (used in the UI)
$scope.currentNode.loading = true;
var virtualPath = $scope.currentNode.parentId + $scope.currentNode.name;
codefileResource.deleteByPath('scripts', virtualPath)
.then(function() {
$scope.currentNode.loading = false;
//get the root node before we remove it
var rootNode = treeService.getTreeRoot($scope.currentNode);
//TODO: Need to sync tree, etc...
treeService.removeNode($scope.currentNode);
navigationService.hideMenu();
});
};
$scope.cancel = function() {
navigationService.hideDialog();
};
}
angular.module("umbraco").controller("Umbraco.Editors.Scripts.DeleteController", ScriptsDeleteController);
@@ -0,0 +1,12 @@
<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.Scripts.DeleteController">
<div class="umb-dialog-body">
<p class="umb-abstract">
<localize key="defaultdialogs_confirmdelete">Are you sure you want to delete</localize> <strong>{{currentNode.name}}</strong> ?
</p>
<umb-confirm on-confirm="performDelete" on-cancel="cancel">
</umb-confirm>
</div>
</div>
@@ -0,0 +1,137 @@
(function () {
"use strict";
function ScriptsEditController($scope, $routeParams, $timeout, appState, editorState, navigationService, assetsService, codefileResource, contentEditingHelper, notificationsService, localizationService) {
var vm = this;
var currentPosition = null;
var localizeSaving = localizationService.localize("general_saving");
vm.page = {};
vm.page.loading = true;
vm.page.menu = {};
vm.page.menu.currentSection = appState.getSectionState("currentSection");
vm.page.menu.currentNode = null;
vm.page.saveButtonState = "init";
vm.script = {};
// bind functions to view model
vm.save = save;
/* Function bound to view model */
function save() {
vm.page.saveButtonState = "busy";
vm.script.content = vm.editor.getValue();
contentEditingHelper.contentEditorPerformSave({
statusMessage: localizeSaving,
saveMethod: codefileResource.save,
scope: $scope,
content: vm.script,
// We do not redirect on failure for scripts - this is because it is not possible to actually save the script
// when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {
localizationService.localize("speechBubbles_fileSavedHeader").then(function (headerValue) {
localizationService.localize("speechBubbles_fileSavedText").then(function(msgValue) {
notificationsService.success(headerValue, msgValue);
});
});
vm.page.saveButtonState = "success";
vm.script = saved;
//sync state
editorState.set(vm.script);
// sync tree
navigationService.syncTree({ tree: "scripts", path: vm.script.virtualPath, forceReload: true }).then(function (syncArgs) {
vm.page.menu.currentNode = syncArgs.node;
});
}, function (err) {
vm.page.saveButtonState = "error";
localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) {
localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) {
notificationsService.error(headerValue, msgValue);
});
});
});
}
/* Local functions */
function init() {
//we need to load this somewhere, for now its here.
assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css");
if ($routeParams.create) {
codefileResource.getScaffold().then(function (script) {
ready(script);
});
} else {
codefileResource.getByPath('scripts', $routeParams.id).then(function (script) {
ready(script);
});
}
}
function ready(script) {
vm.page.loading = false;
vm.script = script;
//sync state
editorState.set(vm.script);
navigationService.syncTree({ tree: "scripts", path: vm.script.virtualPath, forceReload: true }).then(function (syncArgs) {
vm.page.menu.currentNode = syncArgs.node;
});
vm.aceOption = {
mode: "javascript",
theme: "chrome",
showPrintMargin: false,
advanced: {
fontSize: '14px'
},
onLoad: function(_editor) {
vm.editor = _editor;
// initial cursor placement
// Keep cursor in name field if we are create a new script
// else set the cursor at the bottom of the code editor
if(!$routeParams.create) {
$timeout(function(){
vm.editor.navigateFileEnd();
vm.editor.focus();
});
}
}
}
}
init();
}
angular.module("umbraco").controller("Umbraco.Editors.Scripts.EditController", ScriptsEditController);
})();
@@ -0,0 +1,51 @@
<div ng-controller="Umbraco.Editors.Scripts.EditController as vm">
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<form name="scriptsForm"
ng-submit="vm.save()"
novalidate
val-form-manager>
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-header
name="vm.script.name"
hide-alias="true"
description="vm.script.virtualPath"
description-locked="true"
menu="vm.page.menu"
hide-icon="true">
</umb-editor-header>
<umb-editor-container>
<div
auto-scale="85"
umb-ace-editor="vm.aceOption"
model="vm.script.content">
</div>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-button
type="submit"
button-style="success"
state="vm.page.saveButtonState"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
</div>
@@ -1,7 +1,7 @@
(function () {
"use strict";
function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper) {
function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper) {
var vm = this;
var oldMasterTemplateAlias = null;
@@ -26,14 +26,20 @@
saveMethod: templateResource.save,
scope: $scope,
content: vm.template,
//We do not redirect on failure for templates - this is because it is not possible to actually save the doc
//We do not redirect on failure for templates - this is because it is not possible to actually save the template
// type when server side validation fails - as opposed to content where we are capable of saving the content
// item if server side validation fails
redirectOnFailure: false,
rebindCallback: function (orignal, saved) {}
}).then(function (saved) {
notificationsService.success("Template saved");
localizationService.localize("speechBubbles_templateSavedHeader").then(function (headerValue) {
localizationService.localize("speechBubbles_templateSavedText").then(function(msgValue) {
notificationsService.success(headerValue, msgValue);
});
});
vm.page.saveButtonState = "success";
vm.template = saved;
@@ -169,27 +175,30 @@
vm.insertOverlay = {
view: "insert",
allowedTypes: {
macro: true,
dictionary: true,
partial: true,
umbracoField: true
},
hideSubmitButton: true,
show: true,
submit: function(model) {
switch(model.insert.type) {
case "macro":
case "macro":
var macroObject = macroService.collectValueData(model.insert.selectedMacro, model.insert.macroParams, "Mvc");
insert(macroObject.syntax);
break;
case "dictionary":
//crappy hack due to dictionary items not in umbracoNode table
var code = "@Umbraco.GetDictionaryValue(\"" + model.insert.node.name + "\")";
var code = templateHelper.getInsertDictionarySnippet(model.insert.node.name);
insert(code);
break;
case "partial":
//crappy hack due to dictionary items not in umbracoNode table
var nodeNameWithPath = model.insert.node.id.replace(".cshtml", "");
var code = "@Html.Partial(\"" + nodeNameWithPath + "\")";
var code = templateHelper.getInsertPartialSnippet(model.insert.node.name);
insert(code);
break;
@@ -274,8 +283,7 @@
show: true,
title: localizationService.localize("template_insertDictionaryItem"),
select: function(node){
//crappy hack due to dictionary items not in umbracoNode table
var code = "@Umbraco.GetDictionaryValue(\"" + node.name + "\")";
var code = templateHelper.getInsertDictionarySnippet(node.name);
insert(code);
vm.dictionaryItemOverlay.show = false;
@@ -301,9 +309,8 @@
show: true,
title: localizationService.localize("template_insertPartialView"),
select: function(node){
//crappy hack due to dictionary items not in umbracoNode table
var nodeNameWithPath = node.id.replace(".cshtml", "");
var code = "@Html.Partial(\"" + nodeNameWithPath + "\")";
var code = templateHelper.getInsertPartialSnippet(node.name);
insert(code);
vm.partialItemOverlay.show = false;
@@ -324,18 +331,9 @@
view: "querybuilder",
show: true,
title: localizationService.localize("template_queryBuilder"),
submit: function (model) {
var code = "\n@{\n" + "\tvar selection = " + model.result.queryExpression + ";\n}\n";
code += "<ul>\n" +
"\t@foreach(var item in selection){\n" +
"\t\t<li>\n" +
"\t\t\t<a href=\"@item.Url\">@item.Name</a>\n" +
"\t\t</li>\n" +
"\t}\n" +
"</ul>\n\n";
var code = templateHelper.getQuerySnippet(model.result.queryExpression);
insert(code);
vm.queryBuilderOverlay.show = false;
@@ -363,15 +361,18 @@
submit: function(model) {
if (model.insertType === 'renderBody') {
insert("@RenderBody()");
var code = templateHelper.getRenderBodySnippet();
insert(code);
}
if (model.insertType === 'renderSection') {
insert("@RenderSection(\"" + model.renderSectionName + "\", " + model.mandatoryRenderSection + ")");
var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection);
insert(code);
}
if (model.insertType === 'addSection') {
wrap("@section " + model.sectionName + "\r\n{\r\n\r\n\t{0}\r\n\r\n}\r\n");
var code = templateHelper.getAddSectionSnippet(model.sectionName);
wrap(code);
}
vm.sectionsOverlay.show = false;
@@ -101,6 +101,13 @@
$setPristine: function() {}
}
}
},
templateHelper: {
getInsertDictionary: function() { return ""; },
getInsertPartialSnippet: function() { return ""; },
getQuerySnippet: function() { return ""; },
getRenderBodySnippet: function() { return ""; },
getRenderSectionSnippet: function() { return ""; }
}
});
}
@@ -0,0 +1,84 @@
describe('service: templateHelper', function () {
var templateHelper;
beforeEach(module("umbraco.services"));
beforeEach(inject(function ($injector) {
templateHelper = $injector.get('templateHelper');
}));
afterEach(inject(function ($rootScope) {
$rootScope.$apply();
}));
describe('getInsertDictionarySnippet', function () {
it('should return the snippet for inserting a dictionary item', function () {
var snippet = '@Umbraco.GetDictionaryValue("nodeName")';
expect(templateHelper.getInsertDictionarySnippet('nodeName')).toBe(snippet);
});
});
describe('getInsertPartialSnippet', function () {
it('should return the snippet for inserting a partial', function () {
var snippet = '@Html.Partial("nodeName")';
expect(templateHelper.getInsertPartialSnippet("nodeName")).toBe(snippet);
});
});
describe('getQuerySnippet', function () {
it('should return the snippet for a query', function () {
var queryExpression = "queryExpression";
var snippet = "\n@{\n" + "\tvar selection = " + queryExpression + ";\n}\n";
snippet += "<ul>\n" +
"\t@foreach(var item in selection){\n" +
"\t\t<li>\n" +
"\t\t\t<a href=\"@item.Url\">@item.Name</a>\n" +
"\t\t</li>\n" +
"\t}\n" +
"</ul>\n\n";
expect(templateHelper.getQuerySnippet(queryExpression)).toBe(snippet);
});
});
describe('getRenderBodySnippet', function () {
it('should return the snippet for render body', function () {
var snippet = '@RenderBody()';
expect(templateHelper.getRenderBodySnippet()).toBe(snippet);
});
});
describe('getRenderSectionSnippet', function () {
it('should return the snippet for defining a section', function () {
var snippet = '@RenderSection("sectionName", false)';
expect(templateHelper.getRenderSectionSnippet("sectionName", false)).toBe(snippet);
});
it('should return the snippet for defining a mandatory section', function () {
var snippet = '@RenderSection("sectionName", true)';
expect(templateHelper.getRenderSectionSnippet("sectionName", true)).toBe(snippet);
});
});
describe('getAddSectionSnippet', function () {
it('should return the snippet for implementing a section', function () {
var sectionName = "sectionName";
var snippet = "@section " + sectionName + "\r\n{\r\n\r\n\t{0}\r\n\r\n}\r\n";
expect(templateHelper.getAddSectionSnippet(sectionName)).toEqual(snippet);
});
});
});
-8
View File
@@ -563,13 +563,6 @@
<Compile Include="Umbraco\Settings\Stylesheet\Property\EditStyleSheetProperty.aspx.designer.cs">
<DependentUpon>EditStyleSheetProperty.aspx</DependentUpon>
</Compile>
<Compile Include="Umbraco\Settings\Views\EditView.aspx.cs">
<DependentUpon>EditView.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
</Compile>
<Compile Include="Umbraco\Settings\Views\EditView.aspx.designer.cs">
<DependentUpon>EditView.aspx</DependentUpon>
</Compile>
<Compile Include="Umbraco\TreeInit.aspx.cs">
<DependentUpon>treeInit.aspx</DependentUpon>
<SubType>ASPXCodeBehind</SubType>
@@ -752,7 +745,6 @@
<Content Include="Umbraco\Images\thumbs_med.png" />
<Content Include="Umbraco\Images\thumbs_smll.png" />
<Content Include="Umbraco\Images\Umbraco\settingView.gif" />
<Content Include="Umbraco\Settings\Views\EditView.aspx" />
<Content Include="Umbraco_Client\Application\Extensions.js" />
<Content Include="Umbraco_Client\Application\JQuery\jquery.validate.min.js" />
<Content Include="Umbraco_Client\Application\JQuery\jquery.validate.unobtrusive.min.js" />
+3 -4
View File
@@ -9,10 +9,10 @@
<!--Settings-->
<add initialize="true" sortOrder="0" alias="documentTypes" application="settings" title="Document Types" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.ContentTypeTreeController, umbraco" />
<add application="settings" alias="templates" title="Templates" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.TemplatesTreeController, umbraco" initialize="true" sortOrder="1" />
<add application="settings" alias="partialViews" title="Partial Views" silent="false" initialize="true" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.PartialViewsTree, umbraco" sortOrder="2" />
<add application="settings" alias="partialViews" title="Partial Views" silent="false" initialize="true" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.PartialViewsTreeController, umbraco" sortOrder="2" />
<add application="settings" alias="stylesheets" title="Stylesheets" type="umbraco.loadStylesheets, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="3" />
<add application="settings" alias="stylesheetProperty" title="Stylesheet Property" type="umbraco.loadStylesheetProperty, umbraco" iconClosed="" iconOpen="" initialize="false" sortOrder="0" />
<add application="settings" alias="scripts" title="Scripts" type="umbraco.loadScripts, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
<add application="settings" alias="scripts" title="Scripts" type="Umbraco.Web.Trees.ScriptTreeController, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
<add application="settings" alias="languages" title="Languages" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.LanguageTreeController, umbraco" sortOrder="5" />
<add application="settings" alias="dictionary" title="Dictionary" type="umbraco.loadDictionary, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="6" />
<add initialize="true" sortOrder="7" alias="mediaTypes" application="settings" title="Media Types" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MediaTypeTreeController, umbraco" />
@@ -22,7 +22,7 @@
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2" />
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
<add application="developer" alias="xslt" title="XSLT Files" type="umbraco.loadXslt, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="5" />
<add application="developer" alias="partialViewMacros" type="Umbraco.Web.Trees.PartialViewMacrosTree, umbraco" silent="false" initialize="true" sortOrder="6" title="Partial View Macro Files" iconClosed="icon-folder" iconOpen="icon-folder" />
<add application="developer" alias="partialViewMacros" type="Umbraco.Web.Trees.PartialViewMacrosTreeController, umbraco" silent="false" initialize="true" sortOrder="6" title="Partial View Macro Files" iconClosed="icon-folder" iconOpen="icon-folder" />
<!--Users-->
<add application="users" alias="users" title="Users" type="umbraco.loadUsers, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="0" />
<add application="users" alias="userTypes" title="User Types" type="umbraco.cms.presentation.Trees.UserTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="1" />
@@ -41,5 +41,4 @@
<add initialize="true" sortOrder="0" alias="form" application="forms" title="Forms" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormTreeController, Umbraco.Forms.Web" />
<add initialize="true" sortOrder="3" alias="prevaluesource" application="forms" title="Prevalue sources" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.PreValueSourceTreeController, Umbraco.Forms.Web" />
<add initialize="true" sortOrder="3" alias="formsecurity" application="users" title="Forms Security" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormSecurityTreeController, Umbraco.Forms.Web" />
</trees>
@@ -1,88 +0,0 @@
<%@ Page Language="C#" MasterPageFile="../../masterpages/umbracoPage.Master" AutoEventWireup="True"
CodeBehind="EditView.aspx.cs" Inherits="Umbraco.Web.UI.Umbraco.Settings.Views.EditView"
ValidateRequest="False" %>
<%@ OutputCache Location="None" %>
<%@ Import Namespace="Umbraco.Core" %>
<%@ Import Namespace="Umbraco.Core.IO" %>
<%@ Import Namespace="Umbraco.Web" %>
<%@ Register TagPrefix="cc1" Namespace="umbraco.uicontrols" Assembly="controls" %>
<%@ Register TagPrefix="umb" Namespace="ClientDependency.Core.Controls" Assembly="ClientDependency.Core" %>
<asp:Content ID="DocTypeContent" ContentPlaceHolderID="DocType" runat="server">
<!DOCTYPE html>
</asp:Content>
<asp:Content ContentPlaceHolderID="head" runat="server">
<umb:JsInclude ID="JsInclude1" runat="server" FilePath="Editors/EditView.js" PathNameAlias="UmbracoClient" />
<script type="text/javascript">
//we need to have this as a global object since we reference this object with callbacks.
var editViewEditor;
(function ($) {
$(document).ready(function () {
//create and assign a new EditView object
editViewEditor = new Umbraco.Editors.EditView({
treeSyncPath: "<%=TemplateTreeSyncPath%>",
currentTreeType: "<%=CurrentTreeType%>",
editorType: "<%= EditorType.ToString() %>",
originalFileName: "<%=OriginalFileName %>",
restServiceLocation: "<%= Url.GetSaveFileServicePath() %>",
masterPageDropDown: $("#<%= MasterTemplate.ClientID %>"),
nameTxtBox: $("#<%= NameTxt.ClientID %>"),
aliasTxtBox: $("#<%= AliasTxt.ClientID %>"),
saveButton: $("#<%= ((Control)SaveButton).ClientID %>"),
templateId: '<%= Request.CleanForXss("templateID") %>',
codeEditorElementId: '<%= editorSource.ClientID %>',
modalUrl: "<%= IOHelper.ResolveUrl(SystemDirectories.Umbraco) %>/dialogs/editMacro.aspx"
});
//initialize it.
editViewEditor.init();
//bind save shortcut
UmbClientMgr.appActions().bindSaveShortCut();
});
})(jQuery);
</script>
</asp:Content>
<asp:Content ContentPlaceHolderID="body" runat="server">
<cc1:TabView ID="Panel1" runat="server" hasMenu="true">
<cc1:Pane ID="Pane8" runat="server" >
<cc1:PropertyPanel ID="pp_source" runat="server">
<cc1:CodeArea ID="editorSource" runat="server" CodeBase="Razor" ClientSaveMethod="doSubmit" AutoResize="false" />
</cc1:PropertyPanel>
</cc1:Pane>
<cc1:Pane ID="Pane7" runat="server" >
<cc1:PropertyPanel ID="pp_name" runat="server">
<asp:Literal runat="server" ID="PathPrefix"></asp:Literal>
<asp:TextBox ID="NameTxt" runat="server"></asp:TextBox>
</cc1:PropertyPanel>
<cc1:PropertyPanel ID="pp_alias" runat="server">
<asp:TextBox ID="AliasTxt" runat="server"></asp:TextBox>
</cc1:PropertyPanel>
<cc1:PropertyPanel ID="pp_masterTemplate" runat="server">
<asp:DropDownList ID="MasterTemplate" runat="server" />
</cc1:PropertyPanel>
</cc1:Pane>
</cc1:TabView>
</asp:Content>
@@ -1,280 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;
using Umbraco.Web.Trees;
using Umbraco.Web.UI.Controls;
using umbraco;
using umbraco.BasePages;
using umbraco.cms.businesslogic.template;
using umbraco.cms.helpers;
using umbraco.cms.presentation.Trees;
using Umbraco.Core;
using umbraco.uicontrols;
namespace Umbraco.Web.UI.Umbraco.Settings.Views
{
public partial class EditView : global::umbraco.BasePages.UmbracoEnsuredPage
{
private Template _template;
public MenuButton SaveButton;
public EditView()
{
CurrentApp = global::umbraco.BusinessLogic.DefaultApps.settings.ToString();
}
/// <summary>
/// The type of MVC/Umbraco view the editor is editing
/// </summary>
public enum ViewEditorType
{
Template,
PartialView,
PartialViewMacro
}
/// <summary>
/// Returns the type of view being edited
/// </summary>
protected ViewEditorType EditorType
{
get
{
if (_template != null) return ViewEditorType.Template;
if (Request.QueryString["treeType"].IsNullOrWhiteSpace() == false && Request.QueryString["treeType"].InvariantEquals("partialViewMacros")) return ViewEditorType.PartialViewMacro;
return ViewEditorType.PartialView;
}
}
protected string TemplateTreeSyncPath { get; private set; }
/// <summary>
/// This view is shared between different trees so we'll look for the query string
/// </summary>
protected string CurrentTreeType
{
get
{
if (Request.QueryString["treeType"].IsNullOrWhiteSpace())
{
return TreeDefinitionCollection.Instance.FindTree<PartialViewsTree>().Tree.Alias;
}
return Request.CleanForXss("treeType");
}
}
/// <summary>
/// Returns the original file name that the editor was loaded with
/// </summary>
/// <remarks>
/// this is used for editing a partial view
/// </remarks>
protected string OriginalFileName { get; private set; }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
if (!IsPostBack)
{
//configure screen for editing a template
if (_template != null)
{
MasterTemplate.Items.Add(new ListItem(ui.Text("none"), "0"));
var selectedTemplate = string.Empty;
foreach (var t in Template.GetAllAsList())
{
if (t.Id == _template.Id) continue;
var li = new ListItem(t.Text, t.Id.ToString(CultureInfo.InvariantCulture));
li.Attributes.Add("id", t.Alias.Replace(" ", "") + ".cshtml");
MasterTemplate.Items.Add(li);
}
try
{
if (_template.MasterTemplate > 0)
MasterTemplate.SelectedValue = _template.MasterTemplate.ToString(CultureInfo.InvariantCulture);
}
catch (Exception ex)
{
LogHelper.Error<EditView>("An error occurred setting a master template id", ex);
}
MasterTemplate.SelectedValue = selectedTemplate;
NameTxt.Text = _template.GetRawText();
AliasTxt.Text = _template.Alias;
editorSource.Text = _template.Design;
PathPrefix.Visible = false;
}
else
{
//configure editor for editing a file....
NameTxt.Text = OriginalFileName;
var svce = ApplicationContext.Current.Services.FileService;
var file = EditorType == ViewEditorType.PartialView
? svce.GetPartialView(OriginalFileName)
: svce.GetPartialViewMacro(OriginalFileName);
editorSource.Text = file.Content;
const string prefixFormat = "<span style=\"display: inline-block; height: 20px; line-height: 20px; margin-bottom: 0px; padding: 4px 6px;\">{0}</span>";
PathPrefix.Text = string.Format(prefixFormat, EditorType == ViewEditorType.PartialView
? "Partials/"
: "MacroPartials/");
}
}
ClientTools
.SetActiveTreeType(CurrentTreeType)
.SyncTree(TemplateTreeSyncPath, false);
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
//check if a templateId is assigned, meaning we are editing a template
if (!Request.QueryString["templateID"].IsNullOrWhiteSpace())
{
_template = new Template(int.Parse(Request.QueryString["templateID"]));
TemplateTreeSyncPath = "-1,init," + _template.Path.Replace("-1,", "");
}
else if (!Request.QueryString["file"].IsNullOrWhiteSpace())
{
//we are editing a view (i.e. partial view)
OriginalFileName = HttpUtility.UrlDecode(Request.QueryString["file"]);
//TemplateTreeSyncPath = "-1,init," + Path.GetFileName(OriginalFileName);
TemplateTreeSyncPath = DeepLink.GetTreePathFromFilePath(OriginalFileName.TrimStart("MacroPartials/").TrimStart("Partials/"));
}
else
{
throw new InvalidOperationException("Cannot render the editor without a supplied templateId or a file");
}
Panel1.hasMenu = true;
var editor = Panel1.NewTabPage(ui.Text("template"));
editor.Controls.Add(Pane8);
var props = Panel1.NewTabPage(ui.Text("properties"));
props.Controls.Add(Pane7);
SaveButton = Panel1.Menu.NewButton();
SaveButton.Text = ui.Text("save");
SaveButton.ButtonType = MenuButtonType.Primary;
SaveButton.ID = "save";
SaveButton.CssClass = "client-side";
Panel1.Text = ui.Text("edittemplate");
pp_name.Text = ui.Text("name", base.getUser());
pp_alias.Text = ui.Text("alias", base.getUser());
pp_masterTemplate.Text = ui.Text("mastertemplate", base.getUser());
// Editing buttons
MenuIconI umbField = editorSource.Menu.NewIcon();
umbField.ImageURL = UmbracoPath + "/images/editor/insField.gif";
umbField.OnClickCommand =
ClientTools.Scripts.OpenModalWindow(
IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" +
editorSource.ClientID + "&tagName=UMBRACOGETDATA&mvcView=true", ui.Text("template", "insertPageField"), 640, 550);
umbField.AltText = ui.Text("template", "insertPageField");
// TODO: Update icon
MenuIconI umbDictionary = editorSource.Menu.NewIcon();
umbDictionary.ImageURL = GlobalSettings.Path + "/images/editor/dictionaryItem.gif";
umbDictionary.OnClickCommand =
ClientTools.Scripts.OpenModalWindow(
IOHelper.ResolveUrl(SystemDirectories.Umbraco) + "/dialogs/umbracoField.aspx?objectId=" +
editorSource.ClientID + "&tagName=UMBRACOGETDICTIONARY&mvcView=true", ui.Text("template", "insertDictionaryItem"),
640, 550);
umbDictionary.AltText = "Insert umbraco dictionary item";
var macroSplitButton = new InsertMacroSplitButton
{
ClientCallbackInsertMacroMarkup = "function(alias) {editViewEditor.insertMacroMarkup(alias);}",
ClientCallbackOpenMacroModel = "function(alias) {editViewEditor.openMacroModal(alias);}"
};
editorSource.Menu.InsertNewControl(macroSplitButton, 40);
MenuIconI umbTemplateQueryBuilder = editorSource.Menu.NewIcon();
umbTemplateQueryBuilder.ImageURL = UmbracoPath + "/images/editor/inshtml.gif";
umbTemplateQueryBuilder.OnClickCommand = "editViewEditor.openQueryModal()";
umbTemplateQueryBuilder.AltText = "Open query builder";
if (_template == null)
{
InitializeEditorForPartialView();
}
else
{
InitializeEditorForTemplate();
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/codeEditorSave.asmx"));
ScriptManager.GetCurrent(Page).Services.Add(new ServiceReference("../webservices/legacyAjaxCalls.asmx"));
}
/// <summary>
/// Configure the editor for partial view editing
/// </summary>
private void InitializeEditorForPartialView()
{
pp_masterTemplate.Visible = false;
pp_alias.Visible = false;
pp_name.Text = "Filename";
}
/// <summary>
/// Configure the editor for editing a template
/// </summary>
private void InitializeEditorForTemplate()
{
//TODO: implement content placeholders, etc... just like we had in v5
editorSource.Menu.InsertSplitter();
MenuIconI umbRenderBody = editorSource.Menu.NewIcon();
umbRenderBody.ImageURL = UmbracoPath + "/images/editor/renderbody.gif";
//umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder");
umbRenderBody.AltText = "Insert @RenderBody()";
umbRenderBody.OnClickCommand = "editViewEditor.insertRenderBody()";
MenuIconI umbSection = editorSource.Menu.NewIcon();
umbSection.ImageURL = UmbracoPath + "/images/editor/masterpagePlaceHolder.gif";
//umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder");
umbSection.AltText = "Insert Section";
umbSection.OnClickCommand = "editViewEditor.openSnippetModal('section')";
MenuIconI umbRenderSection = editorSource.Menu.NewIcon();
umbRenderSection.ImageURL = UmbracoPath + "/images/editor/masterpageContent.gif";
//umbContainer.AltText = ui.Text("template", "insertContentAreaPlaceHolder");
umbRenderSection.AltText = "Insert @RenderSection";
umbRenderSection.OnClickCommand = "editViewEditor.openSnippetModal('rendersection')";
}
}
}
@@ -1,132 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Umbraco.Web.UI.Umbraco.Settings.Views {
public partial class EditView {
/// <summary>
/// JsInclude1 control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::ClientDependency.Core.Controls.JsInclude JsInclude1;
/// <summary>
/// Panel1 control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.TabView Panel1;
/// <summary>
/// Pane8 control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.Pane Pane8;
/// <summary>
/// pp_source control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.PropertyPanel pp_source;
/// <summary>
/// editorSource control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.CodeArea editorSource;
/// <summary>
/// Pane7 control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.Pane Pane7;
/// <summary>
/// pp_name control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.PropertyPanel pp_name;
/// <summary>
/// PathPrefix control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.Literal PathPrefix;
/// <summary>
/// NameTxt control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.TextBox NameTxt;
/// <summary>
/// pp_alias control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.PropertyPanel pp_alias;
/// <summary>
/// AliasTxt control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.TextBox AliasTxt;
/// <summary>
/// pp_masterTemplate control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::umbraco.uicontrols.PropertyPanel pp_masterTemplate;
/// <summary>
/// MasterTemplate control.
/// </summary>
/// <remarks>
/// Auto-generated field.
/// To modify move field declaration from designer file to code-behind file.
/// </remarks>
protected global::System.Web.UI.WebControls.DropDownList MasterTemplate;
}
}
@@ -262,7 +262,7 @@ namespace Umbraco.Web.Editors
},
{
"mediaTypeApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<MediaTypeController>(
controller => controller.GetAllowedChildren("0"))
controller => controller.GetAllowedChildren(0))
},
{
"macroApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<MacroController>(
@@ -367,6 +367,10 @@ namespace Umbraco.Web.Editors
{
"templateQueryApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<TemplateQueryController>(
controller => controller.PostTemplateQuery(null))
},
{
"codeFileApiBaseUrl", Url.GetUmbracoApiServiceBaseUrl<CodeFileController>(
controller => controller.GetByPath("", ""))
}
}
},
@@ -0,0 +1,234 @@
using AutoMapper;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Filters;
namespace Umbraco.Web.Editors
{
[PluginController("UmbracoApi")]
[PrefixlessBodyModelValidator]
[UmbracoApplicationAuthorizeAttribute(Core.Constants.Applications.Settings)]
public class CodeFileController : BackOfficeNotificationsController
{
/// <summary>
/// Used to create a brand new file
/// </summary>
/// <param name="type">This is a string but will be 'scripts' 'partialViews', 'partialViewMacros'</param>
/// <param name="display"></param>
/// <returns>Will return a simple 200 if file creation succeeds</returns>
[ValidationFilter]
public HttpResponseMessage PostCreate(string type, CodeFileDisplay display)
{
switch (type)
{
case Core.Constants.Trees.PartialViews:
var view = new PartialView(display.VirtualPath);
var result = Services.FileService.CreatePartialView(view, display.Snippet, Security.CurrentUser.Id);
return result.Success == true ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateNotificationValidationErrorResponse(result.Exception.Message);
case Core.Constants.Trees.PartialViewMacros:
var viewMacro = new PartialView(display.VirtualPath);
var resultMacro = Services.FileService.CreatePartialViewMacro(viewMacro, display.Snippet, Security.CurrentUser.Id);
return resultMacro.Success == true ? Request.CreateResponse(HttpStatusCode.OK) : Request.CreateNotificationValidationErrorResponse(resultMacro.Exception.Message);
case Core.Constants.Trees.Scripts:
var script = new Script(display.VirtualPath);
Services.FileService.SaveScript(script, Security.CurrentUser.Id);
return Request.CreateResponse(HttpStatusCode.OK);
default:
return Request.CreateResponse(HttpStatusCode.NotFound);
}
}
/// <summary>
/// Used to get a specific file from disk via the FileService
/// </summary>
/// <param name="type">This is a string but will be 'scripts' 'partialViews', 'partialViewMacros'</param>
/// <param name="virtualPath">The filename or urlencoded path of the file to open</param>
/// <returns>The file and its contents from the virtualPath</returns>
public CodeFileDisplay GetByPath(string type, string virtualPath)
{
if (string.IsNullOrWhiteSpace(type) || string.IsNullOrWhiteSpace(virtualPath))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
virtualPath = System.Web.HttpUtility.UrlDecode(virtualPath);
switch (type)
{
case Core.Constants.Trees.PartialViews:
var view = Services.FileService.GetPartialView(virtualPath);
if (view != null)
{
var display = Mapper.Map<IPartialView, CodeFileDisplay>(view);
display.FileType = Core.Constants.Trees.PartialViews;
return display;
}
return null;
case Core.Constants.Trees.PartialViewMacros:
var viewMacro = Services.FileService.GetPartialViewMacro(virtualPath);
if (viewMacro != null)
{
var display = Mapper.Map<IPartialView, CodeFileDisplay>(viewMacro);
display.FileType = Core.Constants.Trees.PartialViewMacros;
return display;
}
return null;
case Core.Constants.Trees.Scripts:
var script = Services.FileService.GetScriptByName(virtualPath);
if (script != null)
{
var display = Mapper.Map<Script, CodeFileDisplay>(script);
display.FileType = Core.Constants.Trees.Scripts;
return display;
}
return null;
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
/// <summary>
/// Used to delete a specific file from disk via the FileService
/// </summary>
/// <param name="type">This is a string but will be 'scripts' 'partialViews', 'partialViewMacros'</param>
/// <param name="virtualPath">The filename or urlencoded path of the file to delete</param>
/// <returns>Will return a simple 200 if file deletion succeeds</returns>
[HttpDelete]
[HttpPost]
public HttpResponseMessage Delete(string type, string virtualPath)
{
if (string.IsNullOrWhiteSpace(type) == false && string.IsNullOrWhiteSpace(virtualPath) == false)
{
switch (type)
{
case Core.Constants.Trees.PartialViews:
if (Services.FileService.DeletePartialView(virtualPath, Security.CurrentUser.Id))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View found with the specified path");
case Core.Constants.Trees.PartialViewMacros:
if (Services.FileService.DeletePartialViewMacro(virtualPath, Security.CurrentUser.Id))
{
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Partial View Macro found with the specified path");
case Core.Constants.Trees.Scripts:
if (Services.FileService.GetScriptByName(virtualPath) != null)
{
Services.FileService.DeleteScript(virtualPath, Security.CurrentUser.Id);
return Request.CreateResponse(HttpStatusCode.OK);
}
return Request.CreateErrorResponse(HttpStatusCode.NotFound, "No Script found with the specified path");
default:
return Request.CreateResponse(HttpStatusCode.NotFound);
}
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
/// <summary>
/// Used to save/update an existing file after its initial creation
/// </summary>
/// <param name="display"></param>
/// <returns>The updated CodeFileDisplay model</returns>
public CodeFileDisplay PostSave(CodeFileDisplay display)
{
if (ModelState.IsValid == false)
{
throw new HttpResponseException(Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState));
}
if (display == null || string.IsNullOrWhiteSpace(display.FileType))
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
switch (display.FileType)
{
case Core.Constants.Trees.PartialViews:
var view = Services.FileService.GetPartialView(display.VirtualPath);
if (view != null)
{
// might need to find the path
var orgPath = view.OriginalPath.Substring(0, view.OriginalPath.IndexOf(view.Name));
view.Path = orgPath + display.Name;
view.Content = display.Content;
var result = Services.FileService.SavePartialView(view, Security.CurrentUser.Id);
if (result.Success == true)
{
return Mapper.Map(view, display);
}
display.AddErrorNotification(
Services.TextService.Localize("speechBubbles/partialViewErrorHeader"),
Services.TextService.Localize("speechBubbles/partialViewErrorText"));
}
else
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
break;
case Core.Constants.Trees.PartialViewMacros:
var viewMacro = Services.FileService.GetPartialViewMacro(display.VirtualPath);
if (viewMacro != null)
{
viewMacro.Content = display.Content;
viewMacro.Path = display.Name;
var result = Services.FileService.SavePartialViewMacro(viewMacro, Security.CurrentUser.Id);
if (result.Success == false)
{
display.AddErrorNotification(
Services.TextService.Localize("speechBubbles/macroPartialViewErrorHeader"),
Services.TextService.Localize("speechBubbles/macroPartialViewErrorText"));
}
}
else
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
break;
case Core.Constants.Trees.Scripts:
var script = Services.FileService.GetScriptByName(display.VirtualPath);
if (script != null)
{
script.Content = display.Content;
script.Path = display.Name;
Services.FileService.SaveScript(script, Security.CurrentUser.Id);
}
else
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
break;
default:
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return display;
}
}
}
@@ -4,11 +4,9 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Text;
using System.Web.Http;
using System.Web.Http.ModelBinding;
using System.Web.Http.ModelBinding.Binders;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Logging;
@@ -17,24 +15,15 @@ using Umbraco.Core.Models.Membership;
using Umbraco.Core.Persistence.DatabaseModelDefinitions;
using Umbraco.Core.Publishing;
using Umbraco.Core.Services;
using Umbraco.Web.Models;
using Umbraco.Web.Models.ContentEditing;
using Umbraco.Web.Models.Mapping;
using Umbraco.Web.Mvc;
using Umbraco.Web.Security;
using Umbraco.Web.WebApi;
using Umbraco.Web.WebApi.Binders;
using Umbraco.Web.WebApi.Filters;
using umbraco;
using Umbraco.Core.Models;
using Umbraco.Core.Dynamics;
using umbraco.BusinessLogic.Actions;
using umbraco.cms.businesslogic.web;
using umbraco.presentation.preview;
using Umbraco.Core.PropertyEditors;
using Umbraco.Web.UI;
using Constants = Umbraco.Core.Constants;
using Notification = Umbraco.Web.Models.ContentEditing.Notification;
namespace Umbraco.Web.Editors
{
@@ -97,6 +97,13 @@ namespace Umbraco.Web.Editors
var dictionary = new Dictionary<string, object>();
//add the files if any
var files = contentItem.UploadedFiles.Where(x => x.PropertyAlias == property.Alias).ToArray();
if (files.Length > 0)
{
dictionary.Add("files", files);
// add extra things needed to figure out where to put the files
dictionary.Add("cuid", contentItem.PersistedContent.Key);
dictionary.Add("puid", dboProperty.PropertyType.Key);
}
foreach (var file in files)
file.FileName = file.FileName.ToSafeFileName();
+70 -33
View File
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Http;
@@ -27,6 +28,7 @@ using Umbraco.Web.Mvc;
using Umbraco.Web.WebApi;
using System.Linq;
using System.Runtime.Serialization;
using System.Web.Http.Controllers;
using Umbraco.Web.WebApi.Binders;
using Umbraco.Web.WebApi.Filters;
using umbraco;
@@ -45,9 +47,22 @@ namespace Umbraco.Web.Editors
/// access to ALL of the methods on this controller will need access to the media application.
/// </remarks>
[PluginController("UmbracoApi")]
[UmbracoApplicationAuthorizeAttribute(Constants.Applications.Media)]
[UmbracoApplicationAuthorize(Constants.Applications.Media)]
[MediaControllerControllerConfiguration]
public class MediaController : ContentControllerBase
{
/// <summary>
/// Configures this controller with a custom action selector
/// </summary>
private class MediaControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector(
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetChildren", "id", typeof(int), typeof(Guid), typeof(string))));
}
}
/// <summary>
/// Constructor
/// </summary>
@@ -173,40 +188,10 @@ namespace Umbraco.Web.Editors
}
/// <summary>
/// Returns the child media objects
/// Returns the child media objects - using the entity INT id
/// </summary>
[FilterAllowedOutgoingMedia(typeof(IEnumerable<ContentItemBasic<ContentPropertyBasic, IMedia>>), "Items")]
public PagedResult<ContentItemBasic<ContentPropertyBasic, IMedia>> GetChildren(string id,
int pageNumber = 0,
int pageSize = 0,
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
bool orderBySystemField = true,
string filter = "")
{
int idInt; Guid idGuid;
if (Guid.TryParse(id, out idGuid))
{
var entity = Services.EntityService.GetByKey(idGuid);
if (entity != null)
{
return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
}
else
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
}
else if (int.TryParse(id, out idInt))
{
return GetChildren(idInt, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
private PagedResult<ContentItemBasic<ContentPropertyBasic, IMedia>> GetChildren(int id,
public PagedResult<ContentItemBasic<ContentPropertyBasic, IMedia>> GetChildren(int id,
int pageNumber = 0,
int pageSize = 0,
string orderBy = "SortOrder",
@@ -240,6 +225,58 @@ namespace Umbraco.Web.Editors
return pagedResult;
}
/// <summary>
/// Returns the child media objects - using the entity GUID id
/// </summary>
/// <param name="id"></param>
/// <param name="pageNumber"></param>
/// <param name="pageSize"></param>
/// <param name="orderBy"></param>
/// <param name="orderDirection"></param>
/// <param name="orderBySystemField"></param>
/// <param name="filter"></param>
/// <returns></returns>
[FilterAllowedOutgoingMedia(typeof(IEnumerable<ContentItemBasic<ContentPropertyBasic, IMedia>>), "Items")]
public PagedResult<ContentItemBasic<ContentPropertyBasic, IMedia>> GetChildren(Guid id,
int pageNumber = 0,
int pageSize = 0,
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
bool orderBySystemField = true,
string filter = "")
{
var entity = Services.EntityService.GetByKey(id);
if (entity != null)
{
return GetChildren(entity.Id, pageNumber, pageSize, orderBy, orderDirection, orderBySystemField, filter);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
[Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")]
[EditorBrowsable(EditorBrowsableState.Never)]
[UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)]
public PagedResult<ContentItemBasic<ContentPropertyBasic, IMedia>> GetChildren(string id,
int pageNumber = 0,
int pageSize = 0,
string orderBy = "SortOrder",
Direction orderDirection = Direction.Ascending,
bool orderBySystemField = true,
string filter = "")
{
foreach (var type in new[] { typeof(int), typeof(Guid) })
{
var parsed = id.TryConvertTo(type);
if (parsed)
{
//oooh magic! will auto select the right overload
return GetChildren((dynamic)parsed.Result);
}
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
/// <summary>
/// Moves an item to the recycle bin, if it is already there then it will permanently delete it
/// </summary>
+52 -17
View File
@@ -13,6 +13,9 @@ using Umbraco.Web.WebApi;
using Umbraco.Core.Services;
using Umbraco.Core.Models.EntityBase;
using System;
using System.ComponentModel;
using System.Web.Http.Controllers;
using Umbraco.Core;
namespace Umbraco.Web.Editors
{
@@ -26,8 +29,21 @@ namespace Umbraco.Web.Editors
[PluginController("UmbracoApi")]
[UmbracoTreeAuthorize(Constants.Trees.MediaTypes)]
[EnableOverrideAuthorization]
[MediaTypeControllerControllerConfigurationAttribute]
public class MediaTypeController : ContentTypeControllerBase
{
/// <summary>
/// Configures this controller with a custom action selector
/// </summary>
private class MediaTypeControllerControllerConfigurationAttribute : Attribute, IControllerConfiguration
{
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector(
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetAllowedChildren", "contentId", typeof(int), typeof(Guid), typeof(string))));
}
}
/// <summary>
/// Constructor
/// </summary>
@@ -172,26 +188,11 @@ namespace Umbraco.Web.Editors
/// <summary>
/// Returns the allowed child content type objects for the content item id passed in
/// Returns the allowed child content type objects for the content item id passed in - based on an INT id
/// </summary>
/// <param name="contentId"></param>
[UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)]
public IEnumerable<ContentTypeBasic> GetAllowedChildren(string contentId)
{
Guid idGuid = Guid.Empty;
int idInt;
if (Guid.TryParse(contentId, out idGuid)) {
var entity = ApplicationContext.Services.EntityService.GetByKey(idGuid);
return GetAllowedChildrenInternal(entity.Id);
} else if (int.TryParse(contentId, out idInt))
{
return GetAllowedChildrenInternal(idInt);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
private IEnumerable<ContentTypeBasic> GetAllowedChildrenInternal(int contentId)
public IEnumerable<ContentTypeBasic> GetAllowedChildren(int contentId)
{
if (contentId == Constants.System.RecycleBinContent)
return Enumerable.Empty<ContentTypeBasic>();
@@ -231,6 +232,40 @@ namespace Umbraco.Web.Editors
return basics;
}
/// <summary>
/// Returns the allowed child content type objects for the content item id passed in - based on a GUID id
/// </summary>
/// <param name="contentId"></param>
[UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)]
public IEnumerable<ContentTypeBasic> GetAllowedChildren(Guid contentId)
{
var entity = ApplicationContext.Services.EntityService.GetByKey(contentId);
if (entity != null)
{
return GetAllowedChildren(entity.Id);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
[Obsolete("Do not use this method, use either the overload with INT or GUID instead, this will be removed in future versions")]
[EditorBrowsable(EditorBrowsableState.Never)]
[UmbracoTreeAuthorize(Constants.Trees.MediaTypes, Constants.Trees.Media)]
public IEnumerable<ContentTypeBasic> GetAllowedChildren(string contentId)
{
foreach (var type in new[] { typeof(int), typeof(Guid) })
{
var parsed = contentId.TryConvertTo(type);
if (parsed)
{
//oooh magic! will auto select the right overload
return GetAllowedChildren((dynamic)parsed.Result);
}
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
/// <summary>
/// Move the media type
/// </summary>
@@ -0,0 +1,84 @@
using System;
using System.Linq;
using System.Web;
using System.Web.Http.Controllers;
using Umbraco.Core;
namespace Umbraco.Web.Editors
{
/// <summary>
/// This is used to auto-select specific actions on controllers that would otherwise be ambiguous based on a single parameter type
/// </summary>
/// <remarks>
/// As an example, lets say we have 2 methods: GetChildren(int id) and GetChildren(Guid id), by default Web Api won't allow this since
/// it won't know what to select, but if this Tuple is passed in new Tuple{string, string}("GetChildren", "id")
/// </remarks>
internal class ParameterSwapControllerActionSelector : ApiControllerActionSelector
{
private readonly ParameterSwapInfo[] _actions;
/// <summary>
/// Constructor accepting a list of action name + parameter name
/// </summary>
/// <param name="actions"></param>
public ParameterSwapControllerActionSelector(params ParameterSwapInfo[] actions)
{
_actions = actions;
}
public override HttpActionDescriptor SelectAction(HttpControllerContext controllerContext)
{
var found = _actions.FirstOrDefault(x => controllerContext.Request.RequestUri.GetLeftPart(UriPartial.Path).InvariantEndsWith(x.ActionName));
if (found != null)
{
var id = HttpUtility.ParseQueryString(controllerContext.Request.RequestUri.Query).Get(found.ParamName);
if (id != null)
{
var idTypes = found.SupportedTypes;
foreach (var idType in idTypes)
{
var converted = id.TryConvertTo(idType);
if (converted)
{
var method = MatchByType(idType, controllerContext, found);
if (method != null)
return method;
}
}
}
}
return base.SelectAction(controllerContext);
}
private static ReflectedHttpActionDescriptor MatchByType(Type idType, HttpControllerContext controllerContext, ParameterSwapInfo found)
{
var controllerType = controllerContext.Controller.GetType();
var methods = controllerType.GetMethods().Where(info => info.Name == found.ActionName).ToArray();
if (methods.Length > 1)
{
//choose the one that has the parameter with the T type
var method = methods.FirstOrDefault(x => x.GetParameters().FirstOrDefault(p => p.Name == found.ParamName && p.ParameterType == idType) != null);
return new ReflectedHttpActionDescriptor(controllerContext.ControllerDescriptor, method);
}
return null;
}
internal class ParameterSwapInfo
{
public string ActionName { get; private set; }
public string ParamName { get; private set; }
public Type[] SupportedTypes { get; private set; }
public ParameterSwapInfo(string actionName, string paramName, params Type[] supportedTypes)
{
ActionName = actionName;
ParamName = paramName;
SupportedTypes = supportedTypes;
}
}
}
}
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
namespace Umbraco.Web.Models.ContentEditing
{
[DataContract(Name = "scriptFile", Namespace = "")]
public class CodeFileDisplay : INotificationModel
{
[DataMember(Name = "virtualPath", IsRequired = true)]
public string VirtualPath { get; set; }
[DataMember(Name = "name", IsRequired = true)]
public string Name { get; set; }
[DataMember(Name = "content", IsRequired = true)]
public string Content { get; set; }
[DataMember(Name = "fileType", IsRequired = true)]
public string FileType { get; set; }
[DataMember(Name = "snippet")]
[ReadOnly(true)]
public string Snippet { get; set; }
public List<Notification> Notifications { get; private set; }
}
}
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using AutoMapper;
using Umbraco.Core;
using Umbraco.Core.Models.Mapping;
using Umbraco.Core.Models;
using Umbraco.Web.Models.ContentEditing;
namespace Umbraco.Web.Models.Mapping
{
public class CodeFileDisplayMapper : MapperConfiguration
{
public override void ConfigureMappings(IConfiguration config, ApplicationContext applicationContext)
{
config.CreateMap<IPartialView, CodeFileDisplay>()
.ForMember(x => x.FileType, exp => exp.Ignore())
.ForMember(x => x.Notifications, exp => exp.Ignore())
.ForMember(x => x.Snippet, exp => exp.Ignore());
config.CreateMap<Script, CodeFileDisplay>()
.ForMember(x => x.FileType, exp => exp.Ignore())
.ForMember(x => x.Notifications, exp => exp.Ignore())
.ForMember(x => x.Snippet, exp => exp.Ignore());
config.CreateMap<CodeFileDisplay, IPartialView>()
.ForMember(x => x.Id, exp => exp.Ignore())
.ForMember(x => x.Key, exp => exp.Ignore())
.ForMember(x => x.Path, exp => exp.Ignore())
.ForMember(x => x.CreateDate, exp => exp.Ignore())
.ForMember(x => x.UpdateDate, exp => exp.Ignore())
.ForMember(x => x.Path, exp => exp.Ignore())
.ForMember(x => x.Alias, exp => exp.Ignore())
.ForMember(x => x.Name, exp => exp.Ignore())
.ForMember(x => x.OriginalPath, exp => exp.Ignore())
.ForMember(x => x.HasIdentity, exp => exp.Ignore());
config.CreateMap<CodeFileDisplay, Script>()
.ForMember(x => x.Id, exp => exp.Ignore())
.ForMember(x => x.Key, exp => exp.Ignore())
.ForMember(x => x.Path, exp => exp.Ignore())
.ForMember(x => x.CreateDate, exp => exp.Ignore())
.ForMember(x => x.UpdateDate, exp => exp.Ignore())
.ForMember(x => x.Path, exp => exp.Ignore())
.ForMember(x => x.Alias, exp => exp.Ignore())
.ForMember(x => x.Name, exp => exp.Ignore())
.ForMember(x => x.OriginalPath, exp => exp.Ignore())
.ForMember(x => x.HasIdentity, exp => exp.Ignore());
}
}
}
@@ -125,9 +125,10 @@ namespace Umbraco.Web.Trees
// use helper method to ensure we support both integer and guid lookups
int iid;
// if it's the root node, we won't use the look up
if (id != "-1")
// look up from GUID if it's not an integer
if (int.TryParse(id, out iid) == false)
{
var idEntity = GetEntityFromId(id);
if (idEntity == null)
{
@@ -135,10 +136,6 @@ namespace Umbraco.Web.Trees
}
iid = idEntity.Id;
}
else
{
iid = int.Parse(id);
}
//if a request is made for the root node data but the user's start node is not the default, then
@@ -13,6 +13,7 @@ namespace Umbraco.Web.Trees
{
protected abstract string FilePath { get; }
protected abstract string FileSearchPattern { get; }
protected abstract string FileIcon { get; }
/// <summary>
/// Inheritors can override this method to modify the file node that is created.
@@ -79,7 +80,7 @@ namespace Umbraco.Web.Trees
if (filterByMultipleExtensions && Array.IndexOf<string>(allowedExtensions, file.Extension.ToLower().Trim('.')) < 0)
continue;
var node = CreateTreeNode(orgPath + file.Name, orgPath, queryStrings, file.Name, "icon-file", false);
var node = CreateTreeNode(System.Web.HttpUtility.UrlEncode(orgPath + file.Name), orgPath, queryStrings, file.Name, FileIcon, false);
OnRenderFileNode(ref node);
@@ -1,49 +0,0 @@
using System.Text;
using Umbraco.Core.IO;
using umbraco.businesslogic;
using umbraco.cms.presentation.Trees;
using Umbraco.Core;
namespace Umbraco.Web.Trees
{
/// <summary>
/// Tree for displaying partial view macros in the developer app
/// </summary>
[Tree(Constants.Applications.Developer, "partialViewMacros", "Partial View Macro Files", sortOrder: 6)]
public class PartialViewMacrosTree : PartialViewsTree
{
public PartialViewMacrosTree(string application) : base(application)
{
}
protected override string FilePath
{
get { return SystemDirectories.MvcViews + "/MacroPartials/"; }
}
public override void RenderJS(ref StringBuilder javascript)
{
javascript.Append(
@"
function openMacroPartialView(id) {
UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViewMacros&file=' + id);
}
");
}/// <summary>
/// Ensures that no folders can be added
/// </summary>
/// <param name="xNode"></param>
protected override void OnRenderFolderNode(ref XmlTreeNode xNode)
{
base.OnRenderFolderNode(ref xNode);
xNode.NodeType = "partialViewMacrosFolder";
}
protected override void ChangeNodeAction(XmlTreeNode xNode)
{
xNode.Action = xNode.Action.Replace("openFile", "openMacroPartialView");
}
}
}
@@ -0,0 +1,60 @@
using Umbraco.Core;
using Umbraco.Core.IO;
using umbraco.BusinessLogic.Actions;
using Umbraco.Web.Models.Trees;
using System.Net.Http.Formatting;
using Umbraco.Core.Services;
namespace Umbraco.Web.Trees
{
/// <summary>
/// Tree for displaying partial view macros in the developer app
/// </summary>
[Tree(Constants.Applications.Developer, "partialViewMacros", "Partial View Macro Files", sortOrder: 6)]
public class PartialViewMacrosTreeController : FileSystemTreeController
{
protected override string FilePath
{
get { return SystemDirectories.MacroPartials; }
}
protected override string FileSearchPattern
{
get { return "*.cshtml"; }
}
protected override string FileIcon
{
get { return "icon-article"; }
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
if (id == Constants.System.Root.ToInvariantString())
{
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
return menu;
}
// TODO: Wire up new delete dialog
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
return menu;
}
protected override void OnRenderFileNode(ref TreeNode treeNode)
{
base.OnRenderFileNode(ref treeNode);
}
protected override void OnRenderFolderNode(ref TreeNode treeNode)
{
//TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now.
treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);";
}
}
}
-88
View File
@@ -1,88 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.IO;
using umbraco.BusinessLogic.Actions;
using umbraco.businesslogic;
using umbraco.cms.businesslogic.template;
using umbraco.cms.presentation.Trees;
using umbraco.interfaces;
namespace Umbraco.Web.Trees
{
/// <summary>
/// Tree for displaying partial views in the settings app
/// </summary>
[Tree(Constants.Applications.Settings, "partialViews", "Partial Views", sortOrder: 2)]
public class PartialViewsTree : FileSystemTree
{
public PartialViewsTree(string application) : base(application) { }
public override void RenderJS(ref StringBuilder javascript)
{
javascript.Append(
@"
function openPartialView(id) {
UmbClientMgr.contentFrame('Settings/Views/EditView.aspx?treeType=partialViews&file=' + id);
}
");
}
protected override void CreateRootNode(ref XmlTreeNode rootNode)
{
rootNode.NodeType = TreeAlias;
rootNode.NodeID = "init";
}
protected override string FilePath
{
get { return SystemDirectories.MvcViews + "/Partials/"; }
}
protected override string FileSearchPattern
{
get { return "*.cshtml"; }
}
/// <summary>
/// Ensures that no folders can be added
/// </summary>
/// <param name="xNode"></param>
protected override void OnRenderFolderNode(ref XmlTreeNode xNode)
{
// We should allow folder hierarchy for organization in large sites.
xNode.Action = "javascript:void(0);";
xNode.NodeType = "partialViewsFolder";
xNode.Menu = new List<IAction>(new IAction[]
{
ActionNew.Instance,
ContextMenuSeperator.Instance,
ActionDelete.Instance,
ContextMenuSeperator.Instance,
ActionRefresh.Instance
});
}
protected virtual void ChangeNodeAction(XmlTreeNode xNode)
{
xNode.Action = xNode.Action.Replace("openFile", "openPartialView");
}
protected override void OnRenderFileNode(ref XmlTreeNode xNode)
{
ChangeNodeAction(xNode);
xNode.Icon = "icon-article";
xNode.OpenIcon = "icon-article";
xNode.Text = xNode.Text.StripFileExtension();
}
}
}
@@ -0,0 +1,59 @@
using Umbraco.Core;
using Umbraco.Core.IO;
using umbraco.BusinessLogic.Actions;
using Umbraco.Web.Models.Trees;
using System.Net.Http.Formatting;
using Umbraco.Core.Services;
namespace Umbraco.Web.Trees
{
/// <summary>
/// Tree for displaying partial views in the settings app
/// </summary>
[Tree(Constants.Applications.Settings, "partialViews", "Partial Views", sortOrder: 2)]
public class PartialViewsTreeController : FileSystemTreeController
{
protected override string FilePath
{
get { return SystemDirectories.PartialViews; }
}
protected override string FileSearchPattern
{
get { return "*.cshtml"; }
}
protected override string FileIcon
{
get { return "icon-article"; }
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
if (id == Constants.System.Root.ToInvariantString())
{
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
return menu;
}
// TODO: Wire up new delete dialog
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
return menu;
}
protected override void OnRenderFileNode(ref TreeNode treeNode)
{
base.OnRenderFileNode(ref treeNode);
}
protected override void OnRenderFolderNode(ref TreeNode treeNode)
{
//TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now.
treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);";
}
}
}
@@ -0,0 +1,56 @@
using Umbraco.Core;
using Umbraco.Core.IO;
using umbraco.BusinessLogic.Actions;
using Umbraco.Web.Models.Trees;
using System.Net.Http.Formatting;
using Umbraco.Core.Services;
namespace Umbraco.Web.Trees
{
[Tree(Constants.Applications.Settings, "scripts", "Scripts", sortOrder: 4)]
public class ScriptTreeController : FileSystemTreeController
{
protected override string FilePath
{
get { return SystemDirectories.Scripts; }
}
protected override string FileSearchPattern
{
get { return "*.js"; }
}
protected override string FileIcon
{
get { return "icon-script"; }
}
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
var menu = new MenuItemCollection();
if (id == Constants.System.Root.ToInvariantString())
{
//refresh action
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
return menu;
}
// TODO: Wire up new delete dialog
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
return menu;
}
protected override void OnRenderFolderNode(ref TreeNode treeNode)
{
//TODO: This isn't the best way to ensure a noop process for clicking a node but it works for now.
treeNode.AdditionalData["jsClickCallback"] = "javascript:void(0);";
}
protected override void OnRenderFileNode(ref TreeNode treeNode)
{
base.OnRenderFileNode(ref treeNode);
}
}
}
+7 -2
View File
@@ -287,6 +287,8 @@
<Compile Include="Editors\EditorValidator.cs" />
<Compile Include="Editors\GravatarController.cs" />
<Compile Include="Editors\TemplateController.cs" />
<Compile Include="Editors\ParameterSwapControllerActionSelector.cs" />
<Compile Include="Editors\CodeFileController.cs" />
<Compile Include="HealthCheck\Checks\Config\AbstractConfigCheck.cs" />
<Compile Include="HealthCheck\Checks\Config\AcceptableConfiguration.cs" />
<Compile Include="HealthCheck\Checks\Config\ConfigurationService.cs" />
@@ -339,11 +341,13 @@
<Compile Include="Models\ContentEditing\SimpleNotificationModel.cs" />
<Compile Include="Models\ContentEditing\TemplateDisplay.cs" />
<Compile Include="Models\LocalPackageInstallModel.cs" />
<Compile Include="Models\Mapping\CodeFileDisplayMapper.cs" />
<Compile Include="Models\Mapping\ContentTypeModelMapperExtensions.cs" />
<Compile Include="Models\Mapping\LockedCompositionsResolver.cs" />
<Compile Include="Models\Mapping\PropertyGroupDisplayResolver.cs" />
<Compile Include="Models\Mapping\TemplateModelMapper.cs" />
<Compile Include="Models\PackageInstallResult.cs" />
<Compile Include="Models\ContentEditing\CodeFileDisplay.cs" />
<Compile Include="Models\SetPasswordModel.cs" />
<Compile Include="Models\RequestPasswordResetModel.cs" />
<Compile Include="Models\PublishedContentWithKeyBase.cs" />
@@ -392,6 +396,7 @@
<Compile Include="Trees\MemberTypeTreeController.cs" />
<Compile Include="Security\OwinExtensions.cs" />
<Compile Include="Security\WebAuthExtensions.cs" />
<Compile Include="Trees\ScriptTreeController.cs" />
<Compile Include="UmbracoDefaultOwinStartup.cs" />
<Compile Include="IUmbracoContextAccessor.cs" />
<Compile Include="Models\ContentEditing\Relation.cs" />
@@ -1046,8 +1051,8 @@
<Compile Include="Strategies\ServerRegistrationEventHandler.cs" />
<Compile Include="Templates\TemplateRenderer.cs" />
<Compile Include="Templates\TemplateUtilities.cs" />
<Compile Include="Trees\PartialViewMacrosTree.cs" />
<Compile Include="Trees\PartialViewsTree.cs" />
<Compile Include="Trees\PartialViewMacrosTreeController.cs" />
<Compile Include="Trees\PartialViewsTreeController.cs" />
<Compile Include="UI\Controls\InsertMacroSplitButton.cs" />
<Compile Include="UI\IAssignedApp.cs" />
<Compile Include="UI\LegacyDialogHandler.cs" />