Merge branch 'v10/dev' into v10/contrib

This commit is contained in:
Sebastiaan Janssen
2022-07-26 10:26:02 +02:00
26 changed files with 1849 additions and 1732 deletions

View File

@@ -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:

View File

@@ -1630,6 +1630,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="tabRules">Editor</key>
</area>
<area alias="template">
<key alias="runtimeModeProduction"><![CDATA[Content is not editable when using runtime mode <code>Production</code>.]]></key>
<key alias="deleteByIdFailed">Failed to delete template with ID %0%</key>
<key alias="edittemplate">Edit template</key>
<key alias="insertSections">Sections</key>

View File

@@ -1683,6 +1683,7 @@ To manage your website, simply open the Umbraco backoffice and start adding cont
<key alias="tabRules">Rich Text Editor</key>
</area>
<area alias="template">
<key alias="runtimeModeProduction"><![CDATA[Content is not editable when using runtime mode <code>Production</code>.]]></key>
<key alias="deleteByIdFailed">Failed to delete template with ID %0%</key>
<key alias="edittemplate">Edit template</key>
<key alias="insertSections">Sections</key>

View File

@@ -1444,6 +1444,7 @@ Echter, Runway biedt een gemakkelijke basis om je snel op weg te helpen. Als je
<key alias="tabRules">Rich Text Editor</key>
</area>
<area alias="template">
<key alias="runtimeModeProduction"><![CDATA[Inhoud kan niet worden bewerkt in de runtime-modus <code>Production</code>.]]></key>
<key alias="deleteByIdFailed">Kan sjabloon met ID %0% niet verwijderen</key>
<key alias="edittemplate">Sjabloon aanpassen</key>
<key alias="insertSections">Secties</key>

View File

@@ -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<MapperCollectionBuilder>();
/// <summary>
/// Gets the NPoco mappers collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static NPocoMapperCollectionBuilder? NPocoMappers(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<NPocoMapperCollectionBuilder>();
@@ -26,4 +31,11 @@ public static partial class UmbracoBuilderExtensions
/// <param name="builder">The builder.</param>
public static PackageMigrationPlanCollectionBuilder? PackageMigrationPlans(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<PackageMigrationPlanCollectionBuilder>();
/// <summary>
/// Gets the runtime mode validators collection builder.
/// </summary>
/// <param name="builder">The builder.</param>
public static RuntimeModeValidatorCollectionBuilder RuntimeModeValidators(this IUmbracoBuilder builder)
=> builder.WithCollectionBuilder<RuntimeModeValidatorCollectionBuilder>();
}

View File

@@ -86,11 +86,12 @@ public static partial class UmbracoBuilderExtensions
// Add runtime mode validation
builder.Services.AddSingleton<IRuntimeModeValidationService, RuntimeModeValidationService>();
builder.Services.AddTransient<IRuntimeModeValidator, JITOptimizerValidator>();
builder.Services.AddTransient<IRuntimeModeValidator, UmbracoApplicationUrlValidator>();
builder.Services.AddTransient<IRuntimeModeValidator, UseHttpsValidator>();
builder.Services.AddTransient<IRuntimeModeValidator, RuntimeMinificationValidator>();
builder.Services.AddTransient<IRuntimeModeValidator, ModelsBuilderModeValidator>();
builder.RuntimeModeValidators()
.Add<JITOptimizerValidator>()
.Add<UmbracoApplicationUrlValidator>()
.Add<UseHttpsValidator>()
.Add<RuntimeMinificationValidator>()
.Add<ModelsBuilderModeValidator>();
// composers
builder

View File

@@ -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<IMedia> medias = _mediaService.GetByIds(definition.MediaUdis);

View File

