From af7d9db32bbafb12da13c723b71a0c28a87d0e3d Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Tue, 12 Jul 2022 14:02:59 +0200 Subject: [PATCH 01/32] Add missing null-check --- .../Implement/CreatedPackageSchemaRepository.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs index 04c60261ea..9f921266ca 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/CreatedPackageSchemaRepository.cs @@ -652,10 +652,13 @@ public class CreatedPackageSchemaRepository : ICreatedPackagesRepository // the media file path is different from the URL and is specifically // extracted using the property editor for this media file and the current media file system. Stream mediaStream = _mediaFileManager.GetFile(media, out var mediaFilePath); - xmlMedia.Add(new XAttribute("mediaFilePath", mediaFilePath!)); + if (mediaFilePath is not null) + { + xmlMedia.Add(new XAttribute("mediaFilePath", mediaFilePath)); - // add the stream to our outgoing stream - mediaStreams.Add(mediaFilePath!, mediaStream); + // add the stream to our outgoing stream + mediaStreams.Add(mediaFilePath, mediaStream); + } } IEnumerable medias = _mediaService.GetByIds(definition.MediaUdis); From ce568343ab613a8b7cf29e335aad71fc92e03fe4 Mon Sep 17 00:00:00 2001 From: Matthew Care Date: Thu, 23 Jun 2022 00:36:37 +0200 Subject: [PATCH 02/32] Issue 12551 search bug Previous optimisation incorrectly filtered the index fields needed to return published results --- src/Umbraco.Infrastructure/PublishedContentQuery.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs index 891f938a2b..e8f25aeae1 100644 --- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs @@ -24,6 +24,9 @@ public class PublishedContentQuery : IPublishedContentQuery private readonly IPublishedSnapshot _publishedSnapshot; private readonly IVariationContextAccessor _variationContextAccessor; + private static readonly HashSet s_returnedQueryFields = + new() { ExamineFieldNames.ItemIdFieldName, ExamineFieldNames.CategoryFieldName }; + /// /// Initializes a new instance of the class. /// @@ -293,8 +296,8 @@ public class PublishedContentQuery : IPublishedContentQuery ordering = query.ManagedQuery(term, fields); } - // Only select item ID field, because results are loaded from the published snapshot based on this single value - IOrdering? queryExecutor = ordering.SelectFields(_itemIdFieldNameHashSet); + // Filter selected fields because results are loaded from the published snapshot based on these + IOrdering? queryExecutor = ordering.SelectFields(s_returnedQueryFields); ISearchResults? results = skip == 0 && take == 0 @@ -328,8 +331,8 @@ public class PublishedContentQuery : IPublishedContentQuery if (query is IOrdering ordering) { - // Only select item ID field, because results are loaded from the published snapshot based on this single value - query = ordering.SelectFields(_itemIdFieldNameHashSet); + // Filter selected fields because results are loaded from the published snapshot based on these + query = ordering.SelectFields(s_returnedQueryFields); } ISearchResults? results = skip == 0 && take == 0 From b8ef3836a1b7d33324b9df6aaed665894e3027bf Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 13 Jul 2022 11:14:31 +0200 Subject: [PATCH 03/32] Fix formatting --- src/Umbraco.Infrastructure/PublishedContentQuery.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs index e8f25aeae1..f0518b777d 100644 --- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs @@ -23,8 +23,7 @@ public class PublishedContentQuery : IPublishedContentQuery private readonly IExamineManager _examineManager; private readonly IPublishedSnapshot _publishedSnapshot; private readonly IVariationContextAccessor _variationContextAccessor; - - private static readonly HashSet s_returnedQueryFields = + private static readonly HashSet _returnedQueryFields = new() { ExamineFieldNames.ItemIdFieldName, ExamineFieldNames.CategoryFieldName }; /// @@ -297,7 +296,7 @@ public class PublishedContentQuery : IPublishedContentQuery } // Filter selected fields because results are loaded from the published snapshot based on these - IOrdering? queryExecutor = ordering.SelectFields(s_returnedQueryFields); + IOrdering? queryExecutor = ordering.SelectFields(_returnedQueryFields); ISearchResults? results = skip == 0 && take == 0 @@ -332,7 +331,7 @@ public class PublishedContentQuery : IPublishedContentQuery if (query is IOrdering ordering) { // Filter selected fields because results are loaded from the published snapshot based on these - query = ordering.SelectFields(s_returnedQueryFields); + query = ordering.SelectFields(_returnedQueryFields); } ISearchResults? results = skip == 0 && take == 0 From 3e464933fc21869261abd45fc6e65047c80a48e1 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 13 Jul 2022 11:14:51 +0200 Subject: [PATCH 04/32] delete unused static leftover from merge --- src/Umbraco.Infrastructure/PublishedContentQuery.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Infrastructure/PublishedContentQuery.cs b/src/Umbraco.Infrastructure/PublishedContentQuery.cs index f0518b777d..ef16aeccc6 100644 --- a/src/Umbraco.Infrastructure/PublishedContentQuery.cs +++ b/src/Umbraco.Infrastructure/PublishedContentQuery.cs @@ -18,8 +18,6 @@ namespace Umbraco.Cms.Infrastructure; /// public class PublishedContentQuery : IPublishedContentQuery { - private static readonly HashSet _itemIdFieldNameHashSet = new() {ExamineFieldNames.ItemIdFieldName}; - private readonly IExamineManager _examineManager; private readonly IPublishedSnapshot _publishedSnapshot; private readonly IVariationContextAccessor _variationContextAccessor; From c6f15341483728c7845a99e62156142bda2890e5 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 13 Jul 2022 12:08:23 +0200 Subject: [PATCH 05/32] Change delete action back to move --- src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs index 5b01d348d7..27c26004b9 100644 --- a/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs +++ b/src/Umbraco.Web.BackOffice/Trees/ContentTypeTreeController.cs @@ -180,10 +180,10 @@ public class ContentTypeTreeController : TreeController, ISearchableTree menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); - //no move action if this is a child doc type + // No move action if this is a child doc type if (parent == null) { - menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); + menu.Items.Add(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false); } menu.Items.Add(LocalizedTextService, opensDialog: true, useLegacyIcon: false); From 66cd2145adaf4fdee0179b7fcb0e0aafea3e8ca8 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 13 Jul 2022 13:33:42 +0200 Subject: [PATCH 06/32] Reinstate old ctor and obsolete --- .../Controllers/DictionaryController.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs index f4cefcc76b..ddc4acb6c2 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs @@ -20,6 +20,8 @@ using Umbraco.Extensions; using Umbraco.Cms.Infrastructure.Packaging; using System.Xml.Linq; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Web.Common.DependencyInjection; namespace Umbraco.Cms.Web.BackOffice.Controllers; @@ -60,8 +62,7 @@ public class DictionaryController : BackOfficeNotificationsController { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _localizationService = localizationService ?? throw new ArgumentNullException(nameof(localizationService)); - _backofficeSecurityAccessor = backofficeSecurityAccessor ?? - throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); + _backofficeSecurityAccessor = backofficeSecurityAccessor ?? throw new ArgumentNullException(nameof(backofficeSecurityAccessor)); _globalSettings = globalSettings.Value ?? throw new ArgumentNullException(nameof(globalSettings)); _localizedTextService = localizedTextService ?? throw new ArgumentNullException(nameof(localizedTextService)); _umbracoMapper = umbracoMapper ?? throw new ArgumentNullException(nameof(umbracoMapper)); @@ -70,6 +71,27 @@ public class DictionaryController : BackOfficeNotificationsController _packageDataInstallation = packageDataInstallation ?? throw new ArgumentNullException(nameof(packageDataInstallation)); } + [Obsolete("Please use ctor that also takes an IEntityXmlSerializer, IHostingEnvironment & PackageDataInstallation instead, scheduled for removal in v12")] + public DictionaryController( + ILogger logger, + ILocalizationService localizationService, + IBackOfficeSecurityAccessor backofficeSecurityAccessor, + IOptionsSnapshot globalSettings, + ILocalizedTextService localizedTextService, + IUmbracoMapper umbracoMapper) + : this( + logger, + localizationService, + backofficeSecurityAccessor, + globalSettings, + localizedTextService, + umbracoMapper, + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService(), + StaticServiceProvider.Instance.GetRequiredService()) + { + } + /// /// Deletes a data type with a given ID /// From 7f020861c29e981995e7b7cb9022fe4624ca093e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:19:41 +0200 Subject: [PATCH 07/32] Add ActivatorUtilitiesConstructor to ctor --- src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs index ddc4acb6c2..fe4e3b2378 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/DictionaryController.cs @@ -49,6 +49,7 @@ public class DictionaryController : BackOfficeNotificationsController private readonly IHostingEnvironment _hostingEnvironment; private readonly PackageDataInstallation _packageDataInstallation; + [ActivatorUtilitiesConstructor] public DictionaryController( ILogger logger, ILocalizationService localizationService, From bd2dd3e4424e8a5d292a508488d410ab3538b8ed Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 13 Jul 2022 15:36:56 +0200 Subject: [PATCH 08/32] bumb version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index c5916db03d..11587874da 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.1.0-alpha.1", + "version": "10.1.0-rc", "assemblyVersion": { "precision": "Build" // optional. Use when you want a more precise assembly version than the default major.minor. }, From 920e952e6a31138b562a1aed5cb41916dfee7de7 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Jul 2022 10:13:55 +0200 Subject: [PATCH 09/32] edit pipelines for release --- build/azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/azure-pipelines.yml b/build/azure-pipelines.yml index ae8ca25f52..6289a9bfc4 100644 --- a/build/azure-pipelines.yml +++ b/build/azure-pipelines.yml @@ -305,7 +305,7 @@ stages: - powershell: sqllocaldb start mssqllocaldb displayName: Start localdb (Windows only) condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) - - powershell: docker run --name mssql -d -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD=$(SA_PASSWORD) -e MSSQL_PID=Developer mcr.microsoft.com/mssql/server:2019-latest + - powershell: docker run --name mssql -d -p 1433:1433 -e ACCEPT_EULA=Y -e SA_PASSWORD=$(SA_PASSWORD) -e MSSQL_PID=Developer mcr.microsoft.com/mssql/server:2019-latest displayName: Start SQL Server (Linux only) condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) - task: DotNetCoreCLI@2 @@ -494,7 +494,6 @@ stages: displayName: NuGet release dependsOn: - Deploy_MyGet - - Build_Docs condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.nuGetDeploy}})) jobs: - job: @@ -523,6 +522,7 @@ stages: dependsOn: - Build - Deploy_NuGet + - Build_Docs condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/heads/release/'), ${{parameters.uploadApiDocs}})) jobs: - job: From e92ceae7ee4a66da42a015da3b8a7d3a7f596a3e Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Thu, 14 Jul 2022 11:31:24 +0200 Subject: [PATCH 10/32] Bumb version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 11587874da..0f9ad08c15 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.1.0-rc", + "version": "10.2.0", "assemblyVersion": { "precision": "Build" // optional. Use when you want a more precise assembly version than the default major.minor. }, From 93225a2c34407fefaa3f8c4a22956583d45c284b Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 16:48:01 +0200 Subject: [PATCH 11/32] Add RuntimeModeValidatorCollection and builder --- .../Runtime/RuntimeModeValidatorCollection.cs | 10 ++++++++++ .../Runtime/RuntimeModeValidatorCollectionBuilder.cs | 11 +++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs create mode 100644 src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs new file mode 100644 index 0000000000..97f25f3b8f --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Core.Composing; + +namespace Umbraco.Cms.Infrastructure.Runtime; + +public class RuntimeModeValidatorCollection : BuilderCollectionBase +{ + public RuntimeModeValidatorCollection(Func> items) + : base(items) + { } +} diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs new file mode 100644 index 0000000000..4c755a6040 --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; + +namespace Umbraco.Cms.Infrastructure.Runtime; + +public class RuntimeModeValidatorCollectionBuilder : SetCollectionBuilderBase +{ + protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; + + protected override RuntimeModeValidatorCollectionBuilder This => this; +} From 9cec603d46ac5a550332e0c967b5f9a4bb62375d Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 16:48:52 +0200 Subject: [PATCH 12/32] Add RuntimeModeValidators() extension method --- .../UmbracoBuilder.Collections.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs index bc83695d94..609c5305dc 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs @@ -2,6 +2,7 @@ using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; +using Umbraco.Cms.Infrastructure.Runtime; namespace Umbraco.Extensions; @@ -17,6 +18,10 @@ public static partial class UmbracoBuilderExtensions public static MapperCollectionBuilder? Mappers(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); + /// + /// Gets the NPoco mappers collection builder. + /// + /// The builder. public static NPocoMapperCollectionBuilder? NPocoMappers(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); @@ -26,4 +31,11 @@ public static partial class UmbracoBuilderExtensions /// The builder. public static PackageMigrationPlanCollectionBuilder? PackageMigrationPlans(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); + + /// + /// Gets the runtime mode validators collection builder. + /// + /// The builder. + public static RuntimeModeValidatorCollectionBuilder RuntimeModeValidators(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); } From 1fc64cd4ce5c73a9bb68d8b596e8319a7046bc94 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 16:49:49 +0200 Subject: [PATCH 13/32] Swap registration and resolving of IRuntimeModeValidator items to collection builder --- .../UmbracoBuilder.CoreServices.cs | 11 ++++++----- .../Runtime/RuntimeModeValidationService.cs | 13 ++++++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 154bae9cd0..62bafcd28e 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -86,11 +86,12 @@ public static partial class UmbracoBuilderExtensions // Add runtime mode validation builder.Services.AddSingleton(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); + builder.RuntimeModeValidators() + .Add() + .Add() + .Add() + .Add() + .Add(); // composers builder diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs index 85eec91786..c4bbeb6902 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs @@ -29,11 +29,18 @@ internal class RuntimeModeValidationService : IRuntimeModeValidationService var validationMessages = new List(); // Runtime mode validators are registered transient, but this service is registered as singleton - foreach (var runtimeModeValidator in _serviceProvider.GetServices()) + using (var scope = _serviceProvider.CreateScope()) { - if (runtimeModeValidator.Validate(runtimeMode, out var validationMessage) == false) + var runtimeModeValidators = scope.ServiceProvider.GetService(); + if (runtimeModeValidators is not null) { - validationMessages.Add(validationMessage); + foreach (var runtimeModeValidator in runtimeModeValidators) + { + if (runtimeModeValidator.Validate(runtimeMode, out var validationMessage) == false) + { + validationMessages.Add(validationMessage); + } + } } } From db90b16cb18c3436e133c1fd2b8db266133b22ff Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 22:41:20 +0200 Subject: [PATCH 14/32] Skip saving template file when using runtime mode Production --- .../Implement/TemplateRepository.cs | 33 +++++++++++++++---- .../Repositories/ContentTypeRepositoryTest.cs | 5 ++- .../Repositories/DocumentRepositoryTest.cs | 2 +- .../Repositories/TemplateRepositoryTest.cs | 3 +- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs index 1a0a90269c..9e01320fdc 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs @@ -1,8 +1,10 @@ using System.Text; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -24,17 +26,26 @@ internal class TemplateRepository : EntityRepositoryBase, ITempl { private readonly IIOHelper _ioHelper; private readonly IShortStringHelper _shortStringHelper; - private readonly IViewHelper _viewHelper; private readonly IFileSystem? _viewsFileSystem; + private readonly IViewHelper _viewHelper; + private readonly IOptionsMonitor _runtimeSettings; - public TemplateRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, - FileSystems fileSystems, IIOHelper ioHelper, IShortStringHelper shortStringHelper, IViewHelper viewHelper) + public TemplateRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + FileSystems fileSystems, + IIOHelper ioHelper, + IShortStringHelper shortStringHelper, + IViewHelper viewHelper, + IOptionsMonitor runtimeSettings) : base(scopeAccessor, cache, logger) { _ioHelper = ioHelper; _shortStringHelper = shortStringHelper; _viewsFileSystem = fileSystems.MvcViewsFileSystem; _viewHelper = viewHelper; + _runtimeSettings = runtimeSettings; } public Stream GetFileContentStream(string filepath) @@ -421,8 +432,12 @@ internal class TemplateRepository : EntityRepositoryBase, ITempl template.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set template.Path = nodeDto.Path; - //now do the file work - SaveFile(template); + // Only save file when not in production runtime mode + if (_runtimeSettings.CurrentValue.Mode != RuntimeMode.Production) + { + //now do the file work + SaveFile(template); + } template.ResetDirtyProperties(); @@ -476,8 +491,12 @@ internal class TemplateRepository : EntityRepositoryBase, ITempl IEnumerable axisDefs = GetAxisDefinitions(dto); template.IsMasterTemplate = axisDefs.Any(x => x.ParentId == dto.NodeId); - //now do the file work - SaveFile((Template)entity, originalAlias); + // Only save file when not in production runtime mode + if (_runtimeSettings.CurrentValue.Mode != RuntimeMode.Production) + { + //now do the file work + SaveFile((Template)entity, originalAlias); + } entity.ResetDirtyProperties(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs index 3547182538..e2a691a102 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -5,10 +5,12 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; @@ -84,7 +86,8 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest FileSystems, IOHelper, ShortStringHelper, - Mock.Of()); + Mock.Of(), + Mock.Of>()); var repository = ContentTypeRepository; Template[] templates = { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs index bcdcf7666a..319470917e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs @@ -111,7 +111,7 @@ public class DocumentRepositoryTest : UmbracoIntegrationTest { appCaches ??= AppCaches; - templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of()); + templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of(), Mock.Of>()); var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches, ShortStringHelper); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs index bcf0f185d1..961c11f471 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Text; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; @@ -59,7 +60,7 @@ public class TemplateRepositoryTest : UmbracoIntegrationTest private IViewHelper ViewHelper => GetRequiredService(); private ITemplateRepository CreateRepository(IScopeProvider provider) => - new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper); + new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, Mock.Of>()); [Test] public void Can_Instantiate_Repository() From 802f2baaa07069d528c0cd43e9bb2c38e73501a2 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 22:48:43 +0200 Subject: [PATCH 15/32] Fix editing template and show warning message when using runtime mode production --- .../EmbeddedResources/Lang/en.xml | 1 + .../EmbeddedResources/Lang/en_us.xml | 1 + .../EmbeddedResources/Lang/nl.xml | 1 + .../Controllers/BackOfficeServerVariables.cs | 2 +- .../src/views/templates/edit.controller.js | 1400 +++++++++-------- .../src/views/templates/edit.html | 10 +- 6 files changed, 715 insertions(+), 700 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 72a1aea35b..49ccf24dd7 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1630,6 +1630,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Editor + Production.]]> Failed to delete template with ID %0% Edit template Sections diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 3e2f7c23dc..04883067f6 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -1683,6 +1683,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Rich Text Editor + Production.]]> Failed to delete template with ID %0% Edit template Sections diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml index a9513c5302..06762e1dd7 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml @@ -1444,6 +1444,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Rich Text Editor + Production.]]> Kan sjabloon met ID %0% niet verwijderen Sjabloon aanpassen Secties diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index b7fc6a92f5..0681ed957a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -597,7 +597,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { "assemblyVersion", _umbracoVersion.AssemblyVersion?.ToString() } }; - + app.Add("runtimeMode", _runtimeSettings.Mode.ToString()); //the value is the hash of the version, cdf version and the configured state app.Add("cacheBuster", $"{version}.{_runtimeState.Level}.{_runtimeMinifier.CacheBuster}".GenerateHash()); diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index 99f11f9913..e231ff51f5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -1,723 +1,727 @@ (function () { - "use strict"; + "use strict"; - function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper, editorService) { + function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper, editorService) { - var vm = this; - var oldMasterTemplateAlias = null; - var infiniteMode = $scope.model && $scope.model.infiniteMode; - var id = infiniteMode ? $scope.model.id : $routeParams.id; - var create = infiniteMode ? $scope.model.create : $routeParams.create; + var vm = this; + var oldMasterTemplateAlias = null; + var infiniteMode = $scope.model && $scope.model.infiniteMode; + var id = infiniteMode ? $scope.model.id : $routeParams.id; + var create = infiniteMode ? $scope.model.create : $routeParams.create; - vm.header = {}; - vm.header.editorfor = "template_template"; - vm.header.setPageTitle = true; + vm.runtimeModeProduction = Umbraco.Sys.ServerVariables.application.runtimeMode == 'Production'; - vm.page = {}; - vm.page.loading = true; - vm.templates = []; + vm.header = {}; + vm.header.editorfor = "template_template"; + vm.header.setPageTitle = true; - //menu - vm.page.menu = {}; - vm.page.menu.currentSection = appState.getSectionState("currentSection"); - vm.page.menu.currentNode = null; + vm.page = {}; + vm.page.loading = true; + vm.templates = []; - // insert buttons - vm.page.insertDefaultButton = { - labelKey: "general_insert", - addEllipsis: "true", - handler: function () { - vm.openInsertOverlay(); - } - }; - vm.page.insertSubButtons = [ - { - labelKey: "template_insertPageField", - addEllipsis: "true", - handler: function () { - vm.openPageFieldOverlay(); - } - }, - { - labelKey: "template_insertPartialView", - addEllipsis: "true", - handler: function () { - vm.openPartialOverlay(); - } - }, - { - labelKey: "template_insertDictionaryItem", - addEllipsis: "true", - handler: function () { - vm.openDictionaryItemOverlay(); - } - }, - { - labelKey: "template_insertMacro", - addEllipsis: "true", - handler: function () { - vm.openMacroOverlay() - } - } - ]; + //menu + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState("currentSection"); + vm.page.menu.currentNode = null; - //Used to toggle the keyboard shortcut modal - //From a custom keybinding in ace editor - that conflicts with our own to show the dialog - vm.showKeyboardShortcut = false; + // insert buttons + vm.page.insertDefaultButton = { + labelKey: "general_insert", + addEllipsis: "true", + handler: function () { + vm.openInsertOverlay(); + } + }; + vm.page.insertSubButtons = [ + { + labelKey: "template_insertPageField", + addEllipsis: "true", + handler: function () { + vm.openPageFieldOverlay(); + } + }, + { + labelKey: "template_insertPartialView", + addEllipsis: "true", + handler: function () { + vm.openPartialOverlay(); + } + }, + { + labelKey: "template_insertDictionaryItem", + addEllipsis: "true", + handler: function () { + vm.openDictionaryItemOverlay(); + } + }, + { + labelKey: "template_insertMacro", + addEllipsis: "true", + handler: function () { + vm.openMacroOverlay() + } + } + ]; - //Keyboard shortcuts for help dialog - vm.page.keyboardShortcutsOverview = []; + //Used to toggle the keyboard shortcut modal + //From a custom keybinding in ace editor - that conflicts with our own to show the dialog + vm.showKeyboardShortcut = false; - templateHelper.getGeneralShortcuts().then(function (data) { - vm.page.keyboardShortcutsOverview.push(data); - }); - templateHelper.getEditorShortcuts().then(function (data) { - vm.page.keyboardShortcutsOverview.push(data); - }); - templateHelper.getTemplateEditorShortcuts().then(function (data) { - vm.page.keyboardShortcutsOverview.push(data); + //Keyboard shortcuts for help dialog + vm.page.keyboardShortcutsOverview = []; + + templateHelper.getGeneralShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + templateHelper.getEditorShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + templateHelper.getTemplateEditorShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + + vm.save = function (suppressNotification) { + vm.page.saveButtonState = "busy"; + + if (vm.editor) { + vm.template.content = vm.editor.getValue(); + } + + contentEditingHelper.contentEditorPerformSave({ + saveMethod: templateResource.save, + scope: $scope, + content: vm.template, + rebindCallback: function (orignal, saved) { } + }).then(function (saved) { + + if (!suppressNotification) { + localizationService.localizeMany(["speechBubbles_templateSavedHeader", "speechBubbles_templateSavedText"]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.success(header, message); + }); + } + + vm.page.saveButtonState = "success"; + vm.template = saved; + + //sync state + if (!infiniteMode) { + editorState.set(vm.template); + } + + // sync tree + // if master template alias has changed move the node to it's new location + if (!infiniteMode && oldMasterTemplateAlias !== vm.template.masterTemplateAlias) { + + // When creating a new template the id is -1. Make sure We don't remove the root node. + if (vm.page.menu.currentNode.id !== "-1") { + // move node to new location in tree + //first we need to remove the node that we're working on + treeService.removeNode(vm.page.menu.currentNode); + } + + // update stored alias to the new one so the node won't move again unless the alias is changed again + oldMasterTemplateAlias = vm.template.masterTemplateAlias; + + navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true, activate: true }).then(function (args) { + vm.page.menu.currentNode = args.node; + }); + + } else { + + // normal tree sync + if (!infiniteMode) { + navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + + } + + // clear $dirty state on form + setFormState("pristine"); + + if (infiniteMode) { + submit(); + } + + + }, function (err) { + if (suppressNotification) { + vm.page.saveButtonState = "error"; + + localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.error(header, message); + }); + } + }); + + }; + + vm.init = function () { + + // we need to load this somewhere, for now its here. + assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); + + // load templates - used in the master template picker + templateResource.getAll() + .then(function (templates) { + vm.templates = templates; }); - vm.save = function (suppressNotification) { - vm.page.saveButtonState = "busy"; + if (create) { + templateResource.getScaffold((id)).then(function (template) { + vm.ready(template); + }); + } else { + templateResource.getById(id).then(function (template) { + vm.ready(template); + }); + } - vm.template.content = vm.editor.getValue(); - - contentEditingHelper.contentEditorPerformSave({ - saveMethod: templateResource.save, - scope: $scope, - content: vm.template, - rebindCallback: function (orignal, saved) { } - }).then(function (saved) { - - if (!suppressNotification) { - localizationService.localizeMany(["speechBubbles_templateSavedHeader", "speechBubbles_templateSavedText"]).then(function (data) { - var header = data[0]; - var message = data[1]; - notificationsService.success(header, message); - }); - } - - vm.page.saveButtonState = "success"; - vm.template = saved; - - //sync state - if (!infiniteMode) { - editorState.set(vm.template); - } - - // sync tree - // if master template alias has changed move the node to it's new location - if (!infiniteMode && oldMasterTemplateAlias !== vm.template.masterTemplateAlias) { - - // When creating a new template the id is -1. Make sure We don't remove the root node. - if (vm.page.menu.currentNode.id !== "-1") { - // move node to new location in tree - //first we need to remove the node that we're working on - treeService.removeNode(vm.page.menu.currentNode); - } - - // update stored alias to the new one so the node won't move again unless the alias is changed again - oldMasterTemplateAlias = vm.template.masterTemplateAlias; - - navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true, activate: true }).then(function (args) { - vm.page.menu.currentNode = args.node; - }); - - } else { - - // normal tree sync - if (!infiniteMode) { - navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { - vm.page.menu.currentNode = syncArgs.node; - }); - } - - } - - // clear $dirty state on form - setFormState("pristine"); - - if (infiniteMode) { - submit(); - } + }; - }, function (err) { - if (suppressNotification) { - vm.page.saveButtonState = "error"; + vm.ready = function (template) { + vm.page.loading = false; + vm.template = template; - localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function (data) { - var header = data[0]; - var message = data[1]; - notificationsService.error(header, message); - }); - } + // if this is a new template, bind to the blur event on the name + if (create) { + $timeout(function () { + var nameField = $('[data-element="editor-name-field"]'); + if (nameField) { + nameField.on('blur', function (event) { + if (event.target.value) { + vm.save(true); + } }); + } + }); + } + // sync state + if (!infiniteMode) { + editorState.set(vm.template); + navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + + // save state of master template to use for comparison when syncing the tree on save + oldMasterTemplateAlias = Utilities.copy(template.masterTemplateAlias); + + // ace configuration + vm.aceOption = { + mode: "razor", + theme: "chrome", + showPrintMargin: false, + advanced: { + fontSize: '14px', + enableSnippets: false, //The Razor mode snippets are awful (Need a way to override these) + enableBasicAutocompletion: true, + enableLiveAutocompletion: false + }, + onLoad: function (_editor) { + vm.editor = _editor; + + //Update the auto-complete method to use ctrl+alt+space + _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); + + // Unassigns the keybinding (That was previously auto-complete) + // As conflicts with our own tree search shortcut + _editor.commands.bindKey("ctrl-space", null); + + // Assign new keybinding + _editor.commands.addCommands([ + // Disable (alt+shift+K) + // Conflicts with our own show shortcuts dialog - this overrides it + { + name: 'unSelectOrFindPrevious', + bindKey: 'Alt-Shift-K', + exec: function () { + // Toggle the show keyboard shortcuts overlay + $scope.$apply(function () { + vm.showKeyboardShortcut = !vm.showKeyboardShortcut; + }); + + }, + readOnly: true + }, + { + name: 'insertUmbracoValue', + bindKey: 'Alt-Shift-V', + exec: function () { + $scope.$apply(function () { + openPageFieldOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertPartialView', + bindKey: 'Alt-Shift-P', + exec: function () { + $scope.$apply(function () { + openPartialOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertDictionary', + bindKey: 'Alt-Shift-D', + exec: function () { + $scope.$apply(function () { + openDictionaryItemOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoMacro', + bindKey: 'Alt-Shift-M', + exec: function () { + $scope.$apply(function () { + openMacroOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertQuery', + bindKey: 'Alt-Shift-Q', + exec: function () { + $scope.$apply(function () { + openQueryBuilderOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertSection', + bindKey: 'Alt-Shift-S', + exec: function () { + $scope.$apply(function () { + openSectionsOverlay(); + }); + }, + readOnly: true + }, + { + name: 'chooseMasterTemplate', + bindKey: 'Alt-Shift-T', + exec: function () { + $scope.$apply(function () { + openMasterTemplateOverlay(); + }); + }, + readOnly: true + } + + ]); + + // 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 (!create) { + $timeout(function () { + vm.editor.navigateFileEnd(); + vm.editor.focus(); + persistCurrentLocation(); + }); + } + + // change on blur, focus + vm.editor.on("blur", persistCurrentLocation); + vm.editor.on("focus", persistCurrentLocation); + vm.editor.on("change", changeAceEditor); + } + } + + }; + + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; + vm.openSectionsOverlay = openSectionsOverlay; + vm.openPartialOverlay = openPartialOverlay; + vm.openMasterTemplateOverlay = openMasterTemplateOverlay; + vm.selectMasterTemplate = selectMasterTemplate; + vm.getMasterTemplateName = getMasterTemplateName; + vm.removeMasterTemplate = removeMasterTemplate; + vm.closeShortcuts = closeShortcuts; + vm.submit = submit; + vm.close = close; + + function openInsertOverlay() { + var insertOverlay = { + allowedTypes: { + macro: true, + dictionary: true, + partial: true, + umbracoField: 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 "partial": + var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name); + insert(code); + break; + case "umbracoField": + insert(model.insert.umbracoField); + break; + } + editorService.close(); + }, + close: function (oldModel) { + // close the dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.insertCodeSnippet(insertOverlay); + } + + function openMacroOverlay() { + var macroPicker = { + dialogData: {}, + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); + insert(macroObject.syntax); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.macroPicker(macroPicker); + } + + function openPageFieldOverlay() { + var insertFieldEditor = { + submit: function (model) { + insert(model.umbracoField); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.insertField(insertFieldEditor); + } + + + function openDictionaryItemOverlay() { + + var labelKeys = [ + "template_insertDictionaryItem", + "emptyStates_emptyDictionaryTree" + ]; + + localizationService.localizeMany(labelKeys).then(function (values) { + var title = values[0]; + var emptyStateMessage = values[1]; + + var dictionaryItem = { + section: "translation", + treeAlias: "dictionary", + entityType: "dictionary", + multiPicker: false, + title: title, + emptyStateMessage: emptyStateMessage, + select: function (node) { + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } }; - vm.init = function () { + editorService.treePicker(dictionaryItem); - // we need to load this somewhere, for now its here. - assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); - - // load templates - used in the master template picker - templateResource.getAll() - .then(function (templates) { - vm.templates = templates; - }); - - if (create) { - templateResource.getScaffold((id)).then(function (template) { - vm.ready(template); - }); - } else { - templateResource.getById(id).then(function (template) { - vm.ready(template); - }); - } - - }; - - - vm.ready = function (template) { - vm.page.loading = false; - vm.template = template; - - // if this is a new template, bind to the blur event on the name - if (create) { - $timeout(function () { - var nameField = $('[data-element="editor-name-field"]'); - if (nameField) { - nameField.on('blur', function (event) { - if (event.target.value) { - vm.save(true); - } - }); - } - }); - } - - // sync state - if (!infiniteMode) { - editorState.set(vm.template); - navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { - vm.page.menu.currentNode = syncArgs.node; - }); - } - - // save state of master template to use for comparison when syncing the tree on save - oldMasterTemplateAlias = Utilities.copy(template.masterTemplateAlias); - - // ace configuration - vm.aceOption = { - mode: "razor", - theme: "chrome", - showPrintMargin: false, - advanced: { - fontSize: '14px', - enableSnippets: false, //The Razor mode snippets are awful (Need a way to override these) - enableBasicAutocompletion: true, - enableLiveAutocompletion: false - }, - onLoad: function (_editor) { - vm.editor = _editor; - - //Update the auto-complete method to use ctrl+alt+space - _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); - - // Unassigns the keybinding (That was previously auto-complete) - // As conflicts with our own tree search shortcut - _editor.commands.bindKey("ctrl-space", null); - - // Assign new keybinding - _editor.commands.addCommands([ - // Disable (alt+shift+K) - // Conflicts with our own show shortcuts dialog - this overrides it - { - name: 'unSelectOrFindPrevious', - bindKey: 'Alt-Shift-K', - exec: function () { - // Toggle the show keyboard shortcuts overlay - $scope.$apply(function () { - vm.showKeyboardShortcut = !vm.showKeyboardShortcut; - }); - - }, - readOnly: true - }, - { - name: 'insertUmbracoValue', - bindKey: 'Alt-Shift-V', - exec: function () { - $scope.$apply(function () { - openPageFieldOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertPartialView', - bindKey: 'Alt-Shift-P', - exec: function () { - $scope.$apply(function () { - openPartialOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertDictionary', - bindKey: 'Alt-Shift-D', - exec: function () { - $scope.$apply(function () { - openDictionaryItemOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertUmbracoMacro', - bindKey: 'Alt-Shift-M', - exec: function () { - $scope.$apply(function () { - openMacroOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertQuery', - bindKey: 'Alt-Shift-Q', - exec: function () { - $scope.$apply(function () { - openQueryBuilderOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertSection', - bindKey: 'Alt-Shift-S', - exec: function () { - $scope.$apply(function () { - openSectionsOverlay(); - }); - }, - readOnly: true - }, - { - name: 'chooseMasterTemplate', - bindKey: 'Alt-Shift-T', - exec: function () { - $scope.$apply(function () { - openMasterTemplateOverlay(); - }); - }, - readOnly: true - } - - ]); - - // 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 (!create) { - $timeout(function () { - vm.editor.navigateFileEnd(); - vm.editor.focus(); - persistCurrentLocation(); - }); - } - - // change on blur, focus - vm.editor.on("blur", persistCurrentLocation); - vm.editor.on("focus", persistCurrentLocation); - vm.editor.on("change", changeAceEditor); - } - } - - }; - - vm.openPageFieldOverlay = openPageFieldOverlay; - vm.openDictionaryItemOverlay = openDictionaryItemOverlay; - vm.openQueryBuilderOverlay = openQueryBuilderOverlay; - vm.openMacroOverlay = openMacroOverlay; - vm.openInsertOverlay = openInsertOverlay; - vm.openSectionsOverlay = openSectionsOverlay; - vm.openPartialOverlay = openPartialOverlay; - vm.openMasterTemplateOverlay = openMasterTemplateOverlay; - vm.selectMasterTemplate = selectMasterTemplate; - vm.getMasterTemplateName = getMasterTemplateName; - vm.removeMasterTemplate = removeMasterTemplate; - vm.closeShortcuts = closeShortcuts; - vm.submit = submit; - vm.close = close; - - function openInsertOverlay() { - var insertOverlay = { - allowedTypes: { - macro: true, - dictionary: true, - partial: true, - umbracoField: 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 "partial": - var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name); - insert(code); - break; - case "umbracoField": - insert(model.insert.umbracoField); - break; - } - editorService.close(); - }, - close: function (oldModel) { - // close the dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.insertCodeSnippet(insertOverlay); - } - - function openMacroOverlay() { - var macroPicker = { - dialogData: {}, - submit: function (model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); - insert(macroObject.syntax); - editorService.close(); - }, - close: function () { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.macroPicker(macroPicker); - } - - function openPageFieldOverlay() { - var insertFieldEditor = { - submit: function (model) { - insert(model.umbracoField); - editorService.close(); - }, - close: function () { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.insertField(insertFieldEditor); - } - - - function openDictionaryItemOverlay() { - - var labelKeys = [ - "template_insertDictionaryItem", - "emptyStates_emptyDictionaryTree" - ]; - - localizationService.localizeMany(labelKeys).then(function (values) { - var title = values[0]; - var emptyStateMessage = values[1]; - - var dictionaryItem = { - section: "translation", - treeAlias: "dictionary", - entityType: "dictionary", - multiPicker: false, - title: title, - emptyStateMessage: emptyStateMessage, - select: function (node) { - var code = templateHelper.getInsertDictionarySnippet(node.name); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - - editorService.treePicker(dictionaryItem); - - }); - - } - - function openPartialOverlay() { - - localizationService.localize("template_insertPartialView").then(function (value) { - var title = value; - - var partialItem = { - section: "settings", - treeAlias: "partialViews", - entityType: "partialView", - multiPicker: false, - title: title, - filter: function (i) { - if (i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) { - return true; - } - }, - filterCssClass: "not-allowed", - select: function (node) { - var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - - editorService.treePicker(partialItem); - }); - } - - function openQueryBuilderOverlay() { - var queryBuilder = { - submit: function (model) { - var code = templateHelper.getQuerySnippet(model.result.queryExpression); - insert(code); - editorService.close(); - }, - close: function () { - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.queryBuilder(queryBuilder); - } - - - function openSectionsOverlay() { - var templateSections = { - isMaster: vm.template.isMasterTemplate, - submit: function (model) { - - if (model.insertType === 'renderBody') { - var code = templateHelper.getRenderBodySnippet(); - insert(code); - } - - if (model.insertType === 'renderSection') { - var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection); - insert(code); - } - - if (model.insertType === 'addSection') { - var code = templateHelper.getAddSectionSnippet(model.sectionName); - wrap(code); - } - - editorService.close(); - - }, - close: function (model) { - editorService.close(); - vm.editor.focus(); - } - } - editorService.templateSections(templateSections); - } - - function openMasterTemplateOverlay() { - - // make collection of available master templates - var availableMasterTemplates = []; - - // filter out the current template and the selected master template - vm.templates.forEach(function (template) { - if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) { - var templatePathArray = template.path.split(','); - // filter descendant templates of current template - if (templatePathArray.indexOf(String(vm.template.id)) === -1) { - availableMasterTemplates.push(template); - } - } - }); - - const editor = { - filterCssClass: 'not-allowed', - filter: item => !availableMasterTemplates.some(template => template.id == item.id), - submit: model => { - const template = model.selection[0]; - if (template && template.alias) { - vm.template.masterTemplateAlias = template.alias; - setLayout(template.alias + ".cshtml"); - } else { - vm.template.masterTemplateAlias = null; - setLayout(null); - } - editorService.close(); - }, - close: () => editorService.close() - } - - localizationService.localize("template_mastertemplate").then(title => { - editor.title = title; - - const currentTemplate = vm.templates.find(template => template.alias == vm.template.masterTemplateAlias); - if (currentTemplate) { - editor.currentNode = { - path: currentTemplate.path - }; - } - - editorService.templatePicker(editor); - }); - - } - - function selectMasterTemplate(template) { - - if (template && template.alias) { - vm.template.masterTemplateAlias = template.alias; - setLayout(template.alias + ".cshtml"); - } else { - vm.template.masterTemplateAlias = null; - setLayout(null); - } - - } - - function getMasterTemplateName(masterTemplateAlias, templates) { - if (masterTemplateAlias) { - var templateName = ""; - templates.forEach(function (template) { - if (template.alias === masterTemplateAlias) { - templateName = template.name; - } - }); - return templateName; - } - } - - function removeMasterTemplate() { - - vm.template.masterTemplateAlias = null; - - // call set layout with no paramters to set layout to null - setLayout(); - - } - - function setLayout(templatePath) { - - var templateCode = vm.editor.getValue(); - var newValue = templatePath; - var layoutDefRegex = new RegExp("(@{[\\s\\S]*?Layout\\s*?=\\s*?)(\"[^\"]*?\"|null)(;[\\s\\S]*?})", "gi"); - - if (newValue !== undefined && newValue !== "") { - if (layoutDefRegex.test(templateCode)) { - // Declaration exists, so just update it - templateCode = templateCode.replace(layoutDefRegex, "$1\"" + newValue + "\"$3"); - } else { - // Declaration doesn't exist, so prepend to start of doc - // TODO: Maybe insert at the cursor position, rather than just at the top of the doc? - templateCode = "@{\n\tLayout = \"" + newValue + "\";\n}\n" + templateCode; - } - } else { - if (layoutDefRegex.test(templateCode)) { - // Declaration exists, so just update it - templateCode = templateCode.replace(layoutDefRegex, "$1null$3"); - } - } - - vm.editor.setValue(templateCode); - vm.editor.clearSelection(); - vm.editor.navigateFileStart(); - - vm.editor.focus(); - // set form state to $dirty - setFormState("dirty"); - - } - - - function insert(str) { - vm.editor.focus(); - vm.editor.moveCursorToPosition(vm.currentPosition); - vm.editor.insert(str); - - // set form state to $dirty - setFormState("dirty"); - } - - function wrap(str) { - - var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange()); - str = str.replace("{0}", selectedContent); - vm.editor.insert(str); - vm.editor.focus(); - - // set form state to $dirty - setFormState("dirty"); - } - - function persistCurrentLocation() { - vm.currentPosition = vm.editor.getCursorPosition(); - } - - function changeAceEditor() { - setFormState("dirty"); - } - - 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(); - } - } - - function closeShortcuts() { - vm.showKeyboardShortcut = false; - } - - function submit() { - if ($scope.model.submit) { - $scope.model.template = vm.template; - $scope.model.submit($scope.model); - } - } - - function close() { - if ($scope.model.close) { - $scope.model.close(); - } - } - - vm.init(); + }); } - angular.module("umbraco").controller("Umbraco.Editors.Templates.EditController", TemplatesEditController); + function openPartialOverlay() { + + localizationService.localize("template_insertPartialView").then(function (value) { + var title = value; + + var partialItem = { + section: "settings", + treeAlias: "partialViews", + entityType: "partialView", + multiPicker: false, + title: title, + filter: function (i) { + if (i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) { + return true; + } + }, + filterCssClass: "not-allowed", + select: function (node) { + var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + + editorService.treePicker(partialItem); + }); + } + + function openQueryBuilderOverlay() { + var queryBuilder = { + submit: function (model) { + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + editorService.close(); + }, + close: function () { + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.queryBuilder(queryBuilder); + } + + + function openSectionsOverlay() { + var templateSections = { + isMaster: vm.template.isMasterTemplate, + submit: function (model) { + + if (model.insertType === 'renderBody') { + var code = templateHelper.getRenderBodySnippet(); + insert(code); + } + + if (model.insertType === 'renderSection') { + var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection); + insert(code); + } + + if (model.insertType === 'addSection') { + var code = templateHelper.getAddSectionSnippet(model.sectionName); + wrap(code); + } + + editorService.close(); + + }, + close: function (model) { + editorService.close(); + vm.editor.focus(); + } + } + editorService.templateSections(templateSections); + } + + function openMasterTemplateOverlay() { + + // make collection of available master templates + var availableMasterTemplates = []; + + // filter out the current template and the selected master template + vm.templates.forEach(function (template) { + if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) { + var templatePathArray = template.path.split(','); + // filter descendant templates of current template + if (templatePathArray.indexOf(String(vm.template.id)) === -1) { + availableMasterTemplates.push(template); + } + } + }); + + const editor = { + filterCssClass: 'not-allowed', + filter: item => !availableMasterTemplates.some(template => template.id == item.id), + submit: model => { + const template = model.selection[0]; + if (template && template.alias) { + vm.template.masterTemplateAlias = template.alias; + setLayout(template.alias + ".cshtml"); + } else { + vm.template.masterTemplateAlias = null; + setLayout(null); + } + editorService.close(); + }, + close: () => editorService.close() + } + + localizationService.localize("template_mastertemplate").then(title => { + editor.title = title; + + const currentTemplate = vm.templates.find(template => template.alias == vm.template.masterTemplateAlias); + if (currentTemplate) { + editor.currentNode = { + path: currentTemplate.path + }; + } + + editorService.templatePicker(editor); + }); + + } + + function selectMasterTemplate(template) { + + if (template && template.alias) { + vm.template.masterTemplateAlias = template.alias; + setLayout(template.alias + ".cshtml"); + } else { + vm.template.masterTemplateAlias = null; + setLayout(null); + } + + } + + function getMasterTemplateName(masterTemplateAlias, templates) { + if (masterTemplateAlias) { + var templateName = ""; + templates.forEach(function (template) { + if (template.alias === masterTemplateAlias) { + templateName = template.name; + } + }); + return templateName; + } + } + + function removeMasterTemplate() { + + vm.template.masterTemplateAlias = null; + + // call set layout with no paramters to set layout to null + setLayout(); + + } + + function setLayout(templatePath) { + + var templateCode = vm.editor.getValue(); + var newValue = templatePath; + var layoutDefRegex = new RegExp("(@{[\\s\\S]*?Layout\\s*?=\\s*?)(\"[^\"]*?\"|null)(;[\\s\\S]*?})", "gi"); + + if (newValue !== undefined && newValue !== "") { + if (layoutDefRegex.test(templateCode)) { + // Declaration exists, so just update it + templateCode = templateCode.replace(layoutDefRegex, "$1\"" + newValue + "\"$3"); + } else { + // Declaration doesn't exist, so prepend to start of doc + // TODO: Maybe insert at the cursor position, rather than just at the top of the doc? + templateCode = "@{\n\tLayout = \"" + newValue + "\";\n}\n" + templateCode; + } + } else { + if (layoutDefRegex.test(templateCode)) { + // Declaration exists, so just update it + templateCode = templateCode.replace(layoutDefRegex, "$1null$3"); + } + } + + vm.editor.setValue(templateCode); + vm.editor.clearSelection(); + vm.editor.navigateFileStart(); + + vm.editor.focus(); + // set form state to $dirty + setFormState("dirty"); + + } + + + function insert(str) { + vm.editor.focus(); + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + + // set form state to $dirty + setFormState("dirty"); + } + + function wrap(str) { + + var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange()); + str = str.replace("{0}", selectedContent); + vm.editor.insert(str); + vm.editor.focus(); + + // set form state to $dirty + setFormState("dirty"); + } + + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + + function changeAceEditor() { + setFormState("dirty"); + } + + 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(); + } + } + + function closeShortcuts() { + vm.showKeyboardShortcut = false; + } + + function submit() { + if ($scope.model.submit) { + $scope.model.template = vm.template; + $scope.model.submit($scope.model); + } + } + + function close() { + if ($scope.model.close) { + $scope.model.close(); + } + } + + vm.init(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.Templates.EditController", TemplatesEditController); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index f909bc197f..663e0a1f04 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -12,6 +12,7 @@ - + +
+ Template content is not editable when using runtime mode Production. +
+
+ +
@@ -106,6 +113,7 @@ From 97010c2de53d4b5b084aa012482f4e31212d9a4b Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 23:41:17 +0200 Subject: [PATCH 16/32] Show editor in read-only mode when using runtime mode production --- .../src/views/templates/edit.controller.js | 9 +++++---- .../src/views/templates/edit.html | 16 +++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index e231ff51f5..1e14cf1e87 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -83,9 +83,7 @@ vm.save = function (suppressNotification) { vm.page.saveButtonState = "busy"; - if (vm.editor) { - vm.template.content = vm.editor.getValue(); - } + vm.template.content = vm.editor.getValue(); contentEditingHelper.contentEditorPerformSave({ saveMethod: templateResource.save, @@ -228,7 +226,10 @@ onLoad: function (_editor) { vm.editor = _editor; - //Update the auto-complete method to use ctrl+alt+space + // Set read-only when using runtime mode Production + _editor.setReadOnly(vm.runtimeModeProduction); + + // Update the auto-complete method to use ctrl+alt+space _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); // Unassigns the keybinding (That was previously auto-complete) diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index 663e0a1f04..5fb4118491 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -23,15 +23,13 @@ - -
- Template content is not editable when using runtime mode Production. -
-
+ + +
+ Template content is not editable when using runtime mode Production. +
- - -
+
@@ -99,7 +97,7 @@
From e581fdc90a6ee678895d7b5d888548d7215d56c2 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 15 Jul 2022 10:34:11 +0200 Subject: [PATCH 17/32] Disable editing partial views and macro files --- .../partialViewMacros/edit.controller.js | 683 +++++++-------- .../src/views/partialViewMacros/edit.html | 118 +-- .../src/views/partialViews/edit.controller.js | 819 +++++++++--------- .../src/views/partialViews/edit.html | 149 ++-- .../src/views/templates/edit.html | 242 +++--- 5 files changed, 1007 insertions(+), 1004 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js index 2f7d879607..d72cdbe69a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js @@ -1,362 +1,367 @@ (function () { - "use strict"; + "use strict"; - function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource, editorService) { + function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource, editorService) { - var vm = this; + var vm = this; - vm.header = {}; - vm.header.editorfor = "visuallyHiddenTexts_newPartialViewMacro"; - vm.header.setPageTitle = true; - vm.page = {}; - vm.page.loading = true; - vm.partialViewMacroFile = {}; + vm.runtimeModeProduction = Umbraco.Sys.ServerVariables.application.runtimeMode == 'Production'; - //menu - vm.page.menu = {}; - vm.page.menu.currentSection = appState.getSectionState("currentSection"); - vm.page.menu.currentNode = null; + vm.header = {}; + vm.header.editorfor = "visuallyHiddenTexts_newPartialViewMacro"; + vm.header.setPageTitle = true; + vm.page = {}; + vm.page.loading = true; + vm.partialViewMacroFile = {}; - // insert buttons - vm.page.insertDefaultButton = { - labelKey: "general_insert", - addEllipsis: "true", - handler: function() { - vm.openInsertOverlay(); - } - }; - vm.page.insertSubButtons = [ - { - labelKey: "template_insertPageField", - addEllipsis: "true", - handler: function () { - vm.openPageFieldOverlay(); - } - }, - { - labelKey: "template_insertMacro", - addEllipsis: "true", - handler: function () { - vm.openMacroOverlay() - } - }, - { - labelKey: "template_insertDictionaryItem", - addEllipsis: "true", - handler: function () { - vm.openDictionaryItemOverlay(); - } - } - ]; + //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; + // insert buttons + vm.page.insertDefaultButton = { + labelKey: "general_insert", + addEllipsis: "true", + handler: function () { + vm.openInsertOverlay(); + } + }; + vm.page.insertSubButtons = [ + { + labelKey: "template_insertPageField", + addEllipsis: "true", + handler: function () { + vm.openPageFieldOverlay(); + } + }, + { + labelKey: "template_insertMacro", + addEllipsis: "true", + handler: function () { + vm.openMacroOverlay() + } + }, + { + labelKey: "template_insertDictionaryItem", + addEllipsis: "true", + handler: function () { + vm.openDictionaryItemOverlay(); + } + } + ]; - /* Functions bound to view model */ + // bind functions to view model + vm.save = save; + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; - function save() { + /* Functions bound to view model */ - vm.page.saveButtonState = "busy"; - vm.partialViewMacro.content = vm.editor.getValue(); + function save() { - contentEditingHelper.contentEditorPerformSave({ - saveMethod: codefileResource.save, - scope: $scope, - content: vm.partialViewMacro, - rebindCallback: function (orignal, saved) {} - }).then(function (saved) { - // create macro if needed - if($routeParams.create && $routeParams.nomacro !== "true") { - macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) { - navigationService.syncTree({ - tree: "macros", - path: '-1,new', - forceReload: true, - activate: false - }); - completeSave(saved); - }, Utilities.noop); - - - } else { - completeSave(saved); - } - - }, function (err) { - - vm.page.saveButtonState = "error"; - - localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) { - notificationsService.error(headerValue, msgValue); - }); - }); + vm.page.saveButtonState = "busy"; + vm.partialViewMacro.content = vm.editor.getValue(); + contentEditingHelper.contentEditorPerformSave({ + saveMethod: codefileResource.save, + scope: $scope, + content: vm.partialViewMacro, + rebindCallback: function (orignal, saved) { } + }).then(function (saved) { + // create macro if needed + if ($routeParams.create && $routeParams.nomacro !== "true") { + macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) { + navigationService.syncTree({ + tree: "macros", + path: '-1,new', + forceReload: true, + activate: false }); + completeSave(saved); + }, Utilities.noop); + + } else { + completeSave(saved); } - function completeSave(saved) { + }, function (err) { - localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_partialViewSavedText").then(function (msgValue) { - notificationsService.success(headerValue, msgValue); - }); - }); + vm.page.saveButtonState = "error"; - //check if the name changed, if so we need to redirect - if (vm.partialViewMacro.id !== saved.id) { - contentEditingHelper.redirectToRenamedContent(saved.id); - } - else { - vm.page.saveButtonState = "success"; - vm.partialViewMacro = saved; + localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { + localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); - //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 openInsertOverlay() { - var insertOverlay = { - allowedTypes: { - macro: true, - dictionary: true, - umbracoField: 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; - } - editorService.close(); - }, - close: function(oldModel) { - // close the dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.insertCodeSnippet(insertOverlay); - } - - function openMacroOverlay() { - var macroPicker = { - dialogData: {}, - submit: function (model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); - insert(macroObject.syntax); - editorService.close(); - }, - close: function() { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.macroPicker(macroPicker); - } - - - function openPageFieldOverlay() { - var insertFieldEditor = { - submit: function (model) { - insert(model.umbracoField); - editorService.close(); - }, - close: function () { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.insertField(insertFieldEditor); - } - - - function openDictionaryItemOverlay() { - - var labelKeys = [ - "template_insertDictionaryItem", - "emptyStates_emptyDictionaryTree" - ]; - - localizationService.localizeMany(labelKeys).then(function(values){ - var title = values[0]; - var emptyStateMessage = values[1]; - - var dictionaryPicker = { - section: "translation", - treeAlias: "dictionary", - entityType: "dictionary", - multiPicker: false, - title: title, - emptyStateMessage: emptyStateMessage, - select: function(node){ - var code = templateHelper.getInsertDictionarySnippet(node.name); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - - editorService.treePicker(dictionaryPicker); - - }); - } - - function openQueryBuilderOverlay() { - var queryBuilder = { - submit: function (model) { - var code = templateHelper.getQuerySnippet(model.result.queryExpression); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.queryBuilder(queryBuilder); - } - - /* Local functions */ - - function init() { - //we need to load this somewhere, for now its here. - assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); - - if ($routeParams.create) { - - var snippet = "Empty"; - - if($routeParams.snippet) { - snippet = $routeParams.snippet; - } - - codefileResource.getScaffold("partialViewMacros", $routeParams.id, snippet).then(function (partialViewMacro) { - if ($routeParams.name) { - partialViewMacro.name = $routeParams.name; - } - ready(partialViewMacro, false); - }); - - } else { - codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) { - ready(partialViewMacro, true); - }); - } - } - - function ready(partialViewMacro, syncTree) { - - vm.page.loading = false; - vm.partialViewMacro = partialViewMacro; - - //sync state - editorState.set(vm.partialViewMacro); - - if (syncTree) { - navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.path, 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); - vm.editor.on("change", changeAceEditor); - - } - } - - } - - function insert(str) { - vm.editor.focus(); - vm.editor.moveCursorToPosition(vm.currentPosition); - vm.editor.insert(str); - - // set form state to $dirty - setFormState("dirty"); - } - - function persistCurrentLocation() { - vm.currentPosition = vm.editor.getCursorPosition(); - } - - function changeAceEditor() { - setFormState("dirty"); - } - - 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); + function completeSave(saved) { + + localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) { + localizationService.localize("speechBubbles_partialViewSavedText").then(function (msgValue) { + notificationsService.success(headerValue, msgValue); + }); + }); + + //check if the name changed, if so we need to redirect + if (vm.partialViewMacro.id !== saved.id) { + contentEditingHelper.redirectToRenamedContent(saved.id); + } + else { + 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 openInsertOverlay() { + var insertOverlay = { + allowedTypes: { + macro: true, + dictionary: true, + umbracoField: 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; + } + editorService.close(); + }, + close: function (oldModel) { + // close the dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.insertCodeSnippet(insertOverlay); + } + + function openMacroOverlay() { + var macroPicker = { + dialogData: {}, + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); + insert(macroObject.syntax); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.macroPicker(macroPicker); + } + + + function openPageFieldOverlay() { + var insertFieldEditor = { + submit: function (model) { + insert(model.umbracoField); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.insertField(insertFieldEditor); + } + + + function openDictionaryItemOverlay() { + + var labelKeys = [ + "template_insertDictionaryItem", + "emptyStates_emptyDictionaryTree" + ]; + + localizationService.localizeMany(labelKeys).then(function (values) { + var title = values[0]; + var emptyStateMessage = values[1]; + + var dictionaryPicker = { + section: "translation", + treeAlias: "dictionary", + entityType: "dictionary", + multiPicker: false, + title: title, + emptyStateMessage: emptyStateMessage, + select: function (node) { + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + + editorService.treePicker(dictionaryPicker); + + }); + } + + function openQueryBuilderOverlay() { + var queryBuilder = { + submit: function (model) { + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.queryBuilder(queryBuilder); + } + + /* Local functions */ + + function init() { + //we need to load this somewhere, for now its here. + assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); + + if ($routeParams.create) { + + var snippet = "Empty"; + + if ($routeParams.snippet) { + snippet = $routeParams.snippet; + } + + codefileResource.getScaffold("partialViewMacros", $routeParams.id, snippet).then(function (partialViewMacro) { + if ($routeParams.name) { + partialViewMacro.name = $routeParams.name; + } + ready(partialViewMacro, false); + }); + + } else { + codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) { + ready(partialViewMacro, true); + }); + } + } + + function ready(partialViewMacro, syncTree) { + + vm.page.loading = false; + vm.partialViewMacro = partialViewMacro; + + //sync state + editorState.set(vm.partialViewMacro); + + if (syncTree) { + navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.path, 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; + + // Set read-only when using runtime mode Production + _editor.setReadOnly(vm.runtimeModeProduction); + + // 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); + vm.editor.on("change", changeAceEditor); + + } + } + + } + + function insert(str) { + vm.editor.focus(); + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + + // set form state to $dirty + setFormState("dirty"); + } + + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + + function changeAceEditor() { + setFormState("dirty"); + } + + 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); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html index 36fff3a044..d15bd866eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html @@ -1,81 +1,81 @@
- + -
+ - + - - + + - - - + + + +
+ Template content is not editable when using runtime mode Production. +
-
+
-
+
- - + + - - + + -
+
-
+
-
-
+
+
-
-
-
+
+
+
- + - + - - + + - + - + -
- +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js index 1e21a63755..512c1176c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js @@ -1,433 +1,438 @@ (function () { - "use strict"; + "use strict"; - function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, editorService) { + function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, editorService) { - var vm = this; - var infiniteMode = $scope.model && $scope.model.infiniteMode; - var id = infiniteMode ? $scope.model.id : $routeParams.id; - var create = infiniteMode ? $scope.model.create : $routeParams.create; - var snippet = infiniteMode ? $scope.model.snippet : $routeParams.snippet; + var vm = this; + var infiniteMode = $scope.model && $scope.model.infiniteMode; + var id = infiniteMode ? $scope.model.id : $routeParams.id; + var create = infiniteMode ? $scope.model.create : $routeParams.create; + var snippet = infiniteMode ? $scope.model.snippet : $routeParams.snippet; - function close() { - if ($scope.model.close) { - $scope.model.close($scope.model); - } + function close() { + if ($scope.model.close) { + $scope.model.close($scope.model); + } + } + + vm.close = close; + + vm.runtimeModeProduction = Umbraco.Sys.ServerVariables.application.runtimeMode == 'Production'; + + vm.header = {}; + vm.header.editorfor = "visuallyHiddenTexts_newPartialView"; + vm.header.setPageTitle = true; + + vm.page = {}; + vm.page.loading = true; + vm.partialView = {}; + + //menu + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState("currentSection"); + vm.page.menu.currentNode = null; + + // insert buttons + vm.page.insertDefaultButton = { + labelKey: "general_insert", + addEllipsis: "true", + handler: function () { + vm.openInsertOverlay(); + } + }; + vm.page.insertSubButtons = [ + { + labelKey: "template_insertPageField", + addEllipsis: "true", + handler: function () { + vm.openPageFieldOverlay(); } + }, + { + labelKey: "template_insertMacro", + addEllipsis: "true", + handler: function () { + vm.openMacroOverlay() + } + }, + { + labelKey: "template_insertDictionaryItem", + addEllipsis: "true", + handler: function () { + vm.openDictionaryItemOverlay(); + } + } + ]; - vm.close = close; + //Used to toggle the keyboard shortcut modal + //From a custom keybinding in ace editor - that conflicts with our own to show the dialog + vm.showKeyboardShortcut = false; - vm.header = {}; - vm.header.editorfor = "visuallyHiddenTexts_newPartialView"; - vm.header.setPageTitle = true; + //Keyboard shortcuts for help dialog + vm.page.keyboardShortcutsOverview = []; - vm.page = {}; - vm.page.loading = true; - vm.partialView = {}; + templateHelper.getGeneralShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + templateHelper.getEditorShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + templateHelper.getPartialViewEditorShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); - //menu - vm.page.menu = {}; - vm.page.menu.currentSection = appState.getSectionState("currentSection"); - vm.page.menu.currentNode = null; - // insert buttons - vm.page.insertDefaultButton = { - labelKey: "general_insert", - addEllipsis: "true", - handler: function() { - vm.openInsertOverlay(); - } - }; - vm.page.insertSubButtons = [ - { - labelKey: "template_insertPageField", - addEllipsis: "true", - handler: function () { - vm.openPageFieldOverlay(); - } - }, - { - labelKey: "template_insertMacro", - addEllipsis: "true", - handler: function () { - vm.openMacroOverlay() - } - }, - { - labelKey: "template_insertDictionaryItem", - addEllipsis: "true", - handler: function () { - vm.openDictionaryItemOverlay(); - } - } - ]; + // bind functions to view model + vm.save = save; + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; - //Used to toggle the keyboard shortcut modal - //From a custom keybinding in ace editor - that conflicts with our own to show the dialog - vm.showKeyboardShortcut = false; + /* Functions bound to view model */ - //Keyboard shortcuts for help dialog - vm.page.keyboardShortcutsOverview = []; + function save() { - templateHelper.getGeneralShortcuts().then(function(data){ - vm.page.keyboardShortcutsOverview.push(data); - }); - templateHelper.getEditorShortcuts().then(function(data){ - vm.page.keyboardShortcutsOverview.push(data); - }); - templateHelper.getPartialViewEditorShortcuts().then(function(data){ - vm.page.keyboardShortcutsOverview.push(data); + vm.page.saveButtonState = "busy"; + vm.partialView.content = vm.editor.getValue(); + + contentEditingHelper.contentEditorPerformSave({ + saveMethod: codefileResource.save, + scope: $scope, + content: vm.partialView, + 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); + }); }); - - // 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({ - saveMethod: codefileResource.save, - scope: $scope, - content: vm.partialView, - 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); - }); - }); - - //check if the name changed, if so we need to redirect - if (vm.partialView.id !== saved.id) { - contentEditingHelper.redirectToRenamedContent(saved.id); - } - else { - 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); - }); - }); - - }); - + //check if the name changed, if so we need to redirect + if (vm.partialView.id !== saved.id) { + contentEditingHelper.redirectToRenamedContent(saved.id); } + else { + vm.page.saveButtonState = "success"; + vm.partialView = saved; - function openInsertOverlay() { - var insertOverlay = { - allowedTypes: { - macro: true, - dictionary: true, - umbracoField: true - }, - submit: function(model) { + //sync state + editorState.set(vm.partialView); - 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; - } - editorService.close(); - }, - close: function() { - // close the dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.insertCodeSnippet(insertOverlay); + // 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"; - function openMacroOverlay() { - var macroPicker = { - dialogData: {}, - submit: function (model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); - insert(macroObject.syntax); - editorService.close(); - }, - close: function() { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.macroPicker(macroPicker); - } + localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { + localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); - function openPageFieldOverlay() { - var insertFieldEditor = { - submit: function (model) { - insert(model.umbracoField); - editorService.close(); - }, - close: function () { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.insertField(insertFieldEditor); - } - - - function openDictionaryItemOverlay() { - - var labelKeys = [ - "template_insertDictionaryItem", - "emptyStates_emptyDictionaryTree" - ]; - - localizationService.localizeMany(labelKeys).then(function(values){ - var title = values[0]; - var emptyStateMessage = values[1]; - - var dictionaryItem = { - section: "translation", - treeAlias: "dictionary", - entityType: "dictionary", - multiPicker: false, - title: title, - emptyStateMessage: emptyStateMessage, - select: function(node){ - var code = templateHelper.getInsertDictionarySnippet(node.name); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.treePicker(dictionaryItem); - }); - } - - function openQueryBuilderOverlay() { - var queryBuilder = { - title: "Query for content", - submit: function (model) { - var code = templateHelper.getQuerySnippet(model.result.queryExpression); - insert(code); - editorService.close(); - }, - close: function () { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.queryBuilder(queryBuilder); - } - - /* Local functions */ - - function init() { - //we need to load this somewhere, for now its here. - assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); - - if (create) { - - if (!snippet) { - snippet = "Empty"; - } - - codefileResource.getScaffold("partialViews", id, snippet).then(function (partialView) { - ready(partialView, false); - }); - - } else { - codefileResource.getByPath('partialViews', id).then(function (partialView) { - ready(partialView, true); - }); - } - - } - - function ready(partialView, syncTree) { - - vm.page.loading = false; - vm.partialView = partialView; - - //sync state - editorState.set(vm.partialView); - - if (!infiniteMode && syncTree) { - navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, 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; - - //Update the auto-complete method to use ctrl+alt+space - _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); - - //Unassigns the keybinding (That was previously auto-complete) - //As conflicts with our own tree search shortcut - _editor.commands.bindKey("ctrl-space", null); - - // Assign new keybinding - _editor.commands.addCommands([ - //Disable (alt+shift+K) - //Conflicts with our own show shortcuts dialog - this overrides it - { - name: 'unSelectOrFindPrevious', - bindKey: 'Alt-Shift-K', - exec: function () { - //Toggle the show keyboard shortcuts overlay - $scope.$apply(function () { - vm.showKeyboardShortcut = !vm.showKeyboardShortcut; - }); - }, - readOnly: true - }, - { - name: 'insertUmbracoValue', - bindKey: 'Alt-Shift-V', - exec: function () { - $scope.$apply(function () { - openPageFieldOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertDictionary', - bindKey: 'Alt-Shift-D', - exec: function () { - $scope.$apply(function () { - openDictionaryItemOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertUmbracoMacro', - bindKey: 'Alt-Shift-M', - exec: function () { - $scope.$apply(function () { - openMacroOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertQuery', - bindKey: 'Alt-Shift-Q', - exec: function () { - $scope.$apply(function () { - openQueryBuilderOverlay(); - }); - }, - readOnly: true - } - - ]); - - // 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(!create) { - $timeout(function(){ - vm.editor.navigateFileEnd(); - vm.editor.focus(); - persistCurrentLocation(); - }); - } - - //change on blur, focus - vm.editor.on("blur", persistCurrentLocation); - vm.editor.on("focus", persistCurrentLocation); - vm.editor.on("change", changeAceEditor); - - } - } - - } - - function insert(str) { - vm.editor.focus(); - vm.editor.moveCursorToPosition(vm.currentPosition); - vm.editor.insert(str); - - // set form state to $dirty - setFormState("dirty"); - } - - function persistCurrentLocation() { - vm.currentPosition = vm.editor.getCursorPosition(); - } - - function changeAceEditor() { - setFormState("dirty"); - } - - 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); + function openInsertOverlay() { + var insertOverlay = { + allowedTypes: { + macro: true, + dictionary: true, + umbracoField: 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; + } + editorService.close(); + }, + close: function () { + // close the dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.insertCodeSnippet(insertOverlay); + } + + + function openMacroOverlay() { + var macroPicker = { + dialogData: {}, + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); + insert(macroObject.syntax); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.macroPicker(macroPicker); + } + + function openPageFieldOverlay() { + var insertFieldEditor = { + submit: function (model) { + insert(model.umbracoField); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.insertField(insertFieldEditor); + } + + + function openDictionaryItemOverlay() { + + var labelKeys = [ + "template_insertDictionaryItem", + "emptyStates_emptyDictionaryTree" + ]; + + localizationService.localizeMany(labelKeys).then(function (values) { + var title = values[0]; + var emptyStateMessage = values[1]; + + var dictionaryItem = { + section: "translation", + treeAlias: "dictionary", + entityType: "dictionary", + multiPicker: false, + title: title, + emptyStateMessage: emptyStateMessage, + select: function (node) { + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.treePicker(dictionaryItem); + }); + } + + function openQueryBuilderOverlay() { + var queryBuilder = { + title: "Query for content", + submit: function (model) { + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + editorService.close(); + }, + close: function () { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.queryBuilder(queryBuilder); + } + + /* Local functions */ + + function init() { + //we need to load this somewhere, for now its here. + assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); + + if (create) { + + if (!snippet) { + snippet = "Empty"; + } + + codefileResource.getScaffold("partialViews", id, snippet).then(function (partialView) { + ready(partialView, false); + }); + + } else { + codefileResource.getByPath('partialViews', id).then(function (partialView) { + ready(partialView, true); + }); + } + + } + + function ready(partialView, syncTree) { + + vm.page.loading = false; + vm.partialView = partialView; + + //sync state + editorState.set(vm.partialView); + + if (!infiniteMode && syncTree) { + navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, 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; + + // Set read-only when using runtime mode Production + _editor.setReadOnly(vm.runtimeModeProduction); + + //Update the auto-complete method to use ctrl+alt+space + _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); + + //Unassigns the keybinding (That was previously auto-complete) + //As conflicts with our own tree search shortcut + _editor.commands.bindKey("ctrl-space", null); + + // Assign new keybinding + _editor.commands.addCommands([ + //Disable (alt+shift+K) + //Conflicts with our own show shortcuts dialog - this overrides it + { + name: 'unSelectOrFindPrevious', + bindKey: 'Alt-Shift-K', + exec: function () { + //Toggle the show keyboard shortcuts overlay + $scope.$apply(function () { + vm.showKeyboardShortcut = !vm.showKeyboardShortcut; + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoValue', + bindKey: 'Alt-Shift-V', + exec: function () { + $scope.$apply(function () { + openPageFieldOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertDictionary', + bindKey: 'Alt-Shift-D', + exec: function () { + $scope.$apply(function () { + openDictionaryItemOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoMacro', + bindKey: 'Alt-Shift-M', + exec: function () { + $scope.$apply(function () { + openMacroOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertQuery', + bindKey: 'Alt-Shift-Q', + exec: function () { + $scope.$apply(function () { + openQueryBuilderOverlay(); + }); + }, + readOnly: true + } + + ]); + + // 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 (!create) { + $timeout(function () { + vm.editor.navigateFileEnd(); + vm.editor.focus(); + persistCurrentLocation(); + }); + } + + //change on blur, focus + vm.editor.on("blur", persistCurrentLocation); + vm.editor.on("focus", persistCurrentLocation); + vm.editor.on("change", changeAceEditor); + + } + } + + } + + function insert(str) { + vm.editor.focus(); + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + + // set form state to $dirty + setFormState("dirty"); + } + + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + + function changeAceEditor() { + setFormState("dirty"); + } + + 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); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html index 4a4d01385e..ec090e6071 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html @@ -1,97 +1,98 @@
- + -
+ - + - - + + - + - - + + +
+ Template content is not editable when using runtime mode Production. +
-
+
-
+
- - + + - - + + -
+
-
+
-
-
+
+
-
-
- -
+ + - +
- - - - + - + + + + - - - - - - + - + + -
- + + + + + + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index 5fb4118491..dd2cda9a54 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -1,150 +1,142 @@
- + -
+ - + - - + + - - - - -
- Template content is not editable when using runtime mode Production. -
+ + -
+ +
+ Template content is not editable when using runtime mode Production. +
-
+
-
+
- +
- + -
+ -
+
-
+
- - +
- - + + - - - -
- -
- -
-
- - - - - - - - - - - - - - - - - + - + - +
- +
-
- +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
From ddfc2fa902d9515376e15a89edc1df1ede888bbb Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 15 Jul 2022 10:45:29 +0200 Subject: [PATCH 18/32] Remove template from warning message --- src/Umbraco.Core/EmbeddedResources/Lang/en.xml | 2 +- src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml | 2 +- src/Umbraco.Core/EmbeddedResources/Lang/nl.xml | 2 +- src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html | 2 +- src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html | 2 +- src/Umbraco.Web.UI.Client/src/views/templates/edit.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 49ccf24dd7..d75e70bb07 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1630,7 +1630,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Editor - Production.]]> + Production.]]> Failed to delete template with ID %0% Edit template Sections diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 04883067f6..9641fda47f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -1683,7 +1683,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Rich Text Editor - Production.]]> + Production.]]> Failed to delete template with ID %0% Edit template Sections diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml index 06762e1dd7..1d76ac06f9 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml @@ -1444,7 +1444,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Rich Text Editor - Production.]]> + Production.]]> Kan sjabloon met ID %0% niet verwijderen Sjabloon aanpassen Secties diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html index d15bd866eb..7cffa70aa1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html @@ -23,7 +23,7 @@
- Template content is not editable when using runtime mode Production. + Content is not editable when using runtime mode Production.
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html index ec090e6071..e1eb195545 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html @@ -24,7 +24,7 @@
- Template content is not editable when using runtime mode Production. + Content is not editable when using runtime mode Production.
diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index dd2cda9a54..a5527095d7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -25,7 +25,7 @@
- Template content is not editable when using runtime mode Production. + Content is not editable when using runtime mode Production.
From ae6ee5c3adc4b1087bc5eed03a3cad887804b2cd Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 15 Jul 2022 11:07:24 +0200 Subject: [PATCH 19/32] Fix integration test ACE Editor mock --- .../test/unit/app/templates/template-editor-controller.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js index b4615eaad8..9b872f137f 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js @@ -1,4 +1,4 @@ -(function() { +(function() { "use strict"; describe("templates editor controller", @@ -26,6 +26,7 @@ getCursorPosition: function() {}, getValue: function() {}, setValue: function() {}, + setReadOnly: function () { }, focus: function() {}, clearSelection: function() {}, navigateFileStart: function() {}, From f4085efc17cc117335c10787f28beb9cc7f29228 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 15 Jul 2022 14:17:45 +0200 Subject: [PATCH 20/32] Fix integration tests --- .../Persistence/Repositories/ContentTypeRepositoryTest.cs | 5 ++++- .../Persistence/Repositories/DocumentRepositoryTest.cs | 5 ++++- .../Persistence/Repositories/TemplateRepositoryTest.cs | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs index e2a691a102..aae69e2f61 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -79,6 +79,9 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest var provider = ScopeProvider; using (var scope = provider.CreateScope()) { + var runtimeSettingsMock = new Mock>(); + runtimeSettingsMock.Setup(x => x.CurrentValue).Returns(new RuntimeSettings()); + var templateRepo = new TemplateRepository( (IScopeAccessor)provider, AppCaches.Disabled, @@ -87,7 +90,7 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest IOHelper, ShortStringHelper, Mock.Of(), - Mock.Of>()); + runtimeSettingsMock.Object); var repository = ContentTypeRepository; Template[] templates = { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs index 319470917e..9870926544 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs @@ -111,7 +111,10 @@ public class DocumentRepositoryTest : UmbracoIntegrationTest { appCaches ??= AppCaches; - templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of(), Mock.Of>()); + var runtimeSettingsMock = new Mock>(); + runtimeSettingsMock.Setup(x => x.CurrentValue).Returns(new RuntimeSettings()); + + templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of(), runtimeSettingsMock.Object); var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches, ShortStringHelper); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs index 961c11f471..e0665aaf6d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -57,10 +57,13 @@ public class TemplateRepositoryTest : UmbracoIntegrationTest private IHostingEnvironment HostingEnvironment => GetRequiredService(); private FileSystems FileSystems => GetRequiredService(); + private IViewHelper ViewHelper => GetRequiredService(); + private IOptionsMonitor RuntimeSettings => GetRequiredService>(); + private ITemplateRepository CreateRepository(IScopeProvider provider) => - new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, Mock.Of>()); + new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, RuntimeSettings); [Test] public void Can_Instantiate_Repository() From f11d717522489e141fa3929ed6262373a4b5e005 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 16:48:01 +0200 Subject: [PATCH 21/32] Add RuntimeModeValidatorCollection and builder --- .../Runtime/RuntimeModeValidatorCollection.cs | 10 ++++++++++ .../Runtime/RuntimeModeValidatorCollectionBuilder.cs | 11 +++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs create mode 100644 src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs new file mode 100644 index 0000000000..97f25f3b8f --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollection.cs @@ -0,0 +1,10 @@ +using Umbraco.Cms.Core.Composing; + +namespace Umbraco.Cms.Infrastructure.Runtime; + +public class RuntimeModeValidatorCollection : BuilderCollectionBase +{ + public RuntimeModeValidatorCollection(Func> items) + : base(items) + { } +} diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs new file mode 100644 index 0000000000..4c755a6040 --- /dev/null +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidatorCollectionBuilder.cs @@ -0,0 +1,11 @@ +using Microsoft.Extensions.DependencyInjection; +using Umbraco.Cms.Core.Composing; + +namespace Umbraco.Cms.Infrastructure.Runtime; + +public class RuntimeModeValidatorCollectionBuilder : SetCollectionBuilderBase +{ + protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient; + + protected override RuntimeModeValidatorCollectionBuilder This => this; +} From e1c80ecd204f6105a0c9cedbb56f2cf2409169c0 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 16:48:52 +0200 Subject: [PATCH 22/32] Add RuntimeModeValidators() extension method --- .../UmbracoBuilder.Collections.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs index bc83695d94..609c5305dc 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.Collections.cs @@ -2,6 +2,7 @@ using Umbraco.Cms.Core.DependencyInjection; using Umbraco.Cms.Core.Packaging; using Umbraco.Cms.Infrastructure.Persistence; using Umbraco.Cms.Infrastructure.Persistence.Mappers; +using Umbraco.Cms.Infrastructure.Runtime; namespace Umbraco.Extensions; @@ -17,6 +18,10 @@ public static partial class UmbracoBuilderExtensions public static MapperCollectionBuilder? Mappers(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); + /// + /// Gets the NPoco mappers collection builder. + /// + /// The builder. public static NPocoMapperCollectionBuilder? NPocoMappers(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); @@ -26,4 +31,11 @@ public static partial class UmbracoBuilderExtensions /// The builder. public static PackageMigrationPlanCollectionBuilder? PackageMigrationPlans(this IUmbracoBuilder builder) => builder.WithCollectionBuilder(); + + /// + /// Gets the runtime mode validators collection builder. + /// + /// The builder. + public static RuntimeModeValidatorCollectionBuilder RuntimeModeValidators(this IUmbracoBuilder builder) + => builder.WithCollectionBuilder(); } From df5d657380450556f83ecbdd83a81cbc86fe0878 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 16:49:49 +0200 Subject: [PATCH 23/32] Swap registration and resolving of IRuntimeModeValidator items to collection builder --- .../UmbracoBuilder.CoreServices.cs | 11 ++++++----- .../Runtime/RuntimeModeValidationService.cs | 13 ++++++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs index 154bae9cd0..62bafcd28e 100644 --- a/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs +++ b/src/Umbraco.Infrastructure/DependencyInjection/UmbracoBuilder.CoreServices.cs @@ -86,11 +86,12 @@ public static partial class UmbracoBuilderExtensions // Add runtime mode validation builder.Services.AddSingleton(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); - builder.Services.AddTransient(); + builder.RuntimeModeValidators() + .Add() + .Add() + .Add() + .Add() + .Add(); // composers builder diff --git a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs index 85eec91786..c4bbeb6902 100644 --- a/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs +++ b/src/Umbraco.Infrastructure/Runtime/RuntimeModeValidationService.cs @@ -29,11 +29,18 @@ internal class RuntimeModeValidationService : IRuntimeModeValidationService var validationMessages = new List(); // Runtime mode validators are registered transient, but this service is registered as singleton - foreach (var runtimeModeValidator in _serviceProvider.GetServices()) + using (var scope = _serviceProvider.CreateScope()) { - if (runtimeModeValidator.Validate(runtimeMode, out var validationMessage) == false) + var runtimeModeValidators = scope.ServiceProvider.GetService(); + if (runtimeModeValidators is not null) { - validationMessages.Add(validationMessage); + foreach (var runtimeModeValidator in runtimeModeValidators) + { + if (runtimeModeValidator.Validate(runtimeMode, out var validationMessage) == false) + { + validationMessages.Add(validationMessage); + } + } } } From 9e1e9641a917497606443f7e306466398dcadbe8 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 22:41:20 +0200 Subject: [PATCH 24/32] Skip saving template file when using runtime mode Production --- .../Implement/TemplateRepository.cs | 33 +++++++++++++++---- .../Repositories/ContentTypeRepositoryTest.cs | 5 ++- .../Repositories/DocumentRepositoryTest.cs | 2 +- .../Repositories/TemplateRepositoryTest.cs | 3 +- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs index 1a0a90269c..9e01320fdc 100644 --- a/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs +++ b/src/Umbraco.Infrastructure/Persistence/Repositories/Implement/TemplateRepository.cs @@ -1,8 +1,10 @@ using System.Text; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NPoco; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Models; using Umbraco.Cms.Core.Models.Entities; @@ -24,17 +26,26 @@ internal class TemplateRepository : EntityRepositoryBase, ITempl { private readonly IIOHelper _ioHelper; private readonly IShortStringHelper _shortStringHelper; - private readonly IViewHelper _viewHelper; private readonly IFileSystem? _viewsFileSystem; + private readonly IViewHelper _viewHelper; + private readonly IOptionsMonitor _runtimeSettings; - public TemplateRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger logger, - FileSystems fileSystems, IIOHelper ioHelper, IShortStringHelper shortStringHelper, IViewHelper viewHelper) + public TemplateRepository( + IScopeAccessor scopeAccessor, + AppCaches cache, + ILogger logger, + FileSystems fileSystems, + IIOHelper ioHelper, + IShortStringHelper shortStringHelper, + IViewHelper viewHelper, + IOptionsMonitor runtimeSettings) : base(scopeAccessor, cache, logger) { _ioHelper = ioHelper; _shortStringHelper = shortStringHelper; _viewsFileSystem = fileSystems.MvcViewsFileSystem; _viewHelper = viewHelper; + _runtimeSettings = runtimeSettings; } public Stream GetFileContentStream(string filepath) @@ -421,8 +432,12 @@ internal class TemplateRepository : EntityRepositoryBase, ITempl template.Id = nodeDto.NodeId; //Set Id on entity to ensure an Id is set template.Path = nodeDto.Path; - //now do the file work - SaveFile(template); + // Only save file when not in production runtime mode + if (_runtimeSettings.CurrentValue.Mode != RuntimeMode.Production) + { + //now do the file work + SaveFile(template); + } template.ResetDirtyProperties(); @@ -476,8 +491,12 @@ internal class TemplateRepository : EntityRepositoryBase, ITempl IEnumerable axisDefs = GetAxisDefinitions(dto); template.IsMasterTemplate = axisDefs.Any(x => x.ParentId == dto.NodeId); - //now do the file work - SaveFile((Template)entity, originalAlias); + // Only save file when not in production runtime mode + if (_runtimeSettings.CurrentValue.Mode != RuntimeMode.Production) + { + //now do the file work + SaveFile((Template)entity, originalAlias); + } entity.ResetDirtyProperties(); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs index 3547182538..e2a691a102 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -5,10 +5,12 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; using Umbraco.Cms.Core.Cache; +using Umbraco.Cms.Core.Configuration.Models; using Umbraco.Cms.Core.IO; using Umbraco.Cms.Core.Mapping; using Umbraco.Cms.Core.Models; @@ -84,7 +86,8 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest FileSystems, IOHelper, ShortStringHelper, - Mock.Of()); + Mock.Of(), + Mock.Of>()); var repository = ContentTypeRepository; Template[] templates = { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs index bcdcf7666a..319470917e 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs @@ -111,7 +111,7 @@ public class DocumentRepositoryTest : UmbracoIntegrationTest { appCaches ??= AppCaches; - templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of()); + templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of(), Mock.Of>()); var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches, ShortStringHelper); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs index bcf0f185d1..961c11f471 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Text; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Moq; using NUnit.Framework; using Umbraco.Cms.Core; @@ -59,7 +60,7 @@ public class TemplateRepositoryTest : UmbracoIntegrationTest private IViewHelper ViewHelper => GetRequiredService(); private ITemplateRepository CreateRepository(IScopeProvider provider) => - new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper); + new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, Mock.Of>()); [Test] public void Can_Instantiate_Repository() From 930b21f2734ed7380dd7268775c46963264ccdf7 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 22:48:43 +0200 Subject: [PATCH 25/32] Fix editing template and show warning message when using runtime mode production --- .../EmbeddedResources/Lang/en.xml | 1 + .../EmbeddedResources/Lang/en_us.xml | 1 + .../EmbeddedResources/Lang/nl.xml | 1 + .../Controllers/BackOfficeServerVariables.cs | 2 +- .../src/views/templates/edit.controller.js | 1400 +++++++++-------- .../src/views/templates/edit.html | 10 +- 6 files changed, 715 insertions(+), 700 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 72a1aea35b..49ccf24dd7 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1630,6 +1630,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Editor + Production.]]> Failed to delete template with ID %0% Edit template Sections diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 3e2f7c23dc..04883067f6 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -1683,6 +1683,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Rich Text Editor + Production.]]> Failed to delete template with ID %0% Edit template Sections diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml index a9513c5302..06762e1dd7 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml @@ -1444,6 +1444,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Rich Text Editor + Production.]]> Kan sjabloon met ID %0% niet verwijderen Sjabloon aanpassen Secties diff --git a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs index b7fc6a92f5..0681ed957a 100644 --- a/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web.BackOffice/Controllers/BackOfficeServerVariables.cs @@ -597,7 +597,7 @@ namespace Umbraco.Cms.Web.BackOffice.Controllers { "assemblyVersion", _umbracoVersion.AssemblyVersion?.ToString() } }; - + app.Add("runtimeMode", _runtimeSettings.Mode.ToString()); //the value is the hash of the version, cdf version and the configured state app.Add("cacheBuster", $"{version}.{_runtimeState.Level}.{_runtimeMinifier.CacheBuster}".GenerateHash()); diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index 99f11f9913..e231ff51f5 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -1,723 +1,727 @@ (function () { - "use strict"; + "use strict"; - function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper, editorService) { + function TemplatesEditController($scope, $routeParams, $timeout, templateResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, treeService, contentEditingHelper, localizationService, angularHelper, templateHelper, editorService) { - var vm = this; - var oldMasterTemplateAlias = null; - var infiniteMode = $scope.model && $scope.model.infiniteMode; - var id = infiniteMode ? $scope.model.id : $routeParams.id; - var create = infiniteMode ? $scope.model.create : $routeParams.create; + var vm = this; + var oldMasterTemplateAlias = null; + var infiniteMode = $scope.model && $scope.model.infiniteMode; + var id = infiniteMode ? $scope.model.id : $routeParams.id; + var create = infiniteMode ? $scope.model.create : $routeParams.create; - vm.header = {}; - vm.header.editorfor = "template_template"; - vm.header.setPageTitle = true; + vm.runtimeModeProduction = Umbraco.Sys.ServerVariables.application.runtimeMode == 'Production'; - vm.page = {}; - vm.page.loading = true; - vm.templates = []; + vm.header = {}; + vm.header.editorfor = "template_template"; + vm.header.setPageTitle = true; - //menu - vm.page.menu = {}; - vm.page.menu.currentSection = appState.getSectionState("currentSection"); - vm.page.menu.currentNode = null; + vm.page = {}; + vm.page.loading = true; + vm.templates = []; - // insert buttons - vm.page.insertDefaultButton = { - labelKey: "general_insert", - addEllipsis: "true", - handler: function () { - vm.openInsertOverlay(); - } - }; - vm.page.insertSubButtons = [ - { - labelKey: "template_insertPageField", - addEllipsis: "true", - handler: function () { - vm.openPageFieldOverlay(); - } - }, - { - labelKey: "template_insertPartialView", - addEllipsis: "true", - handler: function () { - vm.openPartialOverlay(); - } - }, - { - labelKey: "template_insertDictionaryItem", - addEllipsis: "true", - handler: function () { - vm.openDictionaryItemOverlay(); - } - }, - { - labelKey: "template_insertMacro", - addEllipsis: "true", - handler: function () { - vm.openMacroOverlay() - } - } - ]; + //menu + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState("currentSection"); + vm.page.menu.currentNode = null; - //Used to toggle the keyboard shortcut modal - //From a custom keybinding in ace editor - that conflicts with our own to show the dialog - vm.showKeyboardShortcut = false; + // insert buttons + vm.page.insertDefaultButton = { + labelKey: "general_insert", + addEllipsis: "true", + handler: function () { + vm.openInsertOverlay(); + } + }; + vm.page.insertSubButtons = [ + { + labelKey: "template_insertPageField", + addEllipsis: "true", + handler: function () { + vm.openPageFieldOverlay(); + } + }, + { + labelKey: "template_insertPartialView", + addEllipsis: "true", + handler: function () { + vm.openPartialOverlay(); + } + }, + { + labelKey: "template_insertDictionaryItem", + addEllipsis: "true", + handler: function () { + vm.openDictionaryItemOverlay(); + } + }, + { + labelKey: "template_insertMacro", + addEllipsis: "true", + handler: function () { + vm.openMacroOverlay() + } + } + ]; - //Keyboard shortcuts for help dialog - vm.page.keyboardShortcutsOverview = []; + //Used to toggle the keyboard shortcut modal + //From a custom keybinding in ace editor - that conflicts with our own to show the dialog + vm.showKeyboardShortcut = false; - templateHelper.getGeneralShortcuts().then(function (data) { - vm.page.keyboardShortcutsOverview.push(data); - }); - templateHelper.getEditorShortcuts().then(function (data) { - vm.page.keyboardShortcutsOverview.push(data); - }); - templateHelper.getTemplateEditorShortcuts().then(function (data) { - vm.page.keyboardShortcutsOverview.push(data); + //Keyboard shortcuts for help dialog + vm.page.keyboardShortcutsOverview = []; + + templateHelper.getGeneralShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + templateHelper.getEditorShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + templateHelper.getTemplateEditorShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + + vm.save = function (suppressNotification) { + vm.page.saveButtonState = "busy"; + + if (vm.editor) { + vm.template.content = vm.editor.getValue(); + } + + contentEditingHelper.contentEditorPerformSave({ + saveMethod: templateResource.save, + scope: $scope, + content: vm.template, + rebindCallback: function (orignal, saved) { } + }).then(function (saved) { + + if (!suppressNotification) { + localizationService.localizeMany(["speechBubbles_templateSavedHeader", "speechBubbles_templateSavedText"]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.success(header, message); + }); + } + + vm.page.saveButtonState = "success"; + vm.template = saved; + + //sync state + if (!infiniteMode) { + editorState.set(vm.template); + } + + // sync tree + // if master template alias has changed move the node to it's new location + if (!infiniteMode && oldMasterTemplateAlias !== vm.template.masterTemplateAlias) { + + // When creating a new template the id is -1. Make sure We don't remove the root node. + if (vm.page.menu.currentNode.id !== "-1") { + // move node to new location in tree + //first we need to remove the node that we're working on + treeService.removeNode(vm.page.menu.currentNode); + } + + // update stored alias to the new one so the node won't move again unless the alias is changed again + oldMasterTemplateAlias = vm.template.masterTemplateAlias; + + navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true, activate: true }).then(function (args) { + vm.page.menu.currentNode = args.node; + }); + + } else { + + // normal tree sync + if (!infiniteMode) { + navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + + } + + // clear $dirty state on form + setFormState("pristine"); + + if (infiniteMode) { + submit(); + } + + + }, function (err) { + if (suppressNotification) { + vm.page.saveButtonState = "error"; + + localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function (data) { + var header = data[0]; + var message = data[1]; + notificationsService.error(header, message); + }); + } + }); + + }; + + vm.init = function () { + + // we need to load this somewhere, for now its here. + assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); + + // load templates - used in the master template picker + templateResource.getAll() + .then(function (templates) { + vm.templates = templates; }); - vm.save = function (suppressNotification) { - vm.page.saveButtonState = "busy"; + if (create) { + templateResource.getScaffold((id)).then(function (template) { + vm.ready(template); + }); + } else { + templateResource.getById(id).then(function (template) { + vm.ready(template); + }); + } - vm.template.content = vm.editor.getValue(); - - contentEditingHelper.contentEditorPerformSave({ - saveMethod: templateResource.save, - scope: $scope, - content: vm.template, - rebindCallback: function (orignal, saved) { } - }).then(function (saved) { - - if (!suppressNotification) { - localizationService.localizeMany(["speechBubbles_templateSavedHeader", "speechBubbles_templateSavedText"]).then(function (data) { - var header = data[0]; - var message = data[1]; - notificationsService.success(header, message); - }); - } - - vm.page.saveButtonState = "success"; - vm.template = saved; - - //sync state - if (!infiniteMode) { - editorState.set(vm.template); - } - - // sync tree - // if master template alias has changed move the node to it's new location - if (!infiniteMode && oldMasterTemplateAlias !== vm.template.masterTemplateAlias) { - - // When creating a new template the id is -1. Make sure We don't remove the root node. - if (vm.page.menu.currentNode.id !== "-1") { - // move node to new location in tree - //first we need to remove the node that we're working on - treeService.removeNode(vm.page.menu.currentNode); - } - - // update stored alias to the new one so the node won't move again unless the alias is changed again - oldMasterTemplateAlias = vm.template.masterTemplateAlias; - - navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true, activate: true }).then(function (args) { - vm.page.menu.currentNode = args.node; - }); - - } else { - - // normal tree sync - if (!infiniteMode) { - navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { - vm.page.menu.currentNode = syncArgs.node; - }); - } - - } - - // clear $dirty state on form - setFormState("pristine"); - - if (infiniteMode) { - submit(); - } + }; - }, function (err) { - if (suppressNotification) { - vm.page.saveButtonState = "error"; + vm.ready = function (template) { + vm.page.loading = false; + vm.template = template; - localizationService.localizeMany(["speechBubbles_validationFailedHeader", "speechBubbles_validationFailedMessage"]).then(function (data) { - var header = data[0]; - var message = data[1]; - notificationsService.error(header, message); - }); - } + // if this is a new template, bind to the blur event on the name + if (create) { + $timeout(function () { + var nameField = $('[data-element="editor-name-field"]'); + if (nameField) { + nameField.on('blur', function (event) { + if (event.target.value) { + vm.save(true); + } }); + } + }); + } + // sync state + if (!infiniteMode) { + editorState.set(vm.template); + navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { + vm.page.menu.currentNode = syncArgs.node; + }); + } + + // save state of master template to use for comparison when syncing the tree on save + oldMasterTemplateAlias = Utilities.copy(template.masterTemplateAlias); + + // ace configuration + vm.aceOption = { + mode: "razor", + theme: "chrome", + showPrintMargin: false, + advanced: { + fontSize: '14px', + enableSnippets: false, //The Razor mode snippets are awful (Need a way to override these) + enableBasicAutocompletion: true, + enableLiveAutocompletion: false + }, + onLoad: function (_editor) { + vm.editor = _editor; + + //Update the auto-complete method to use ctrl+alt+space + _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); + + // Unassigns the keybinding (That was previously auto-complete) + // As conflicts with our own tree search shortcut + _editor.commands.bindKey("ctrl-space", null); + + // Assign new keybinding + _editor.commands.addCommands([ + // Disable (alt+shift+K) + // Conflicts with our own show shortcuts dialog - this overrides it + { + name: 'unSelectOrFindPrevious', + bindKey: 'Alt-Shift-K', + exec: function () { + // Toggle the show keyboard shortcuts overlay + $scope.$apply(function () { + vm.showKeyboardShortcut = !vm.showKeyboardShortcut; + }); + + }, + readOnly: true + }, + { + name: 'insertUmbracoValue', + bindKey: 'Alt-Shift-V', + exec: function () { + $scope.$apply(function () { + openPageFieldOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertPartialView', + bindKey: 'Alt-Shift-P', + exec: function () { + $scope.$apply(function () { + openPartialOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertDictionary', + bindKey: 'Alt-Shift-D', + exec: function () { + $scope.$apply(function () { + openDictionaryItemOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoMacro', + bindKey: 'Alt-Shift-M', + exec: function () { + $scope.$apply(function () { + openMacroOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertQuery', + bindKey: 'Alt-Shift-Q', + exec: function () { + $scope.$apply(function () { + openQueryBuilderOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertSection', + bindKey: 'Alt-Shift-S', + exec: function () { + $scope.$apply(function () { + openSectionsOverlay(); + }); + }, + readOnly: true + }, + { + name: 'chooseMasterTemplate', + bindKey: 'Alt-Shift-T', + exec: function () { + $scope.$apply(function () { + openMasterTemplateOverlay(); + }); + }, + readOnly: true + } + + ]); + + // 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 (!create) { + $timeout(function () { + vm.editor.navigateFileEnd(); + vm.editor.focus(); + persistCurrentLocation(); + }); + } + + // change on blur, focus + vm.editor.on("blur", persistCurrentLocation); + vm.editor.on("focus", persistCurrentLocation); + vm.editor.on("change", changeAceEditor); + } + } + + }; + + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; + vm.openSectionsOverlay = openSectionsOverlay; + vm.openPartialOverlay = openPartialOverlay; + vm.openMasterTemplateOverlay = openMasterTemplateOverlay; + vm.selectMasterTemplate = selectMasterTemplate; + vm.getMasterTemplateName = getMasterTemplateName; + vm.removeMasterTemplate = removeMasterTemplate; + vm.closeShortcuts = closeShortcuts; + vm.submit = submit; + vm.close = close; + + function openInsertOverlay() { + var insertOverlay = { + allowedTypes: { + macro: true, + dictionary: true, + partial: true, + umbracoField: 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 "partial": + var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name); + insert(code); + break; + case "umbracoField": + insert(model.insert.umbracoField); + break; + } + editorService.close(); + }, + close: function (oldModel) { + // close the dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.insertCodeSnippet(insertOverlay); + } + + function openMacroOverlay() { + var macroPicker = { + dialogData: {}, + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); + insert(macroObject.syntax); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.macroPicker(macroPicker); + } + + function openPageFieldOverlay() { + var insertFieldEditor = { + submit: function (model) { + insert(model.umbracoField); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.insertField(insertFieldEditor); + } + + + function openDictionaryItemOverlay() { + + var labelKeys = [ + "template_insertDictionaryItem", + "emptyStates_emptyDictionaryTree" + ]; + + localizationService.localizeMany(labelKeys).then(function (values) { + var title = values[0]; + var emptyStateMessage = values[1]; + + var dictionaryItem = { + section: "translation", + treeAlias: "dictionary", + entityType: "dictionary", + multiPicker: false, + title: title, + emptyStateMessage: emptyStateMessage, + select: function (node) { + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } }; - vm.init = function () { + editorService.treePicker(dictionaryItem); - // we need to load this somewhere, for now its here. - assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); - - // load templates - used in the master template picker - templateResource.getAll() - .then(function (templates) { - vm.templates = templates; - }); - - if (create) { - templateResource.getScaffold((id)).then(function (template) { - vm.ready(template); - }); - } else { - templateResource.getById(id).then(function (template) { - vm.ready(template); - }); - } - - }; - - - vm.ready = function (template) { - vm.page.loading = false; - vm.template = template; - - // if this is a new template, bind to the blur event on the name - if (create) { - $timeout(function () { - var nameField = $('[data-element="editor-name-field"]'); - if (nameField) { - nameField.on('blur', function (event) { - if (event.target.value) { - vm.save(true); - } - }); - } - }); - } - - // sync state - if (!infiniteMode) { - editorState.set(vm.template); - navigationService.syncTree({ tree: "templates", path: vm.template.path, forceReload: true }).then(function (syncArgs) { - vm.page.menu.currentNode = syncArgs.node; - }); - } - - // save state of master template to use for comparison when syncing the tree on save - oldMasterTemplateAlias = Utilities.copy(template.masterTemplateAlias); - - // ace configuration - vm.aceOption = { - mode: "razor", - theme: "chrome", - showPrintMargin: false, - advanced: { - fontSize: '14px', - enableSnippets: false, //The Razor mode snippets are awful (Need a way to override these) - enableBasicAutocompletion: true, - enableLiveAutocompletion: false - }, - onLoad: function (_editor) { - vm.editor = _editor; - - //Update the auto-complete method to use ctrl+alt+space - _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); - - // Unassigns the keybinding (That was previously auto-complete) - // As conflicts with our own tree search shortcut - _editor.commands.bindKey("ctrl-space", null); - - // Assign new keybinding - _editor.commands.addCommands([ - // Disable (alt+shift+K) - // Conflicts with our own show shortcuts dialog - this overrides it - { - name: 'unSelectOrFindPrevious', - bindKey: 'Alt-Shift-K', - exec: function () { - // Toggle the show keyboard shortcuts overlay - $scope.$apply(function () { - vm.showKeyboardShortcut = !vm.showKeyboardShortcut; - }); - - }, - readOnly: true - }, - { - name: 'insertUmbracoValue', - bindKey: 'Alt-Shift-V', - exec: function () { - $scope.$apply(function () { - openPageFieldOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertPartialView', - bindKey: 'Alt-Shift-P', - exec: function () { - $scope.$apply(function () { - openPartialOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertDictionary', - bindKey: 'Alt-Shift-D', - exec: function () { - $scope.$apply(function () { - openDictionaryItemOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertUmbracoMacro', - bindKey: 'Alt-Shift-M', - exec: function () { - $scope.$apply(function () { - openMacroOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertQuery', - bindKey: 'Alt-Shift-Q', - exec: function () { - $scope.$apply(function () { - openQueryBuilderOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertSection', - bindKey: 'Alt-Shift-S', - exec: function () { - $scope.$apply(function () { - openSectionsOverlay(); - }); - }, - readOnly: true - }, - { - name: 'chooseMasterTemplate', - bindKey: 'Alt-Shift-T', - exec: function () { - $scope.$apply(function () { - openMasterTemplateOverlay(); - }); - }, - readOnly: true - } - - ]); - - // 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 (!create) { - $timeout(function () { - vm.editor.navigateFileEnd(); - vm.editor.focus(); - persistCurrentLocation(); - }); - } - - // change on blur, focus - vm.editor.on("blur", persistCurrentLocation); - vm.editor.on("focus", persistCurrentLocation); - vm.editor.on("change", changeAceEditor); - } - } - - }; - - vm.openPageFieldOverlay = openPageFieldOverlay; - vm.openDictionaryItemOverlay = openDictionaryItemOverlay; - vm.openQueryBuilderOverlay = openQueryBuilderOverlay; - vm.openMacroOverlay = openMacroOverlay; - vm.openInsertOverlay = openInsertOverlay; - vm.openSectionsOverlay = openSectionsOverlay; - vm.openPartialOverlay = openPartialOverlay; - vm.openMasterTemplateOverlay = openMasterTemplateOverlay; - vm.selectMasterTemplate = selectMasterTemplate; - vm.getMasterTemplateName = getMasterTemplateName; - vm.removeMasterTemplate = removeMasterTemplate; - vm.closeShortcuts = closeShortcuts; - vm.submit = submit; - vm.close = close; - - function openInsertOverlay() { - var insertOverlay = { - allowedTypes: { - macro: true, - dictionary: true, - partial: true, - umbracoField: 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 "partial": - var code = templateHelper.getInsertPartialSnippet(model.insert.node.parentId, model.insert.node.name); - insert(code); - break; - case "umbracoField": - insert(model.insert.umbracoField); - break; - } - editorService.close(); - }, - close: function (oldModel) { - // close the dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.insertCodeSnippet(insertOverlay); - } - - function openMacroOverlay() { - var macroPicker = { - dialogData: {}, - submit: function (model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); - insert(macroObject.syntax); - editorService.close(); - }, - close: function () { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.macroPicker(macroPicker); - } - - function openPageFieldOverlay() { - var insertFieldEditor = { - submit: function (model) { - insert(model.umbracoField); - editorService.close(); - }, - close: function () { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.insertField(insertFieldEditor); - } - - - function openDictionaryItemOverlay() { - - var labelKeys = [ - "template_insertDictionaryItem", - "emptyStates_emptyDictionaryTree" - ]; - - localizationService.localizeMany(labelKeys).then(function (values) { - var title = values[0]; - var emptyStateMessage = values[1]; - - var dictionaryItem = { - section: "translation", - treeAlias: "dictionary", - entityType: "dictionary", - multiPicker: false, - title: title, - emptyStateMessage: emptyStateMessage, - select: function (node) { - var code = templateHelper.getInsertDictionarySnippet(node.name); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - - editorService.treePicker(dictionaryItem); - - }); - - } - - function openPartialOverlay() { - - localizationService.localize("template_insertPartialView").then(function (value) { - var title = value; - - var partialItem = { - section: "settings", - treeAlias: "partialViews", - entityType: "partialView", - multiPicker: false, - title: title, - filter: function (i) { - if (i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) { - return true; - } - }, - filterCssClass: "not-allowed", - select: function (node) { - var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - - editorService.treePicker(partialItem); - }); - } - - function openQueryBuilderOverlay() { - var queryBuilder = { - submit: function (model) { - var code = templateHelper.getQuerySnippet(model.result.queryExpression); - insert(code); - editorService.close(); - }, - close: function () { - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.queryBuilder(queryBuilder); - } - - - function openSectionsOverlay() { - var templateSections = { - isMaster: vm.template.isMasterTemplate, - submit: function (model) { - - if (model.insertType === 'renderBody') { - var code = templateHelper.getRenderBodySnippet(); - insert(code); - } - - if (model.insertType === 'renderSection') { - var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection); - insert(code); - } - - if (model.insertType === 'addSection') { - var code = templateHelper.getAddSectionSnippet(model.sectionName); - wrap(code); - } - - editorService.close(); - - }, - close: function (model) { - editorService.close(); - vm.editor.focus(); - } - } - editorService.templateSections(templateSections); - } - - function openMasterTemplateOverlay() { - - // make collection of available master templates - var availableMasterTemplates = []; - - // filter out the current template and the selected master template - vm.templates.forEach(function (template) { - if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) { - var templatePathArray = template.path.split(','); - // filter descendant templates of current template - if (templatePathArray.indexOf(String(vm.template.id)) === -1) { - availableMasterTemplates.push(template); - } - } - }); - - const editor = { - filterCssClass: 'not-allowed', - filter: item => !availableMasterTemplates.some(template => template.id == item.id), - submit: model => { - const template = model.selection[0]; - if (template && template.alias) { - vm.template.masterTemplateAlias = template.alias; - setLayout(template.alias + ".cshtml"); - } else { - vm.template.masterTemplateAlias = null; - setLayout(null); - } - editorService.close(); - }, - close: () => editorService.close() - } - - localizationService.localize("template_mastertemplate").then(title => { - editor.title = title; - - const currentTemplate = vm.templates.find(template => template.alias == vm.template.masterTemplateAlias); - if (currentTemplate) { - editor.currentNode = { - path: currentTemplate.path - }; - } - - editorService.templatePicker(editor); - }); - - } - - function selectMasterTemplate(template) { - - if (template && template.alias) { - vm.template.masterTemplateAlias = template.alias; - setLayout(template.alias + ".cshtml"); - } else { - vm.template.masterTemplateAlias = null; - setLayout(null); - } - - } - - function getMasterTemplateName(masterTemplateAlias, templates) { - if (masterTemplateAlias) { - var templateName = ""; - templates.forEach(function (template) { - if (template.alias === masterTemplateAlias) { - templateName = template.name; - } - }); - return templateName; - } - } - - function removeMasterTemplate() { - - vm.template.masterTemplateAlias = null; - - // call set layout with no paramters to set layout to null - setLayout(); - - } - - function setLayout(templatePath) { - - var templateCode = vm.editor.getValue(); - var newValue = templatePath; - var layoutDefRegex = new RegExp("(@{[\\s\\S]*?Layout\\s*?=\\s*?)(\"[^\"]*?\"|null)(;[\\s\\S]*?})", "gi"); - - if (newValue !== undefined && newValue !== "") { - if (layoutDefRegex.test(templateCode)) { - // Declaration exists, so just update it - templateCode = templateCode.replace(layoutDefRegex, "$1\"" + newValue + "\"$3"); - } else { - // Declaration doesn't exist, so prepend to start of doc - // TODO: Maybe insert at the cursor position, rather than just at the top of the doc? - templateCode = "@{\n\tLayout = \"" + newValue + "\";\n}\n" + templateCode; - } - } else { - if (layoutDefRegex.test(templateCode)) { - // Declaration exists, so just update it - templateCode = templateCode.replace(layoutDefRegex, "$1null$3"); - } - } - - vm.editor.setValue(templateCode); - vm.editor.clearSelection(); - vm.editor.navigateFileStart(); - - vm.editor.focus(); - // set form state to $dirty - setFormState("dirty"); - - } - - - function insert(str) { - vm.editor.focus(); - vm.editor.moveCursorToPosition(vm.currentPosition); - vm.editor.insert(str); - - // set form state to $dirty - setFormState("dirty"); - } - - function wrap(str) { - - var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange()); - str = str.replace("{0}", selectedContent); - vm.editor.insert(str); - vm.editor.focus(); - - // set form state to $dirty - setFormState("dirty"); - } - - function persistCurrentLocation() { - vm.currentPosition = vm.editor.getCursorPosition(); - } - - function changeAceEditor() { - setFormState("dirty"); - } - - 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(); - } - } - - function closeShortcuts() { - vm.showKeyboardShortcut = false; - } - - function submit() { - if ($scope.model.submit) { - $scope.model.template = vm.template; - $scope.model.submit($scope.model); - } - } - - function close() { - if ($scope.model.close) { - $scope.model.close(); - } - } - - vm.init(); + }); } - angular.module("umbraco").controller("Umbraco.Editors.Templates.EditController", TemplatesEditController); + function openPartialOverlay() { + + localizationService.localize("template_insertPartialView").then(function (value) { + var title = value; + + var partialItem = { + section: "settings", + treeAlias: "partialViews", + entityType: "partialView", + multiPicker: false, + title: title, + filter: function (i) { + if (i.name.indexOf(".cshtml") === -1 && i.name.indexOf(".vbhtml") === -1) { + return true; + } + }, + filterCssClass: "not-allowed", + select: function (node) { + var code = templateHelper.getInsertPartialSnippet(node.parentId, node.name); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + + editorService.treePicker(partialItem); + }); + } + + function openQueryBuilderOverlay() { + var queryBuilder = { + submit: function (model) { + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + editorService.close(); + }, + close: function () { + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.queryBuilder(queryBuilder); + } + + + function openSectionsOverlay() { + var templateSections = { + isMaster: vm.template.isMasterTemplate, + submit: function (model) { + + if (model.insertType === 'renderBody') { + var code = templateHelper.getRenderBodySnippet(); + insert(code); + } + + if (model.insertType === 'renderSection') { + var code = templateHelper.getRenderSectionSnippet(model.renderSectionName, model.mandatoryRenderSection); + insert(code); + } + + if (model.insertType === 'addSection') { + var code = templateHelper.getAddSectionSnippet(model.sectionName); + wrap(code); + } + + editorService.close(); + + }, + close: function (model) { + editorService.close(); + vm.editor.focus(); + } + } + editorService.templateSections(templateSections); + } + + function openMasterTemplateOverlay() { + + // make collection of available master templates + var availableMasterTemplates = []; + + // filter out the current template and the selected master template + vm.templates.forEach(function (template) { + if (template.alias !== vm.template.alias && template.alias !== vm.template.masterTemplateAlias) { + var templatePathArray = template.path.split(','); + // filter descendant templates of current template + if (templatePathArray.indexOf(String(vm.template.id)) === -1) { + availableMasterTemplates.push(template); + } + } + }); + + const editor = { + filterCssClass: 'not-allowed', + filter: item => !availableMasterTemplates.some(template => template.id == item.id), + submit: model => { + const template = model.selection[0]; + if (template && template.alias) { + vm.template.masterTemplateAlias = template.alias; + setLayout(template.alias + ".cshtml"); + } else { + vm.template.masterTemplateAlias = null; + setLayout(null); + } + editorService.close(); + }, + close: () => editorService.close() + } + + localizationService.localize("template_mastertemplate").then(title => { + editor.title = title; + + const currentTemplate = vm.templates.find(template => template.alias == vm.template.masterTemplateAlias); + if (currentTemplate) { + editor.currentNode = { + path: currentTemplate.path + }; + } + + editorService.templatePicker(editor); + }); + + } + + function selectMasterTemplate(template) { + + if (template && template.alias) { + vm.template.masterTemplateAlias = template.alias; + setLayout(template.alias + ".cshtml"); + } else { + vm.template.masterTemplateAlias = null; + setLayout(null); + } + + } + + function getMasterTemplateName(masterTemplateAlias, templates) { + if (masterTemplateAlias) { + var templateName = ""; + templates.forEach(function (template) { + if (template.alias === masterTemplateAlias) { + templateName = template.name; + } + }); + return templateName; + } + } + + function removeMasterTemplate() { + + vm.template.masterTemplateAlias = null; + + // call set layout with no paramters to set layout to null + setLayout(); + + } + + function setLayout(templatePath) { + + var templateCode = vm.editor.getValue(); + var newValue = templatePath; + var layoutDefRegex = new RegExp("(@{[\\s\\S]*?Layout\\s*?=\\s*?)(\"[^\"]*?\"|null)(;[\\s\\S]*?})", "gi"); + + if (newValue !== undefined && newValue !== "") { + if (layoutDefRegex.test(templateCode)) { + // Declaration exists, so just update it + templateCode = templateCode.replace(layoutDefRegex, "$1\"" + newValue + "\"$3"); + } else { + // Declaration doesn't exist, so prepend to start of doc + // TODO: Maybe insert at the cursor position, rather than just at the top of the doc? + templateCode = "@{\n\tLayout = \"" + newValue + "\";\n}\n" + templateCode; + } + } else { + if (layoutDefRegex.test(templateCode)) { + // Declaration exists, so just update it + templateCode = templateCode.replace(layoutDefRegex, "$1null$3"); + } + } + + vm.editor.setValue(templateCode); + vm.editor.clearSelection(); + vm.editor.navigateFileStart(); + + vm.editor.focus(); + // set form state to $dirty + setFormState("dirty"); + + } + + + function insert(str) { + vm.editor.focus(); + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + + // set form state to $dirty + setFormState("dirty"); + } + + function wrap(str) { + + var selectedContent = vm.editor.session.getTextRange(vm.editor.getSelectionRange()); + str = str.replace("{0}", selectedContent); + vm.editor.insert(str); + vm.editor.focus(); + + // set form state to $dirty + setFormState("dirty"); + } + + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + + function changeAceEditor() { + setFormState("dirty"); + } + + 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(); + } + } + + function closeShortcuts() { + vm.showKeyboardShortcut = false; + } + + function submit() { + if ($scope.model.submit) { + $scope.model.template = vm.template; + $scope.model.submit($scope.model); + } + } + + function close() { + if ($scope.model.close) { + $scope.model.close(); + } + } + + vm.init(); + + } + + angular.module("umbraco").controller("Umbraco.Editors.Templates.EditController", TemplatesEditController); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index f909bc197f..663e0a1f04 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -12,6 +12,7 @@ - + +
+ Template content is not editable when using runtime mode Production. +
+
+ +
@@ -106,6 +113,7 @@ From 40eae24a552c2c4de8c180c3600bd3b9f36089a2 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Thu, 14 Jul 2022 23:41:17 +0200 Subject: [PATCH 26/32] Show editor in read-only mode when using runtime mode production --- .../src/views/templates/edit.controller.js | 9 +++++---- .../src/views/templates/edit.html | 16 +++++++--------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js index e231ff51f5..1e14cf1e87 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.controller.js @@ -83,9 +83,7 @@ vm.save = function (suppressNotification) { vm.page.saveButtonState = "busy"; - if (vm.editor) { - vm.template.content = vm.editor.getValue(); - } + vm.template.content = vm.editor.getValue(); contentEditingHelper.contentEditorPerformSave({ saveMethod: templateResource.save, @@ -228,7 +226,10 @@ onLoad: function (_editor) { vm.editor = _editor; - //Update the auto-complete method to use ctrl+alt+space + // Set read-only when using runtime mode Production + _editor.setReadOnly(vm.runtimeModeProduction); + + // Update the auto-complete method to use ctrl+alt+space _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); // Unassigns the keybinding (That was previously auto-complete) diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index 663e0a1f04..5fb4118491 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -23,15 +23,13 @@ - -
- Template content is not editable when using runtime mode Production. -
-
+ + +
+ Template content is not editable when using runtime mode Production. +
- - -
+
@@ -99,7 +97,7 @@
From 234552bcab93e7b701d0083933a91527498237a8 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 15 Jul 2022 10:34:11 +0200 Subject: [PATCH 27/32] Disable editing partial views and macro files --- .../partialViewMacros/edit.controller.js | 683 +++++++-------- .../src/views/partialViewMacros/edit.html | 118 +-- .../src/views/partialViews/edit.controller.js | 819 +++++++++--------- .../src/views/partialViews/edit.html | 149 ++-- .../src/views/templates/edit.html | 242 +++--- 5 files changed, 1007 insertions(+), 1004 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js index 2f7d879607..d72cdbe69a 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.controller.js @@ -1,362 +1,367 @@ (function () { - "use strict"; + "use strict"; - function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource, editorService) { + function partialViewMacrosEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, macroResource, editorService) { - var vm = this; + var vm = this; - vm.header = {}; - vm.header.editorfor = "visuallyHiddenTexts_newPartialViewMacro"; - vm.header.setPageTitle = true; - vm.page = {}; - vm.page.loading = true; - vm.partialViewMacroFile = {}; + vm.runtimeModeProduction = Umbraco.Sys.ServerVariables.application.runtimeMode == 'Production'; - //menu - vm.page.menu = {}; - vm.page.menu.currentSection = appState.getSectionState("currentSection"); - vm.page.menu.currentNode = null; + vm.header = {}; + vm.header.editorfor = "visuallyHiddenTexts_newPartialViewMacro"; + vm.header.setPageTitle = true; + vm.page = {}; + vm.page.loading = true; + vm.partialViewMacroFile = {}; - // insert buttons - vm.page.insertDefaultButton = { - labelKey: "general_insert", - addEllipsis: "true", - handler: function() { - vm.openInsertOverlay(); - } - }; - vm.page.insertSubButtons = [ - { - labelKey: "template_insertPageField", - addEllipsis: "true", - handler: function () { - vm.openPageFieldOverlay(); - } - }, - { - labelKey: "template_insertMacro", - addEllipsis: "true", - handler: function () { - vm.openMacroOverlay() - } - }, - { - labelKey: "template_insertDictionaryItem", - addEllipsis: "true", - handler: function () { - vm.openDictionaryItemOverlay(); - } - } - ]; + //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; + // insert buttons + vm.page.insertDefaultButton = { + labelKey: "general_insert", + addEllipsis: "true", + handler: function () { + vm.openInsertOverlay(); + } + }; + vm.page.insertSubButtons = [ + { + labelKey: "template_insertPageField", + addEllipsis: "true", + handler: function () { + vm.openPageFieldOverlay(); + } + }, + { + labelKey: "template_insertMacro", + addEllipsis: "true", + handler: function () { + vm.openMacroOverlay() + } + }, + { + labelKey: "template_insertDictionaryItem", + addEllipsis: "true", + handler: function () { + vm.openDictionaryItemOverlay(); + } + } + ]; - /* Functions bound to view model */ + // bind functions to view model + vm.save = save; + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; - function save() { + /* Functions bound to view model */ - vm.page.saveButtonState = "busy"; - vm.partialViewMacro.content = vm.editor.getValue(); + function save() { - contentEditingHelper.contentEditorPerformSave({ - saveMethod: codefileResource.save, - scope: $scope, - content: vm.partialViewMacro, - rebindCallback: function (orignal, saved) {} - }).then(function (saved) { - // create macro if needed - if($routeParams.create && $routeParams.nomacro !== "true") { - macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) { - navigationService.syncTree({ - tree: "macros", - path: '-1,new', - forceReload: true, - activate: false - }); - completeSave(saved); - }, Utilities.noop); - - - } else { - completeSave(saved); - } - - }, function (err) { - - vm.page.saveButtonState = "error"; - - localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_validationFailedMessage").then(function(msgValue) { - notificationsService.error(headerValue, msgValue); - }); - }); + vm.page.saveButtonState = "busy"; + vm.partialViewMacro.content = vm.editor.getValue(); + contentEditingHelper.contentEditorPerformSave({ + saveMethod: codefileResource.save, + scope: $scope, + content: vm.partialViewMacro, + rebindCallback: function (orignal, saved) { } + }).then(function (saved) { + // create macro if needed + if ($routeParams.create && $routeParams.nomacro !== "true") { + macroResource.createPartialViewMacroWithFile(saved.virtualPath, saved.name).then(function (created) { + navigationService.syncTree({ + tree: "macros", + path: '-1,new', + forceReload: true, + activate: false }); + completeSave(saved); + }, Utilities.noop); + + } else { + completeSave(saved); } - function completeSave(saved) { + }, function (err) { - localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) { - localizationService.localize("speechBubbles_partialViewSavedText").then(function (msgValue) { - notificationsService.success(headerValue, msgValue); - }); - }); + vm.page.saveButtonState = "error"; - //check if the name changed, if so we need to redirect - if (vm.partialViewMacro.id !== saved.id) { - contentEditingHelper.redirectToRenamedContent(saved.id); - } - else { - vm.page.saveButtonState = "success"; - vm.partialViewMacro = saved; + localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { + localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); - //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 openInsertOverlay() { - var insertOverlay = { - allowedTypes: { - macro: true, - dictionary: true, - umbracoField: 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; - } - editorService.close(); - }, - close: function(oldModel) { - // close the dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.insertCodeSnippet(insertOverlay); - } - - function openMacroOverlay() { - var macroPicker = { - dialogData: {}, - submit: function (model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); - insert(macroObject.syntax); - editorService.close(); - }, - close: function() { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.macroPicker(macroPicker); - } - - - function openPageFieldOverlay() { - var insertFieldEditor = { - submit: function (model) { - insert(model.umbracoField); - editorService.close(); - }, - close: function () { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.insertField(insertFieldEditor); - } - - - function openDictionaryItemOverlay() { - - var labelKeys = [ - "template_insertDictionaryItem", - "emptyStates_emptyDictionaryTree" - ]; - - localizationService.localizeMany(labelKeys).then(function(values){ - var title = values[0]; - var emptyStateMessage = values[1]; - - var dictionaryPicker = { - section: "translation", - treeAlias: "dictionary", - entityType: "dictionary", - multiPicker: false, - title: title, - emptyStateMessage: emptyStateMessage, - select: function(node){ - var code = templateHelper.getInsertDictionarySnippet(node.name); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - - editorService.treePicker(dictionaryPicker); - - }); - } - - function openQueryBuilderOverlay() { - var queryBuilder = { - submit: function (model) { - var code = templateHelper.getQuerySnippet(model.result.queryExpression); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.queryBuilder(queryBuilder); - } - - /* Local functions */ - - function init() { - //we need to load this somewhere, for now its here. - assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); - - if ($routeParams.create) { - - var snippet = "Empty"; - - if($routeParams.snippet) { - snippet = $routeParams.snippet; - } - - codefileResource.getScaffold("partialViewMacros", $routeParams.id, snippet).then(function (partialViewMacro) { - if ($routeParams.name) { - partialViewMacro.name = $routeParams.name; - } - ready(partialViewMacro, false); - }); - - } else { - codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) { - ready(partialViewMacro, true); - }); - } - } - - function ready(partialViewMacro, syncTree) { - - vm.page.loading = false; - vm.partialViewMacro = partialViewMacro; - - //sync state - editorState.set(vm.partialViewMacro); - - if (syncTree) { - navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.path, 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); - vm.editor.on("change", changeAceEditor); - - } - } - - } - - function insert(str) { - vm.editor.focus(); - vm.editor.moveCursorToPosition(vm.currentPosition); - vm.editor.insert(str); - - // set form state to $dirty - setFormState("dirty"); - } - - function persistCurrentLocation() { - vm.currentPosition = vm.editor.getCursorPosition(); - } - - function changeAceEditor() { - setFormState("dirty"); - } - - 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); + function completeSave(saved) { + + localizationService.localize("speechBubbles_partialViewSavedHeader").then(function (headerValue) { + localizationService.localize("speechBubbles_partialViewSavedText").then(function (msgValue) { + notificationsService.success(headerValue, msgValue); + }); + }); + + //check if the name changed, if so we need to redirect + if (vm.partialViewMacro.id !== saved.id) { + contentEditingHelper.redirectToRenamedContent(saved.id); + } + else { + 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 openInsertOverlay() { + var insertOverlay = { + allowedTypes: { + macro: true, + dictionary: true, + umbracoField: 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; + } + editorService.close(); + }, + close: function (oldModel) { + // close the dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.insertCodeSnippet(insertOverlay); + } + + function openMacroOverlay() { + var macroPicker = { + dialogData: {}, + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); + insert(macroObject.syntax); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.macroPicker(macroPicker); + } + + + function openPageFieldOverlay() { + var insertFieldEditor = { + submit: function (model) { + insert(model.umbracoField); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.insertField(insertFieldEditor); + } + + + function openDictionaryItemOverlay() { + + var labelKeys = [ + "template_insertDictionaryItem", + "emptyStates_emptyDictionaryTree" + ]; + + localizationService.localizeMany(labelKeys).then(function (values) { + var title = values[0]; + var emptyStateMessage = values[1]; + + var dictionaryPicker = { + section: "translation", + treeAlias: "dictionary", + entityType: "dictionary", + multiPicker: false, + title: title, + emptyStateMessage: emptyStateMessage, + select: function (node) { + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + + editorService.treePicker(dictionaryPicker); + + }); + } + + function openQueryBuilderOverlay() { + var queryBuilder = { + submit: function (model) { + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.queryBuilder(queryBuilder); + } + + /* Local functions */ + + function init() { + //we need to load this somewhere, for now its here. + assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); + + if ($routeParams.create) { + + var snippet = "Empty"; + + if ($routeParams.snippet) { + snippet = $routeParams.snippet; + } + + codefileResource.getScaffold("partialViewMacros", $routeParams.id, snippet).then(function (partialViewMacro) { + if ($routeParams.name) { + partialViewMacro.name = $routeParams.name; + } + ready(partialViewMacro, false); + }); + + } else { + codefileResource.getByPath('partialViewMacros', $routeParams.id).then(function (partialViewMacro) { + ready(partialViewMacro, true); + }); + } + } + + function ready(partialViewMacro, syncTree) { + + vm.page.loading = false; + vm.partialViewMacro = partialViewMacro; + + //sync state + editorState.set(vm.partialViewMacro); + + if (syncTree) { + navigationService.syncTree({ tree: "partialViewMacros", path: vm.partialViewMacro.path, 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; + + // Set read-only when using runtime mode Production + _editor.setReadOnly(vm.runtimeModeProduction); + + // 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); + vm.editor.on("change", changeAceEditor); + + } + } + + } + + function insert(str) { + vm.editor.focus(); + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + + // set form state to $dirty + setFormState("dirty"); + } + + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + + function changeAceEditor() { + setFormState("dirty"); + } + + 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); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html index 36fff3a044..d15bd866eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html @@ -1,81 +1,81 @@
- + -
+ - + - - + + - - - + + + +
+ Template content is not editable when using runtime mode Production. +
-
+
-
+
- - + + - - + + -
+
-
+
-
-
+
+
-
-
-
+
+
+
- + - + - - + + - + - + -
- +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js index 1e21a63755..512c1176c1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.controller.js @@ -1,433 +1,438 @@ (function () { - "use strict"; + "use strict"; - function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, editorService) { + function PartialViewsEditController($scope, $routeParams, codefileResource, assetsService, notificationsService, editorState, navigationService, appState, macroService, angularHelper, $timeout, contentEditingHelper, localizationService, templateHelper, editorService) { - var vm = this; - var infiniteMode = $scope.model && $scope.model.infiniteMode; - var id = infiniteMode ? $scope.model.id : $routeParams.id; - var create = infiniteMode ? $scope.model.create : $routeParams.create; - var snippet = infiniteMode ? $scope.model.snippet : $routeParams.snippet; + var vm = this; + var infiniteMode = $scope.model && $scope.model.infiniteMode; + var id = infiniteMode ? $scope.model.id : $routeParams.id; + var create = infiniteMode ? $scope.model.create : $routeParams.create; + var snippet = infiniteMode ? $scope.model.snippet : $routeParams.snippet; - function close() { - if ($scope.model.close) { - $scope.model.close($scope.model); - } + function close() { + if ($scope.model.close) { + $scope.model.close($scope.model); + } + } + + vm.close = close; + + vm.runtimeModeProduction = Umbraco.Sys.ServerVariables.application.runtimeMode == 'Production'; + + vm.header = {}; + vm.header.editorfor = "visuallyHiddenTexts_newPartialView"; + vm.header.setPageTitle = true; + + vm.page = {}; + vm.page.loading = true; + vm.partialView = {}; + + //menu + vm.page.menu = {}; + vm.page.menu.currentSection = appState.getSectionState("currentSection"); + vm.page.menu.currentNode = null; + + // insert buttons + vm.page.insertDefaultButton = { + labelKey: "general_insert", + addEllipsis: "true", + handler: function () { + vm.openInsertOverlay(); + } + }; + vm.page.insertSubButtons = [ + { + labelKey: "template_insertPageField", + addEllipsis: "true", + handler: function () { + vm.openPageFieldOverlay(); } + }, + { + labelKey: "template_insertMacro", + addEllipsis: "true", + handler: function () { + vm.openMacroOverlay() + } + }, + { + labelKey: "template_insertDictionaryItem", + addEllipsis: "true", + handler: function () { + vm.openDictionaryItemOverlay(); + } + } + ]; - vm.close = close; + //Used to toggle the keyboard shortcut modal + //From a custom keybinding in ace editor - that conflicts with our own to show the dialog + vm.showKeyboardShortcut = false; - vm.header = {}; - vm.header.editorfor = "visuallyHiddenTexts_newPartialView"; - vm.header.setPageTitle = true; + //Keyboard shortcuts for help dialog + vm.page.keyboardShortcutsOverview = []; - vm.page = {}; - vm.page.loading = true; - vm.partialView = {}; + templateHelper.getGeneralShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + templateHelper.getEditorShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); + templateHelper.getPartialViewEditorShortcuts().then(function (data) { + vm.page.keyboardShortcutsOverview.push(data); + }); - //menu - vm.page.menu = {}; - vm.page.menu.currentSection = appState.getSectionState("currentSection"); - vm.page.menu.currentNode = null; - // insert buttons - vm.page.insertDefaultButton = { - labelKey: "general_insert", - addEllipsis: "true", - handler: function() { - vm.openInsertOverlay(); - } - }; - vm.page.insertSubButtons = [ - { - labelKey: "template_insertPageField", - addEllipsis: "true", - handler: function () { - vm.openPageFieldOverlay(); - } - }, - { - labelKey: "template_insertMacro", - addEllipsis: "true", - handler: function () { - vm.openMacroOverlay() - } - }, - { - labelKey: "template_insertDictionaryItem", - addEllipsis: "true", - handler: function () { - vm.openDictionaryItemOverlay(); - } - } - ]; + // bind functions to view model + vm.save = save; + vm.openPageFieldOverlay = openPageFieldOverlay; + vm.openDictionaryItemOverlay = openDictionaryItemOverlay; + vm.openQueryBuilderOverlay = openQueryBuilderOverlay; + vm.openMacroOverlay = openMacroOverlay; + vm.openInsertOverlay = openInsertOverlay; - //Used to toggle the keyboard shortcut modal - //From a custom keybinding in ace editor - that conflicts with our own to show the dialog - vm.showKeyboardShortcut = false; + /* Functions bound to view model */ - //Keyboard shortcuts for help dialog - vm.page.keyboardShortcutsOverview = []; + function save() { - templateHelper.getGeneralShortcuts().then(function(data){ - vm.page.keyboardShortcutsOverview.push(data); - }); - templateHelper.getEditorShortcuts().then(function(data){ - vm.page.keyboardShortcutsOverview.push(data); - }); - templateHelper.getPartialViewEditorShortcuts().then(function(data){ - vm.page.keyboardShortcutsOverview.push(data); + vm.page.saveButtonState = "busy"; + vm.partialView.content = vm.editor.getValue(); + + contentEditingHelper.contentEditorPerformSave({ + saveMethod: codefileResource.save, + scope: $scope, + content: vm.partialView, + 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); + }); }); - - // 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({ - saveMethod: codefileResource.save, - scope: $scope, - content: vm.partialView, - 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); - }); - }); - - //check if the name changed, if so we need to redirect - if (vm.partialView.id !== saved.id) { - contentEditingHelper.redirectToRenamedContent(saved.id); - } - else { - 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); - }); - }); - - }); - + //check if the name changed, if so we need to redirect + if (vm.partialView.id !== saved.id) { + contentEditingHelper.redirectToRenamedContent(saved.id); } + else { + vm.page.saveButtonState = "success"; + vm.partialView = saved; - function openInsertOverlay() { - var insertOverlay = { - allowedTypes: { - macro: true, - dictionary: true, - umbracoField: true - }, - submit: function(model) { + //sync state + editorState.set(vm.partialView); - 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; - } - editorService.close(); - }, - close: function() { - // close the dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.insertCodeSnippet(insertOverlay); + // 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"; - function openMacroOverlay() { - var macroPicker = { - dialogData: {}, - submit: function (model) { - var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); - insert(macroObject.syntax); - editorService.close(); - }, - close: function() { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.macroPicker(macroPicker); - } + localizationService.localize("speechBubbles_validationFailedHeader").then(function (headerValue) { + localizationService.localize("speechBubbles_validationFailedMessage").then(function (msgValue) { + notificationsService.error(headerValue, msgValue); + }); + }); - function openPageFieldOverlay() { - var insertFieldEditor = { - submit: function (model) { - insert(model.umbracoField); - editorService.close(); - }, - close: function () { - editorService.close(); - vm.editor.focus(); - } - }; - editorService.insertField(insertFieldEditor); - } - - - function openDictionaryItemOverlay() { - - var labelKeys = [ - "template_insertDictionaryItem", - "emptyStates_emptyDictionaryTree" - ]; - - localizationService.localizeMany(labelKeys).then(function(values){ - var title = values[0]; - var emptyStateMessage = values[1]; - - var dictionaryItem = { - section: "translation", - treeAlias: "dictionary", - entityType: "dictionary", - multiPicker: false, - title: title, - emptyStateMessage: emptyStateMessage, - select: function(node){ - var code = templateHelper.getInsertDictionarySnippet(node.name); - insert(code); - editorService.close(); - }, - close: function (model) { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.treePicker(dictionaryItem); - }); - } - - function openQueryBuilderOverlay() { - var queryBuilder = { - title: "Query for content", - submit: function (model) { - var code = templateHelper.getQuerySnippet(model.result.queryExpression); - insert(code); - editorService.close(); - }, - close: function () { - // close dialog - editorService.close(); - // focus editor - vm.editor.focus(); - } - }; - editorService.queryBuilder(queryBuilder); - } - - /* Local functions */ - - function init() { - //we need to load this somewhere, for now its here. - assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); - - if (create) { - - if (!snippet) { - snippet = "Empty"; - } - - codefileResource.getScaffold("partialViews", id, snippet).then(function (partialView) { - ready(partialView, false); - }); - - } else { - codefileResource.getByPath('partialViews', id).then(function (partialView) { - ready(partialView, true); - }); - } - - } - - function ready(partialView, syncTree) { - - vm.page.loading = false; - vm.partialView = partialView; - - //sync state - editorState.set(vm.partialView); - - if (!infiniteMode && syncTree) { - navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, 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; - - //Update the auto-complete method to use ctrl+alt+space - _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); - - //Unassigns the keybinding (That was previously auto-complete) - //As conflicts with our own tree search shortcut - _editor.commands.bindKey("ctrl-space", null); - - // Assign new keybinding - _editor.commands.addCommands([ - //Disable (alt+shift+K) - //Conflicts with our own show shortcuts dialog - this overrides it - { - name: 'unSelectOrFindPrevious', - bindKey: 'Alt-Shift-K', - exec: function () { - //Toggle the show keyboard shortcuts overlay - $scope.$apply(function () { - vm.showKeyboardShortcut = !vm.showKeyboardShortcut; - }); - }, - readOnly: true - }, - { - name: 'insertUmbracoValue', - bindKey: 'Alt-Shift-V', - exec: function () { - $scope.$apply(function () { - openPageFieldOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertDictionary', - bindKey: 'Alt-Shift-D', - exec: function () { - $scope.$apply(function () { - openDictionaryItemOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertUmbracoMacro', - bindKey: 'Alt-Shift-M', - exec: function () { - $scope.$apply(function () { - openMacroOverlay(); - }); - }, - readOnly: true - }, - { - name: 'insertQuery', - bindKey: 'Alt-Shift-Q', - exec: function () { - $scope.$apply(function () { - openQueryBuilderOverlay(); - }); - }, - readOnly: true - } - - ]); - - // 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(!create) { - $timeout(function(){ - vm.editor.navigateFileEnd(); - vm.editor.focus(); - persistCurrentLocation(); - }); - } - - //change on blur, focus - vm.editor.on("blur", persistCurrentLocation); - vm.editor.on("focus", persistCurrentLocation); - vm.editor.on("change", changeAceEditor); - - } - } - - } - - function insert(str) { - vm.editor.focus(); - vm.editor.moveCursorToPosition(vm.currentPosition); - vm.editor.insert(str); - - // set form state to $dirty - setFormState("dirty"); - } - - function persistCurrentLocation() { - vm.currentPosition = vm.editor.getCursorPosition(); - } - - function changeAceEditor() { - setFormState("dirty"); - } - - 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); + function openInsertOverlay() { + var insertOverlay = { + allowedTypes: { + macro: true, + dictionary: true, + umbracoField: 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; + } + editorService.close(); + }, + close: function () { + // close the dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.insertCodeSnippet(insertOverlay); + } + + + function openMacroOverlay() { + var macroPicker = { + dialogData: {}, + submit: function (model) { + var macroObject = macroService.collectValueData(model.selectedMacro, model.macroParams, "Mvc"); + insert(macroObject.syntax); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.macroPicker(macroPicker); + } + + function openPageFieldOverlay() { + var insertFieldEditor = { + submit: function (model) { + insert(model.umbracoField); + editorService.close(); + }, + close: function () { + editorService.close(); + vm.editor.focus(); + } + }; + editorService.insertField(insertFieldEditor); + } + + + function openDictionaryItemOverlay() { + + var labelKeys = [ + "template_insertDictionaryItem", + "emptyStates_emptyDictionaryTree" + ]; + + localizationService.localizeMany(labelKeys).then(function (values) { + var title = values[0]; + var emptyStateMessage = values[1]; + + var dictionaryItem = { + section: "translation", + treeAlias: "dictionary", + entityType: "dictionary", + multiPicker: false, + title: title, + emptyStateMessage: emptyStateMessage, + select: function (node) { + var code = templateHelper.getInsertDictionarySnippet(node.name); + insert(code); + editorService.close(); + }, + close: function (model) { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.treePicker(dictionaryItem); + }); + } + + function openQueryBuilderOverlay() { + var queryBuilder = { + title: "Query for content", + submit: function (model) { + var code = templateHelper.getQuerySnippet(model.result.queryExpression); + insert(code); + editorService.close(); + }, + close: function () { + // close dialog + editorService.close(); + // focus editor + vm.editor.focus(); + } + }; + editorService.queryBuilder(queryBuilder); + } + + /* Local functions */ + + function init() { + //we need to load this somewhere, for now its here. + assetsService.loadCss("lib/ace-razor-mode/theme/razor_chrome.css", $scope); + + if (create) { + + if (!snippet) { + snippet = "Empty"; + } + + codefileResource.getScaffold("partialViews", id, snippet).then(function (partialView) { + ready(partialView, false); + }); + + } else { + codefileResource.getByPath('partialViews', id).then(function (partialView) { + ready(partialView, true); + }); + } + + } + + function ready(partialView, syncTree) { + + vm.page.loading = false; + vm.partialView = partialView; + + //sync state + editorState.set(vm.partialView); + + if (!infiniteMode && syncTree) { + navigationService.syncTree({ tree: "partialViews", path: vm.partialView.path, 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; + + // Set read-only when using runtime mode Production + _editor.setReadOnly(vm.runtimeModeProduction); + + //Update the auto-complete method to use ctrl+alt+space + _editor.commands.bindKey("ctrl-alt-space", "startAutocomplete"); + + //Unassigns the keybinding (That was previously auto-complete) + //As conflicts with our own tree search shortcut + _editor.commands.bindKey("ctrl-space", null); + + // Assign new keybinding + _editor.commands.addCommands([ + //Disable (alt+shift+K) + //Conflicts with our own show shortcuts dialog - this overrides it + { + name: 'unSelectOrFindPrevious', + bindKey: 'Alt-Shift-K', + exec: function () { + //Toggle the show keyboard shortcuts overlay + $scope.$apply(function () { + vm.showKeyboardShortcut = !vm.showKeyboardShortcut; + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoValue', + bindKey: 'Alt-Shift-V', + exec: function () { + $scope.$apply(function () { + openPageFieldOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertDictionary', + bindKey: 'Alt-Shift-D', + exec: function () { + $scope.$apply(function () { + openDictionaryItemOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertUmbracoMacro', + bindKey: 'Alt-Shift-M', + exec: function () { + $scope.$apply(function () { + openMacroOverlay(); + }); + }, + readOnly: true + }, + { + name: 'insertQuery', + bindKey: 'Alt-Shift-Q', + exec: function () { + $scope.$apply(function () { + openQueryBuilderOverlay(); + }); + }, + readOnly: true + } + + ]); + + // 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 (!create) { + $timeout(function () { + vm.editor.navigateFileEnd(); + vm.editor.focus(); + persistCurrentLocation(); + }); + } + + //change on blur, focus + vm.editor.on("blur", persistCurrentLocation); + vm.editor.on("focus", persistCurrentLocation); + vm.editor.on("change", changeAceEditor); + + } + } + + } + + function insert(str) { + vm.editor.focus(); + vm.editor.moveCursorToPosition(vm.currentPosition); + vm.editor.insert(str); + + // set form state to $dirty + setFormState("dirty"); + } + + function persistCurrentLocation() { + vm.currentPosition = vm.editor.getCursorPosition(); + } + + function changeAceEditor() { + setFormState("dirty"); + } + + 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); })(); diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html index 4a4d01385e..ec090e6071 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html @@ -1,97 +1,98 @@
- + -
+ - + - - + + - + - - + + +
+ Template content is not editable when using runtime mode Production. +
-
+
-
+
- - + + - - + + -
+
-
+
-
-
+
+
-
-
- -
+ + - +
- - - - + - + + + + - - - - - - + - + + -
- + + + + + + +
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index 5fb4118491..dd2cda9a54 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -1,150 +1,142 @@
- + -
+ - + - - + + - - - - -
- Template content is not editable when using runtime mode Production. -
+ + -
+ +
+ Template content is not editable when using runtime mode Production. +
-
+
-
+
- +
- + -
+ -
+
-
+
- - +
- - + + - - - -
- -
- -
-
- - - - - - - - - - - - - - - - - + - + - +
- +
-
- +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+
From fabb49feaf2cfffd4f3d8ee0cce4a534b332bfdb Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 15 Jul 2022 10:45:29 +0200 Subject: [PATCH 28/32] Remove template from warning message --- src/Umbraco.Core/EmbeddedResources/Lang/en.xml | 2 +- src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml | 2 +- src/Umbraco.Core/EmbeddedResources/Lang/nl.xml | 2 +- src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html | 2 +- src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html | 2 +- src/Umbraco.Web.UI.Client/src/views/templates/edit.html | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml index 49ccf24dd7..d75e70bb07 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en.xml @@ -1630,7 +1630,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Editor - Production.]]> + Production.]]> Failed to delete template with ID %0% Edit template Sections diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml index 04883067f6..9641fda47f 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/en_us.xml @@ -1683,7 +1683,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont Rich Text Editor - Production.]]> + Production.]]> Failed to delete template with ID %0% Edit template Sections diff --git a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml index 06762e1dd7..1d76ac06f9 100644 --- a/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml +++ b/src/Umbraco.Core/EmbeddedResources/Lang/nl.xml @@ -1444,7 +1444,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je Rich Text Editor - Production.]]> + Production.]]> Kan sjabloon met ID %0% niet verwijderen Sjabloon aanpassen Secties diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html index d15bd866eb..7cffa70aa1 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialViewMacros/edit.html @@ -23,7 +23,7 @@
- Template content is not editable when using runtime mode Production. + Content is not editable when using runtime mode Production.
diff --git a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html index ec090e6071..e1eb195545 100644 --- a/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/partialViews/edit.html @@ -24,7 +24,7 @@
- Template content is not editable when using runtime mode Production. + Content is not editable when using runtime mode Production.
diff --git a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html index dd2cda9a54..a5527095d7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/templates/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/templates/edit.html @@ -25,7 +25,7 @@
- Template content is not editable when using runtime mode Production. + Content is not editable when using runtime mode Production.
From d2806501d993bc34156aa6918089bfc2e6bdece8 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 15 Jul 2022 11:07:24 +0200 Subject: [PATCH 29/32] Fix integration test ACE Editor mock --- .../test/unit/app/templates/template-editor-controller.spec.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js index b4615eaad8..9b872f137f 100644 --- a/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js +++ b/src/Umbraco.Web.UI.Client/test/unit/app/templates/template-editor-controller.spec.js @@ -1,4 +1,4 @@ -(function() { +(function() { "use strict"; describe("templates editor controller", @@ -26,6 +26,7 @@ getCursorPosition: function() {}, getValue: function() {}, setValue: function() {}, + setReadOnly: function () { }, focus: function() {}, clearSelection: function() {}, navigateFileStart: function() {}, From 2d217d505d3df54033eb279cbb677840f543fc25 Mon Sep 17 00:00:00 2001 From: Ronald Barendse Date: Fri, 15 Jul 2022 14:17:45 +0200 Subject: [PATCH 30/32] Fix integration tests --- .../Persistence/Repositories/ContentTypeRepositoryTest.cs | 5 ++++- .../Persistence/Repositories/DocumentRepositoryTest.cs | 5 ++++- .../Persistence/Repositories/TemplateRepositoryTest.cs | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs index e2a691a102..aae69e2f61 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/ContentTypeRepositoryTest.cs @@ -79,6 +79,9 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest var provider = ScopeProvider; using (var scope = provider.CreateScope()) { + var runtimeSettingsMock = new Mock>(); + runtimeSettingsMock.Setup(x => x.CurrentValue).Returns(new RuntimeSettings()); + var templateRepo = new TemplateRepository( (IScopeAccessor)provider, AppCaches.Disabled, @@ -87,7 +90,7 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest IOHelper, ShortStringHelper, Mock.Of(), - Mock.Of>()); + runtimeSettingsMock.Object); var repository = ContentTypeRepository; Template[] templates = { diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs index 319470917e..9870926544 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/DocumentRepositoryTest.cs @@ -111,7 +111,10 @@ public class DocumentRepositoryTest : UmbracoIntegrationTest { appCaches ??= AppCaches; - templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of(), Mock.Of>()); + var runtimeSettingsMock = new Mock>(); + runtimeSettingsMock.Setup(x => x.CurrentValue).Returns(new RuntimeSettings()); + + templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, Mock.Of(), runtimeSettingsMock.Object); var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger()); var commonRepository = new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches, ShortStringHelper); diff --git a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs index 961c11f471..e0665aaf6d 100644 --- a/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs +++ b/tests/Umbraco.Tests.Integration/Umbraco.Infrastructure/Persistence/Repositories/TemplateRepositoryTest.cs @@ -57,10 +57,13 @@ public class TemplateRepositoryTest : UmbracoIntegrationTest private IHostingEnvironment HostingEnvironment => GetRequiredService(); private FileSystems FileSystems => GetRequiredService(); + private IViewHelper ViewHelper => GetRequiredService(); + private IOptionsMonitor RuntimeSettings => GetRequiredService>(); + private ITemplateRepository CreateRepository(IScopeProvider provider) => - new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, Mock.Of>()); + new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, RuntimeSettings); [Test] public void Can_Instantiate_Repository() From 08a7a26743576ba585380f19bcd202ff4d2301f1 Mon Sep 17 00:00:00 2001 From: Matt Brailsford Date: Wed, 13 Jul 2022 23:08:44 +0200 Subject: [PATCH 31/32] Allow dashboards to also be the "last accessed item" (#12689) * Allow dashboards to also be the "last accessed item" * Use tripple = --- .../src/common/services/history.service.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/history.service.js b/src/Umbraco.Web.UI.Client/src/common/services/history.service.js index 114d002a4d..d94ff52183 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/history.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/history.service.js @@ -130,7 +130,7 @@ angular.module('umbraco.services') getLastAccessedItemForSection: function (sectionAlias) { for (var i = 0, len = nArray.length; i < len; i++) { var item = nArray[i]; - if (item.link.indexOf(sectionAlias + "/") === 0) { + if (item.link === sectionAlias || item.link.indexOf(sectionAlias + "/") === 0) { return item; } } @@ -138,4 +138,4 @@ angular.module('umbraco.services') return null; } }; -}); \ No newline at end of file +}); From 338bc2c19c2feae9719d3b9da9497da4d8e18a73 Mon Sep 17 00:00:00 2001 From: Nikolaj Geisle <70372949+Zeegaan@users.noreply.github.com> Date: Wed, 20 Jul 2022 09:36:26 +0200 Subject: [PATCH 32/32] Bumb version --- version.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.json b/version.json index 11587874da..f74c6fceb1 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "10.1.0-rc", + "version": "10.1.0-rc2", "assemblyVersion": { "precision": "Build" // optional. Use when you want a more precise assembly version than the default major.minor. },