@@ -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<int, ITemplate>, 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> _runtimeSettings;
public TemplateRepository(IScopeAccessor scopeAccessor, AppCaches cache, ILogger<TemplateRepository> logger,
FileSystems fileSystems, IIOHelper ioHelper, IShortStringHelper shortStringHelper, IViewHelper viewHelper)
public TemplateRepository(
IScopeAccessor scopeAccessor,
AppCaches cache,
ILogger<TemplateRepository> logger,
FileSystems fileSystems,
IIOHelper ioHelper,
IShortStringHelper shortStringHelper,
IViewHelper viewHelper,
IOptionsMonitor<RuntimeSettings> 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<int, ITemplate>, 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<int, ITemplate>, ITempl
IEnumerable<IUmbracoEntity> 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();

View File

@@ -18,11 +18,11 @@ namespace Umbraco.Cms.Infrastructure;
/// <seealso cref="Umbraco.Cms.Core.IPublishedContentQuery" />
public class PublishedContentQuery : IPublishedContentQuery
{
private static readonly HashSet<string> _itemIdFieldNameHashSet = new() {ExamineFieldNames.ItemIdFieldName};
private readonly IExamineManager _examineManager;
private readonly IPublishedSnapshot _publishedSnapshot;
private readonly IVariationContextAccessor _variationContextAccessor;
private static readonly HashSet<string> _returnedQueryFields =
new() { ExamineFieldNames.ItemIdFieldName, ExamineFieldNames.CategoryFieldName };
/// <summary>
/// Initializes a new instance of the <see cref="PublishedContentQuery" /> class.
@@ -293,8 +293,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(_returnedQueryFields);
ISearchResults? results = skip == 0 && take == 0
@@ -328,8 +328,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(_returnedQueryFields);
}
ISearchResults? results = skip == 0 && take == 0

View File

@@ -29,11 +29,18 @@ internal class RuntimeModeValidationService : IRuntimeModeValidationService
var validationMessages = new List<string>();
// Runtime mode validators are registered transient, but this service is registered as singleton
foreach (var runtimeModeValidator in _serviceProvider.GetServices<IRuntimeModeValidator>())
using (var scope = _serviceProvider.CreateScope())
{
if (runtimeModeValidator.Validate(runtimeMode, out var validationMessage) == false)
var runtimeModeValidators = scope.ServiceProvider.GetService<RuntimeModeValidatorCollection>();
if (runtimeModeValidators is not null)
{
validationMessages.Add(validationMessage);
foreach (var runtimeModeValidator in runtimeModeValidators)
{
if (runtimeModeValidator.Validate(runtimeMode, out var validationMessage) == false)
{
validationMessages.Add(validationMessage);
}
}
}
}

View File

@@ -0,0 +1,10 @@
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Infrastructure.Runtime;
public class RuntimeModeValidatorCollection : BuilderCollectionBase<IRuntimeModeValidator>
{
public RuntimeModeValidatorCollection(Func<IEnumerable<IRuntimeModeValidator>> items)
: base(items)
{ }
}

View File

@@ -0,0 +1,11 @@
using Microsoft.Extensions.DependencyInjection;
using Umbraco.Cms.Core.Composing;
namespace Umbraco.Cms.Infrastructure.Runtime;
public class RuntimeModeValidatorCollectionBuilder : SetCollectionBuilderBase<RuntimeModeValidatorCollectionBuilder, RuntimeModeValidatorCollection, IRuntimeModeValidator>
{
protected override ServiceLifetime CollectionLifetime => ServiceLifetime.Transient;
protected override RuntimeModeValidatorCollectionBuilder This => this;
}

View File

@@ -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());

View File

@@ -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;
@@ -47,6 +49,7 @@ public class DictionaryController : BackOfficeNotificationsController
private readonly IHostingEnvironment _hostingEnvironment;
private readonly PackageDataInstallation _packageDataInstallation;
[ActivatorUtilitiesConstructor]
public DictionaryController(
ILogger<DictionaryController> logger,
ILocalizationService localizationService,
@@ -60,8 +63,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 +72,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<DictionaryController> logger,
ILocalizationService localizationService,
IBackOfficeSecurityAccessor backofficeSecurityAccessor,
IOptionsSnapshot<GlobalSettings> globalSettings,
ILocalizedTextService localizedTextService,
IUmbracoMapper umbracoMapper)
: this(
logger,
localizationService,
backofficeSecurityAccessor,
globalSettings,
localizedTextService,
umbracoMapper,
StaticServiceProvider.Instance.GetRequiredService<IEntityXmlSerializer>(),
StaticServiceProvider.Instance.GetRequiredService<IHostingEnvironment>(),
StaticServiceProvider.Instance.GetRequiredService<PackageDataInstallation>())
{
}
/// <summary>
/// Deletes a data type with a given ID
/// </summary>

View File

@@ -180,10 +180,10 @@ public class ContentTypeTreeController : TreeController, ISearchableTree
menu.Items.Add<ActionNew>(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<ActionDelete>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
menu.Items.Add<ActionMove>(LocalizedTextService, hasSeparator: true, opensDialog: true, useLegacyIcon: false);
}
menu.Items.Add<ActionCopy>(LocalizedTextService, opensDialog: true, useLegacyIcon: false);

View File

@@ -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);
})();

View File

@@ -1,81 +1,81 @@
<div data-element="editor-partial-view-macros" ng-controller="Umbraco.Editors.PartialViewMacros.EditController as vm">
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<form name="contentForm"
ng-submit="vm.save()"
novalidate
val-form-manager>
<form name="contentForm"
ng-submit="vm.save()"
novalidate
val-form-manager>
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-header
name="vm.partialViewMacro.name"
hide-alias="true"
description="vm.partialViewMacro.virtualPath"
description-locked="true"
hide-icon="true"
editorfor="vm.header.editorfor"
setpagetitle="vm.header.setPageTitle">
</umb-editor-header>
<umb-editor-header name="vm.partialViewMacro.name"
name-locked="vm.runtimeModeProduction"
hide-alias="true"
description="vm.partialViewMacro.virtualPath"
description-locked="true"
hide-icon="true"
editorfor="vm.header.editorfor"
setpagetitle="vm.header.setPageTitle">
</umb-editor-header>
<umb-editor-container>
<umb-box>
<umb-box-content>
<umb-editor-container>
<umb-box>
<umb-box-content>
<div class="umb-alert umb-alert--info mb3" ng-if="vm.runtimeModeProduction">
<localize key="template_runtimeModeProduction">Content is not editable when using runtime mode <code>Production</code>.</localize>
</div>
<div class="flex" style="margin-bottom: 30px;">
<div class="flex mb3" ng-if="!vm.runtimeModeProduction">
<div class="flex" style="margin-left: auto;">
<div class="flex" style="margin-left: auto;">
<umb-button-group
style="margin-right: 5px;"
default-button="vm.page.insertDefaultButton"
sub-buttons="vm.page.insertSubButtons"
size="xs"
icon="icon-add">
</umb-button-group>
<umb-button-group style="margin-right: 5px;"
default-button="vm.page.insertDefaultButton"
sub-buttons="vm.page.insertSubButtons"
size="xs"
icon="icon-add">
</umb-button-group>
<umb-button
type="button"
size="xs"
action="vm.openQueryBuilderOverlay()"
icon="icon-wand"
label-key="template_queryBuilder"
add-ellipsis="true">
</umb-button>
<umb-button type="button"
size="xs"
action="vm.openQueryBuilderOverlay()"
icon="icon-wand"
label-key="template_queryBuilder"
add-ellipsis="true">
</umb-button>
</div>
</div>
</div>
</div>
<div
auto-scale="85"
umb-ace-editor="vm.aceOption"
model="vm.partialViewMacro.content">
</div>
<div auto-scale="90"
umb-ace-editor="vm.aceOption"
model="vm.partialViewMacro.content">
</div>
</umb-box-content>
</umb-box>
</umb-editor-container>
</umb-box-content>
</umb-box>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-editor-footer-content-right>
<umb-button
type="submit"
button-style="success"
state="vm.page.saveButtonState"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
<umb-button type="submit"
button-style="success"
state="vm.page.saveButtonState"
disabled="vm.runtimeModeProduction"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-footer>
</umb-editor-view>
</form>
</umb-editor-view>
</form>
</div>

View File

@@ -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);
})();

View File

@@ -1,97 +1,98 @@
<div data-element="editor-partial-views" ng-controller="Umbraco.Editors.PartialViews.EditController as vm">
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<form name="contentForm"
ng-submit="vm.save()"
novalidate
val-form-manager>
<form name="contentForm"
ng-submit="vm.save()"
novalidate
val-form-manager>
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-header
name="vm.partialView.name"
hide-alias="true"
description="vm.partialView.virtualPath"
description-locked="true"
hide-icon="true"
editorfor="vm.header.editorfor"
setpagetitle="vm.header.setPageTitle">
</umb-editor-header>
<umb-editor-header name="vm.partialView.name"
name-locked="vm.runtimeModeProduction"
hide-alias="true"
description="vm.partialView.virtualPath"
description-locked="true"
hide-icon="true"
editorfor="vm.header.editorfor"
setpagetitle="vm.header.setPageTitle">
</umb-editor-header>
<umb-editor-container>
<umb-editor-container>
<umb-box>
<umb-box-content>
<umb-box>
<umb-box-content>
<div class="umb-alert umb-alert--info mb3" ng-if="vm.runtimeModeProduction">
<localize key="template_runtimeModeProduction">Content is not editable when using runtime mode <code>Production</code>.</localize>
</div>
<div class="flex" style="margin-bottom: 30px;">
<div class="flex mb3" ng-if="!vm.runtimeModeProduction">
<div class="flex" style="margin-left: auto;">
<div class="flex" style="margin-left: auto;">
<umb-button-group
style="margin-right: 5px;"
default-button="vm.page.insertDefaultButton"
sub-buttons="vm.page.insertSubButtons"
size="xs"
icon="icon-add">
</umb-button-group>
<umb-button-group style="margin-right: 5px;"
default-button="vm.page.insertDefaultButton"
sub-buttons="vm.page.insertSubButtons"
size="xs"
icon="icon-add">
</umb-button-group>
<umb-button
type="button"
size="xs"
action="vm.openQueryBuilderOverlay()"
icon="icon-wand"
label-key="template_queryBuilder"
add-ellipsis="true">
</umb-button>
<umb-button type="button"
size="xs"
action="vm.openQueryBuilderOverlay()"
icon="icon-wand"
label-key="template_queryBuilder"
add-ellipsis="true">
</umb-button>
</div>
</div>
</div>
</div>
<div
auto-scale="85"
umb-ace-editor="vm.aceOption"
model="vm.partialView.content">
</div>
<div auto-scale="90"
umb-ace-editor="vm.aceOption"
model="vm.partialView.content">
</div>
</umb-box-content>
</umb-box>
</umb-editor-container>
</umb-box-content>
</umb-box>
<umb-editor-footer>
</umb-editor-container>
<umb-editor-footer-content-left>
<umb-keyboard-shortcuts-overview
model="vm.page.keyboardShortcutsOverview"
show-overlay="vm.showKeyboardShortcut"
on-close="vm.showKeyboardShortcut = false;">
</umb-keyboard-shortcuts-overview>
</umb-editor-footer-content-left>
<umb-editor-footer>
<umb-editor-footer-content-right>
<umb-editor-footer-content-left>
<umb-keyboard-shortcuts-overview ng-if="!vm.runtimeModeProduction"
model="vm.page.keyboardShortcutsOverview"
show-overlay="vm.showKeyboardShortcut"
on-close="vm.showKeyboardShortcut = false;">
</umb-keyboard-shortcuts-overview>
</umb-editor-footer-content-left>
<umb-button ng-if="model.infiniteMode"
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button type="submit"
button-style="success"
state="vm.page.saveButtonState"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
<umb-editor-footer-content-right>
</umb-editor-footer>
<umb-button ng-if="model.infiniteMode"
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
</umb-editor-view>
</form>
<umb-button type="submit"
button-style="success"
state="vm.page.saveButtonState"
disabled="vm.runtimeModeProduction"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
</div>

View File

@@ -1,144 +1,142 @@
<div data-element="editor-templates" ng-controller="Umbraco.Editors.Templates.EditController as vm">
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<umb-load-indicator ng-if="vm.page.loading"></umb-load-indicator>
<form name="contentForm"
ng-submit="vm.save()"
novalidate
val-form-manager>
<form name="contentForm"
ng-submit="vm.save()"
novalidate
val-form-manager>
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-view ng-if="!vm.page.loading">
<umb-editor-header
name="vm.template.name"
alias="vm.template.alias"
key="vm.template.key"
description="vm.template.virtualPath"
description-locked="true"
hide-icon="false"
editorfor="vm.header.editorfor"
setpagetitle="vm.header.setPageTitle">
</umb-editor-header>
<umb-editor-header name="vm.template.name"
alias="vm.template.alias"
alias-locked="vm.runtimeModeProduction"
key="vm.template.key"
description="vm.template.virtualPath"
description-locked="true"
hide-icon="false"
editorfor="vm.header.editorfor"
setpagetitle="vm.header.setPageTitle">
</umb-editor-header>
<umb-editor-container>
<umb-box>
<umb-box-content>
<umb-editor-container>
<umb-box>
<div class="flex" style="margin-bottom: 30px;">
<umb-box-content>
<div class="umb-alert umb-alert--info mb3" ng-if="vm.runtimeModeProduction">
<localize key="template_runtimeModeProduction">Content is not editable when using runtime mode <code>Production</code>.</localize>
</div>
<div class="flex">
<div class="flex mb3" ng-if="!vm.runtimeModeProduction">
<div ng-class="{'btn-group': vm.template.masterTemplateAlias}" style="margin-right: 10px;">
<div class="flex">
<button type="button"
data-element="button-masterTemplate"
class="btn umb-button__button umb-button--xs umb-outline"
style="font-size: 14px;"
ng-click="vm.openMasterTemplateOverlay()">
<span class="bold"><localize key="template_mastertemplate">Master template</localize>:</span>
<span style="margin-left: 5px;">
<span ng-if="vm.template.masterTemplateAlias">{{ vm.getMasterTemplateName(vm.template.masterTemplateAlias, vm.templates) }}</span>
<span ng-if="!vm.template.masterTemplateAlias"><localize key="template_noMaster">No master</localize></span>
</span>
</button>
<div ng-class="{'btn-group': vm.template.masterTemplateAlias}" style="margin-right: 10px;">
<button type="button"
class="btn umb-button__button umb-button--xs dropdown-toggle umb-button-group__toggle flex-inline items-center"
style="font-size: 14px; height: 100%;"
localize="title"
title="@general_remove"
ng-if="vm.template.masterTemplateAlias"
ng-click="vm.removeMasterTemplate()">
<umb-icon icon="icon-wrong" class="icon"></umb-icon>
</button>
<button type="button"
data-element="button-masterTemplate"
class="btn umb-button__button umb-button--xs umb-outline"
style="font-size: 14px;"
ng-click="vm.openMasterTemplateOverlay()">
<span class="bold"><localize key="template_mastertemplate">Master template</localize>:</span>
<span style="margin-left: 5px;">
<span ng-if="vm.template.masterTemplateAlias">{{ vm.getMasterTemplateName(vm.template.masterTemplateAlias, vm.templates) }}</span>
<span ng-if="!vm.template.masterTemplateAlias"><localize key="template_noMaster">No master</localize></span>
</span>
</button>
</div>
<button type="button"
class="btn umb-button__button umb-button--xs dropdown-toggle umb-button-group__toggle flex-inline items-center"
style="font-size: 14px; height: 100%;"
localize="title"
title="@general_remove"
ng-if="vm.template.masterTemplateAlias"
ng-click="vm.removeMasterTemplate()">
<umb-icon icon="icon-wrong" class="icon"></umb-icon>
</button>
</div>
</div>
<div class="flex" style="margin-left: auto;">
</div>
<umb-button-group
style="margin-right: 5px;"
default-button="vm.page.insertDefaultButton"
sub-buttons="vm.page.insertSubButtons"
size="xs">
</umb-button-group>
<div class="flex" style="margin-left: auto;">
<umb-button
style="margin-right: 5px;"
alias="queryBuilder"
type="button"
size="xs"
action="vm.openQueryBuilderOverlay()"
icon="icon-wand"
label-key="template_queryBuilder"
add-ellipsis="true">
</umb-button>
<umb-button-group style="margin-right: 5px;"
default-button="vm.page.insertDefaultButton"
sub-buttons="vm.page.insertSubButtons"
size="xs">
</umb-button-group>
<umb-button
alias="sections"
type="button"
size="xs"
action="vm.openSectionsOverlay()"
icon="icon-indent"
label-key="template_insertSections"
add-ellipsis="true">
</umb-button>
</div>
</div>
<div
data-element="code-editor"
auto-scale="85"
umb-ace-editor="vm.aceOption"
model="vm.template.content">
</div>
</umb-box-content>
</umb-box>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-left>
<umb-keyboard-shortcuts-overview
model="vm.page.keyboardShortcutsOverview"
show-overlay="vm.showKeyboardShortcut"
on-close="vm.closeShortcuts()">
</umb-keyboard-shortcuts-overview>
</umb-editor-footer-content-left>
<umb-editor-footer-content-right>
<umb-button
ng-if="model.infiniteMode"
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
<umb-button style="margin-right: 5px;"
alias="queryBuilder"
type="button"
size="xs"
action="vm.openQueryBuilderOverlay()"
icon="icon-wand"
label-key="template_queryBuilder"
add-ellipsis="true">
</umb-button>
<umb-button
alias="save"
type="submit"
button-style="success"
state="vm.page.saveButtonState"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
<umb-button alias="sections"
type="button"
size="xs"
action="vm.openSectionsOverlay()"
icon="icon-indent"
label-key="template_insertSections"
add-ellipsis="true">
</umb-button>
</umb-editor-footer-content-right>
</div>
</umb-editor-footer>
</div>
</umb-editor-view>
</form>
<div data-element="code-editor"
auto-scale="90"
umb-ace-editor="vm.aceOption"
model="vm.template.content">
</div>
</umb-box-content>
</umb-box>
</umb-editor-container>
<umb-editor-footer>
<umb-editor-footer-content-left>
<umb-keyboard-shortcuts-overview ng-if="!vm.runtimeModeProduction"
model="vm.page.keyboardShortcutsOverview"
show-overlay="vm.showKeyboardShortcut"
on-close="vm.closeShortcuts()">
</umb-keyboard-shortcuts-overview>
</umb-editor-footer-content-left>
<umb-editor-footer-content-right>
<umb-button ng-if="model.infiniteMode"
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button alias="save"
type="submit"
button-style="success"
state="vm.page.saveButtonState"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
</umb-editor-view>
</form>
</div>

View File

@@ -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() {},

View File

@@ -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;
@@ -77,6 +79,9 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest
var provider = ScopeProvider;
using (var scope = provider.CreateScope())
{
var runtimeSettingsMock = new Mock<IOptionsMonitor<RuntimeSettings>>();
runtimeSettingsMock.Setup(x => x.CurrentValue).Returns(new RuntimeSettings());
var templateRepo = new TemplateRepository(
(IScopeAccessor)provider,
AppCaches.Disabled,
@@ -84,7 +89,8 @@ public class ContentTypeRepositoryTest : UmbracoIntegrationTest
FileSystems,
IOHelper,
ShortStringHelper,
Mock.Of<IViewHelper>());
Mock.Of<IViewHelper>(),
runtimeSettingsMock.Object);
var repository = ContentTypeRepository;
Template[] templates =
{

View File

@@ -111,7 +111,10 @@ public class DocumentRepositoryTest : UmbracoIntegrationTest
{
appCaches ??= AppCaches;
templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<TemplateRepository>(), FileSystems, IOHelper, ShortStringHelper, Mock.Of<IViewHelper>());
var runtimeSettingsMock = new Mock<IOptionsMonitor<RuntimeSettings>>();
runtimeSettingsMock.Setup(x => x.CurrentValue).Returns(new RuntimeSettings());
templateRepository = new TemplateRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<TemplateRepository>(), FileSystems, IOHelper, ShortStringHelper, Mock.Of<IViewHelper>(), runtimeSettingsMock.Object);
var tagRepository = new TagRepository(scopeAccessor, appCaches, LoggerFactory.CreateLogger<TagRepository>());
var commonRepository =
new ContentTypeCommonRepository(scopeAccessor, templateRepository, appCaches, ShortStringHelper);

View File

@@ -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;
@@ -56,10 +57,13 @@ public class TemplateRepositoryTest : UmbracoIntegrationTest
private IHostingEnvironment HostingEnvironment => GetRequiredService<IHostingEnvironment>();
private FileSystems FileSystems => GetRequiredService<FileSystems>();
private IViewHelper ViewHelper => GetRequiredService<IViewHelper>();
private IOptionsMonitor<RuntimeSettings> RuntimeSettings => GetRequiredService<IOptionsMonitor<RuntimeSettings>>();
private ITemplateRepository CreateRepository(IScopeProvider provider) =>
new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger<TemplateRepository>(), FileSystems, IOHelper, ShortStringHelper, ViewHelper);
new TemplateRepository((IScopeAccessor)provider, AppCaches.Disabled, LoggerFactory.CreateLogger<TemplateRepository>(), FileSystems, IOHelper, ShortStringHelper, ViewHelper, RuntimeSettings);
[Test]
public void Can_Instantiate_Repository()

View File

@@ -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.2.0",
"assemblyVersion": {
"precision": "Build" // optional. Use when you want a more precise assembly version than the default major.minor.
},