Merge remote-tracking branch 'origin/v8/dev' into netcore/dev

# Conflicts:
#	src/Umbraco.Infrastructure/Runtime/SqlMainDomLock.cs
#	src/Umbraco.PublishedCache.NuCache/PublishedSnapshotService.cs
#	src/Umbraco.Web.UI.Client/src/views/users/user.controller.js
#	src/Umbraco.Web/Models/Trees/MenuItemList.cs
#	src/Umbraco.Web/Runtime/WebInitialComposer.cs
#	src/Umbraco.Web/Trees/RelationTypeTreeController.cs
#	src/Umbraco.Web/UmbracoApplication.cs
This commit is contained in:
Bjarke Berg
2020-05-29 12:27:00 +02:00
64 changed files with 874 additions and 252 deletions
+17 -3
View File
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Umbraco.Core.Composing;
@@ -77,15 +78,28 @@ namespace Umbraco.Core
var ctorParameters = ctor.GetParameters();
var ctorArgs = new object[ctorParameters.Length];
var availableArgs = new List<object>(args);
var i = 0;
foreach (var parameter in ctorParameters)
{
// no! IsInstanceOfType is not ok here
// ReSharper disable once UseMethodIsInstanceOfType
var arg = args?.FirstOrDefault(a => parameter.ParameterType.IsAssignableFrom(a.GetType()));
ctorArgs[i++] = arg ?? factory.GetInstance(parameter.ParameterType);
var idx = availableArgs.FindIndex(a => parameter.ParameterType.IsAssignableFrom(a.GetType()));
if(idx >= 0)
{
// Found a suitable supplied argument
ctorArgs[i++] = availableArgs[idx];
// A supplied argument can be used at most once
availableArgs.RemoveAt(idx);
}
else
{
// None of the provided arguments is suitable: get an instance from the factory
ctorArgs[i++] = factory.GetInstance(parameter.ParameterType);
}
}
return ctor.Invoke(ctorArgs);
}
}
}
}
@@ -16,6 +16,11 @@
/// </summary>
public string Culture { get; set; }
/// <summary>
/// When dealing with content variants, this is the segment for the variant
/// </summary>
public string Segment { get; set; }
/// <summary>
/// An array of metadata that is parsed out from the file info posted to the server which is set on the client.
/// </summary>
@@ -0,0 +1,12 @@
namespace Umbraco.Core.Models
{
public static class RelationTypeExtensions
{
public static bool IsSystemRelationType(this IRelationType relationType) =>
relationType.Alias == Constants.Conventions.RelationTypes.RelatedDocumentAlias
|| relationType.Alias == Constants.Conventions.RelationTypes.RelatedMediaAlias
|| relationType.Alias == Constants.Conventions.RelationTypes.RelateDocumentOnCopyAlias
|| relationType.Alias == Constants.Conventions.RelationTypes.RelateParentDocumentOnDeleteAlias
|| relationType.Alias == Constants.Conventions.RelationTypes.RelateParentMediaFolderOnDeleteAlias;
}
}
+8 -1
View File
@@ -5,6 +5,7 @@ using System.Collections.Generic;
using Umbraco.Core;
using Umbraco.Core.Services;
using Umbraco.Web.Actions;
using System.Threading;
namespace Umbraco.Web.Models.Trees
{
@@ -28,12 +29,15 @@ namespace Umbraco.Web.Models.Trees
Name = name;
}
public MenuItem(string alias, ILocalizedTextService textService)
: this()
{
var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture);
values.TryGetValue($"visuallyHiddenTexts/{alias}_description", out var textDescription);
Alias = alias;
Name = textService.Localize($"actions/{Alias}");
TextDescription = textDescription;
}
/// <summary>
@@ -74,6 +78,9 @@ namespace Umbraco.Web.Models.Trees
[Required]
public string Alias { get; set; }
[DataMember(Name = "textDescription")]
public string TextDescription { get; set; }
/// <summary>
/// Ensures a menu separator will exist before this menu item
/// </summary>
+6 -2
View File
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Threading;
using Umbraco.Core.Services;
using Umbraco.Web.Actions;
@@ -50,14 +51,17 @@ namespace Umbraco.Web.Models.Trees
var item = _actionCollection.GetAction<T>();
if (item == null) return null;
var values = textService.GetAllStoredValues(Thread.CurrentThread.CurrentUICulture);
values.TryGetValue($"visuallyHiddenTexts/{item.Alias}Description", out var textDescription);
var menuItem = new MenuItem(item, textService.Localize($"actions/{item.Alias}"))
{
SeparatorBefore = hasSeparator,
OpensDialog = opensDialog
OpensDialog = opensDialog,
TextDescription = textDescription,
};
return menuItem;
}
}
}
@@ -13,6 +13,9 @@ namespace Umbraco.Web.Models.ContentEditing
Notifications = new List<BackOfficeNotification>();
}
[DataMember(Name = "isSystemRelationType")]
public bool IsSystemRelationType { get; set; }
/// <summary>
/// Gets or sets a boolean indicating whether the RelationType is Bidirectional (true) or Parent to Child (false)
/// </summary>
@@ -39,6 +39,8 @@ namespace Umbraco.Web.Models.Mapping
target.Udi = Udi.Create(Constants.UdiEntityType.RelationType, source.Key);
target.Path = "-1," + source.Id;
target.IsSystemRelationType = source.IsSystemRelationType();
// Set the "friendly" and entity names for the parent and child object types
if (source.ParentObjectType.HasValue)
{
@@ -3,9 +3,12 @@ using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using Umbraco.Core.Configuration;
using System.Web;
using Umbraco.Core.Hosting;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
@@ -17,9 +20,10 @@ namespace Umbraco.Core.Runtime
public class SqlMainDomLock : IMainDomLock
{
private string _lockId;
private const string MainDomKey = "Umbraco.Core.Runtime.SqlMainDom";
private const string MainDomKeyPrefix = "Umbraco.Core.Runtime.SqlMainDom";
private const string UpdatedSuffix = "_updated";
private readonly ILogger _logger;
private readonly IHostingEnvironment _hostingEnvironment;
private IUmbracoDatabase _db;
private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
private SqlServerSyntaxProvider _sqlServerSyntax = new SqlServerSyntaxProvider();
@@ -28,17 +32,20 @@ namespace Umbraco.Core.Runtime
private bool _hasError;
private object _locker = new object();
public SqlMainDomLock(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator)
public SqlMainDomLock(ILogger logger, IGlobalSettings globalSettings, IConnectionStrings connectionStrings, IDbProviderFactoryCreator dbProviderFactoryCreator, IHostingEnvironment hostingEnvironment)
{
// unique id for our appdomain, this is more unique than the appdomain id which is just an INT counter to its safer
_lockId = Guid.NewGuid().ToString();
_logger = logger;
_hostingEnvironment = hostingEnvironment;
_dbFactory = new UmbracoDatabaseFactory(_logger,
globalSettings,
connectionStrings,
Constants.System.UmbracoConnectionName,
new Lazy<IMapperCollection>(() => new MapperCollection(Enumerable.Empty<BaseMapper>())),
dbProviderFactoryCreator);
MainDomKey = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId(_hostingEnvironment)).GenerateHash<SHA1>();
}
public async Task<bool> AcquireLockAsync(int millisecondsTimeout)
@@ -128,6 +135,16 @@ namespace Umbraco.Core.Runtime
}
/// <summary>
/// Returns the keyvalue table key for the current server/app
/// </summary>
/// <remarks>
/// The key is the the normal MainDomId which takes into account the AppDomainAppId and the physical file path of the app and this is
/// combined with the current machine name. The machine name is required because the default semaphore lock is machine wide so it implicitly
/// takes into account machine name whereas this needs to be explicitly per machine.
/// </remarks>
private string MainDomKey { get; }
private void ListeningLoop()
{
while (true)
@@ -900,11 +900,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
// we ran this on a background thread then those cache refreshers are going to not get 'live' data when they query the content cache which
// they require.
// These cannot currently be run side by side in parallel, due to the monitors need to be exits my the same thread that enter them.
// These can be run side by side in parallel.
using (_contentStore.GetScopedWriteLock(_scopeProvider))
{
NotifyLocked(new[] { new ContentCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _, out _);
}
using (_mediaStore.GetScopedWriteLock(_scopeProvider))
{
NotifyLocked(new[] { new MediaCacheRefresher.JsonPayload(0, null, TreeChangeTypes.RefreshAll) }, out _);
@@ -6,5 +6,6 @@
"username": "<insert username/email in cypress.env.json>",
"password": "<insert password in cypress.env.json>"
},
"supportFile": "cypress/support/index.ts"
"supportFile": "cypress/support/index.ts",
"videoUploadOnPasses" : false
}
@@ -6,7 +6,7 @@ context('Languages', () => {
});
it('Add language', () => {
const name = "Neddersasssch (Nedderlannen)"; // Must be an option in the select box
const name = "Kyrgyz (Kyrgyzstan)"; // Must be an option in the select box
cy.umbracoEnsureLanguageNameNotExists(name);
@@ -1,4 +1,6 @@
/// <reference types="Cypress" />
import {DocumentTypeBuilder, TemplateBuilder} from "umbraco-cypress-testhelpers";
context('Templates', () => {
beforeEach(() => {
@@ -6,28 +8,50 @@ context('Templates', () => {
});
it('Create template', () => {
const name = "Test template";
const name = "Test template";
cy.umbracoEnsureTemplateNameNotExists(name);
cy.umbracoEnsureTemplateNameNotExists(name);
cy.umbracoSection('settings');
cy.get('li .umb-tree-root:contains("Settings")').should("be.visible");
cy.umbracoSection('settings');
cy.get('li .umb-tree-root:contains("Settings")').should("be.visible");
cy.umbracoTreeItem("settings", ["Templates"]).rightclick();
cy.umbracoTreeItem("settings", ["Templates"]).rightclick();
cy.umbracoContextMenuAction("action-create").click();
cy.umbracoContextMenuAction("action-create").click();
//Type name
cy.umbracoEditorHeaderName(name);
//Type name
cy.umbracoEditorHeaderName(name);
//Save
cy.get('.btn-success').click();
//Save
cy.get("form[name='contentForm']").submit();
//Assert
cy.umbracoSuccessNotification().should('be.visible');
//Assert
cy.umbracoSuccessNotification().should('be.visible');
//Clean up
cy.umbracoEnsureTemplateNameNotExists(name);
//Clean up
cy.umbracoEnsureTemplateNameNotExists(name);
});
it('Delete template', () => {
const name = "Test template";
cy.umbracoEnsureTemplateNameNotExists(name);
const template = new TemplateBuilder()
.withName(name)
.build();
cy.saveTemplate(template);
cy.umbracoSection('settings');
cy.get('li .umb-tree-root:contains("Settings")').should("be.visible");
cy.umbracoTreeItem("settings", ["Templates", name]).rightclick();
cy.umbracoContextMenuAction("action-delete").click();
cy.umbracoButtonByLabelKey("general_ok").click();
cy.contains(name).should('not.exist');
cy.umbracoEnsureTemplateNameNotExists(name);
});
});
@@ -6,8 +6,8 @@
"devDependencies": {
"cross-env": "^7.0.2",
"ncp": "^2.0.0",
"cypress": "^4.5.0",
"umbraco-cypress-testhelpers": "1.0.0-beta-38"
"cypress": "^4.6.0",
"umbraco-cypress-testhelpers": "1.0.0-beta-39"
},
"dependencies": {
"typescript": "^3.9.2"
@@ -333,6 +333,24 @@ namespace Umbraco.Tests.Composing
Assert.AreSame(s1, s2);
}
[Test]
public void CanRegisterMultipleSameTypeParametersWithCreateInstance()
{
var register = GetRegister();
register.Register<Thing4>(c =>
{
const string param1 = "param1";
const string param2 = "param2";
return c.CreateInstance<Thing4>(param1, param2);
});
var factory = register.CreateFactory();
var instance = factory.GetInstance<Thing4>();
Assert.AreNotEqual(instance.Thing, instance.AnotherThing);
}
public interface IThing { }
public abstract class ThingBase : IThing { }
@@ -353,5 +371,17 @@ namespace Umbraco.Tests.Composing
public IEnumerable<ThingBase> Things { get; }
}
public class Thing4 : ThingBase
{
public readonly string Thing;
public readonly string AnotherThing;
public Thing4(string thing, string anotherThing)
{
Thing = thing;
AnotherThing = anotherThing;
}
}
}
}
@@ -176,7 +176,7 @@ namespace Umbraco.Web.Common.Extensions
var connStrings = configs.ConnectionStrings();
var appSettingMainDomLock = globalSettings.MainDomLock;
var mainDomLock = appSettingMainDomLock == "SqlMainDomLock"
? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connStrings, dbProviderFactoryCreator)
? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connStrings, dbProviderFactoryCreator, hostingEnvironment)
: new MainDomSemaphoreLock(logger, hostingEnvironment);
var mainDom = new MainDom(logger, mainDomLock);
@@ -0,0 +1,261 @@
tinymce.addI18n('en_US',{
"Redo": "Redo",
"Undo": "Undo",
"Cut": "Cut",
"Copy": "Copy",
"Paste": "Paste",
"Select all": "Select all",
"New document": "New document",
"Ok": "Ok",
"Cancel": "Cancel",
"Visual aids": "Visual aids",
"Bold": "Bold",
"Italic": "Italic",
"Underline": "Underline",
"Strikethrough": "Strikethrough",
"Superscript": "Superscript",
"Subscript": "Subscript",
"Clear formatting": "Clear formatting",
"Align left": "Align left",
"Align center": "Align center",
"Align right": "Align right",
"Justify": "Justify",
"Bullet list": "Bullet list",
"Numbered list": "Numbered list",
"Decrease indent": "Decrease indent",
"Increase indent": "Increase indent",
"Close": "Close",
"Formats": "Formats",
"Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.": "Your browser doesn't support direct access to the clipboard. Please use the Ctrl+X\/C\/V keyboard shortcuts instead.",
"Headers": "Headers",
"Header 1": "Header 1",
"Header 2": "Header 2",
"Header 3": "Header 3",
"Header 4": "Header 4",
"Header 5": "Header 5",
"Header 6": "Header 6",
"Headings": "Headings",
"Heading 1": "Heading 1",
"Heading 2": "Heading 2",
"Heading 3": "Heading 3",
"Heading 4": "Heading 4",
"Heading 5": "Heading 5",
"Heading 6": "Heading 6",
"Preformatted": "Preformatted",
"Div": "Div",
"Pre": "Pre",
"Code": "Code",
"Paragraph": "Paragraph",
"Blockquote": "Blockquote",
"Inline": "Inline",
"Blocks": "Blocks",
"Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.": "Paste is now in plain text mode. Contents will now be pasted as plain text until you toggle this option off.",
"Font Family": "Font Family",
"Font Sizes": "Font Sizes",
"Class": "Class",
"Browse for an image": "Browse for an image",
"OR": "OR",
"Drop an image here": "Drop an image here",
"Upload": "Upload",
"Block": "Blocks",
"Align": "Align",
"Default": "Default",
"Circle": "Circle",
"Disc": "Disc",
"Square": "Square",
"Lower Alpha": "Lower Alpha",
"Lower Greek": "Lower Greek",
"Lower Roman": "Lower Roman",
"Upper Alpha": "Upper Alpha",
"Upper Roman": "Upper Roman",
"Anchor": "Anchor",
"Name": "Name",
"Id": "ID",
"Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.": "ID should start with a letter, followed only by letters, numbers, dashes, dots, colons, or underscores.",
"You have unsaved changes are you sure you want to navigate away?": "You have unsaved changes are you sure you want to navigate away?",
"Restore last draft": "Restore last draft",
"Special character": "Special character",
"Source code": "Source code",
"Insert\/Edit code sample": "Insert\/Edit code sample",
"Language": "Language",
"Code sample": "Code sample",
"Color": "color",
"R": "R",
"G": "G",
"B": "B",
"Left to right": "Left to right",
"Right to left": "Right to left",
"Emoticons": "Emoticons",
"Document properties": "Document properties",
"Title": "Title",
"Keywords": "Keywords",
"Description": "Description",
"Robots": "Robots",
"Author": "Author",
"Encoding": "Encoding",
"Fullscreen": "Fullscreen",
"Action": "Action",
"Shortcut": "Shortcut",
"Help": "Help",
"Address": "Address",
"Focus to menubar": "Focus to menubar",
"Focus to toolbar": "Focus to toolbar",
"Focus to element path": "Focus to element path",
"Focus to contextual toolbar": "Focus to contextual toolbar",
"Insert link (if link plugin activated)": "Insert link (if link plugin activated)",
"Save (if save plugin activated)": "Save (if save plugin activated)",
"Find (if searchreplace plugin activated)": "Find (if searchreplace plugin activated)",
"Plugins installed ({0}):": "Plugins installed ({0}):",
"Premium plugins:": "Premium plugins:",
"Learn more...": "Learn more...",
"You are using {0}": "You are using {0}",
"Plugins": "Plugins",
"Handy Shortcuts": "Handy Shortcuts",
"Horizontal line": "Horizontal line",
"Insert\/edit image": "Insert\/edit image",
"Image description": "Image description",
"Source": "Source",
"Dimensions": "Dimensions",
"Constrain proportions": "Constrain proportions",
"General": "General",
"Advanced": "Advanced",
"Style": "Style",
"Vertical space": "Vertical space",
"Horizontal space": "Horizontal space",
"Border": "Border",
"Insert image": "Insert image",
"Image": "Image",
"Image list": "Image list",
"Rotate counterclockwise": "Rotate counterclockwise",
"Rotate clockwise": "Rotate clockwise",
"Flip vertically": "Flip vertically",
"Flip horizontally": "Flip horizontally",
"Edit image": "Edit image",
"Image options": "Image options",
"Zoom in": "Zoom in",
"Zoom out": "Zoom out",
"Crop": "Crop",
"Resize": "Resize",
"Orientation": "Orientation",
"Brightness": "Brightness",
"Sharpen": "Sharpen",
"Contrast": "Contrast",
"Color levels": "color levels",
"Gamma": "Gamma",
"Invert": "Invert",
"Apply": "Apply",
"Back": "Back",
"Insert date\/time": "Insert date\/time",
"Date\/time": "Date\/time",
"Insert link": "Insert link",
"Insert\/edit link": "Insert\/edit link",
"Text to display": "Text to display",
"Url": "Url",
"Target": "Target",
"None": "None",
"New window": "New window",
"Remove link": "Remove link",
"Anchors": "Anchors",
"Link": "Link",
"Paste or type a link": "Paste or type a link",
"The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?": "The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?",
"The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?": "The URL you entered seems to be an external link. Do you want to add the required http:\/\/ prefix?",
"Link list": "Link list",
"Insert video": "Insert video",
"Insert\/edit video": "Insert\/edit video",
"Insert\/edit media": "Insert\/edit media",
"Alternative source": "Alternative source",
"Poster": "Poster",
"Paste your embed code below:": "Paste your embed code below:",
"Embed": "Embed",
"Media": "Media",
"Nonbreaking space": "Nonbreaking space",
"Page break": "Page break",
"Paste as text": "Paste as text",
"Preview": "Preview",
"Print": "Print",
"Save": "Save",
"Find": "Find",
"Replace with": "Replace with",
"Replace": "Replace",
"Replace all": "Replace all",
"Prev": "Prev",
"Next": "Next",
"Find and replace": "Find and replace",
"Could not find the specified string.": "Could not find the specified string.",
"Match case": "Match case",
"Whole words": "Whole words",
"Spellcheck": "Spellcheck",
"Ignore": "Ignore",
"Ignore all": "Ignore all",
"Finish": "Finish",
"Add to Dictionary": "Add to Dictionary",
"Insert table": "Insert table",
"Table properties": "Table properties",
"Delete table": "Delete table",
"Cell": "Cell",
"Row": "Row",
"Column": "Column",
"Cell properties": "Cell properties",
"Merge cells": "Merge cells",
"Split cell": "Split cell",
"Insert row before": "Insert row before",
"Insert row after": "Insert row after",
"Delete row": "Delete row",
"Row properties": "Row properties",
"Cut row": "Cut row",
"Copy row": "Copy row",
"Paste row before": "Paste row before",
"Paste row after": "Paste row after",
"Insert column before": "Insert column before",
"Insert column after": "Insert column after",
"Delete column": "Delete column",
"Cols": "Cols",
"Rows": "Rows",
"Width": "Width",
"Height": "Height",
"Cell spacing": "Cell spacing",
"Cell padding": "Cell padding",
"Caption": "Caption",
"Left": "Left",
"Center": "Center",
"Right": "Right",
"Cell type": "Cell type",
"Scope": "Scope",
"Alignment": "Alignment",
"H Align": "H Align",
"V Align": "V Align",
"Top": "Top",
"Middle": "Middle",
"Bottom": "Bottom",
"Header cell": "Header cell",
"Row group": "Row group",
"Column group": "Column group",
"Row type": "Row type",
"Header": "Header",
"Body": "Body",
"Footer": "Footer",
"Border color": "Border color",
"Insert template": "Insert template",
"Templates": "Templates",
"Template": "Template",
"Text color": "Text color",
"Background color": "Background color",
"Custom...": "Custom...",
"Custom color": "Custom color",
"No color": "No color",
"Table of Contents": "Table of Contents",
"Show blocks": "Show blocks",
"Show invisible characters": "Show invisible characters",
"Words: {0}": "Words: {0}",
"{0} words": "{0} words",
"File": "File",
"Edit": "Edit",
"Insert": "Insert",
"View": "View",
"Format": "Format",
"Table": "Table",
"Tools": "Tools",
"Powered by {0}": "Powered by {0}",
"Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help": "Rich Text Area. Press ALT-F9 for menu. Press ALT-F10 for toolbar. Press ALT-0 for help"
});
+1 -1
View File
@@ -42,7 +42,7 @@
"npm": "6.13.6",
"signalr": "2.4.0",
"spectrum-colorpicker": "1.8.0",
"tinymce": "4.9.9",
"tinymce": "4.9.10",
"typeahead.js": "0.11.1",
"underscore": "1.9.1"
},
@@ -63,10 +63,14 @@
vm.labels = {};
localizationService.localizeMany([
vm.usernameIsEmail ? "general_email" : "general_username",
vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint"]
vm.usernameIsEmail ? "placeholders_email" : "placeholders_usernameHint",
vm.usernameIsEmail ? "placeholders_emptyEmail" : "placeholders_emptyUsername",
"placeholders_emptyPassword"]
).then(function (data) {
vm.labels.usernameLabel = data[0];
vm.labels.usernamePlaceholder = data[1];
vm.labels.usernameError = data[2];
vm.labels.passwordError = data[3];
});
vm.twoFactor = {};
@@ -193,70 +197,70 @@
}
function loginSubmit() {
// make sure that we are returning to the login view.
vm.view = "login";
// TODO: Do validation properly like in the invite password update
if (formHelper.submitForm({ scope: $scope })) {
//if the login and password are not empty we need to automatically
// validate them - this is because if there are validation errors on the server
// then the user has to change both username & password to resubmit which isn't ideal,
// so if they're not empty, we'll just make sure to set them to valid.
if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
if (vm.loginForm.$invalid) {
SetTitle();
return;
}
// make sure that we are returning to the login view.
vm.view = "login";
//if the login and password are not empty we need to automatically
// validate them - this is because if there are validation errors on the server
// then the user has to change both username & password to resubmit which isn't ideal,
// so if they're not empty, we'll just make sure to set them to valid.
if (vm.login && vm.password && vm.login.length > 0 && vm.password.length > 0) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
vm.loginStates.submitButton = "busy";
if (vm.loginForm.$invalid) {
return;
}
userService.authenticate(vm.login, vm.password)
.then(function(data) {
vm.loginStates.submitButton = "success";
userService._retryRequestQueue(true);
if (vm.onLogin) {
vm.onLogin();
}
},
function(reason) {
vm.loginStates.submitButton = "busy";
//is Two Factor required?
if (reason.status === 402) {
vm.errorMsg = "Additional authentication required";
show2FALoginDialog(reason.data.twoFactorView);
} else {
vm.loginStates.submitButton = "error";
vm.errorMsg = reason.errorMsg;
userService.authenticate(vm.login, vm.password)
.then(function (data) {
vm.loginStates.submitButton = "success";
userService._retryRequestQueue(true);
if(vm.onLogin) {
vm.onLogin();
//set the form inputs to invalid
vm.loginForm.username.$setValidity("auth", false);
vm.loginForm.password.$setValidity("auth", false);
}
userService._retryRequestQueue();
});
//setup a watch for both of the model values changing, if they change
// while the form is invalid, then revalidate them so that the form can
// be submitted again.
vm.loginForm.username.$viewChangeListeners.push(function() {
if (vm.loginForm.$invalid) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
},
function (reason) {
//is Two Factor required?
if (reason.status === 402) {
vm.errorMsg = "Additional authentication required";
show2FALoginDialog(reason.data.twoFactorView);
}
else {
vm.loginStates.submitButton = "error";
vm.errorMsg = reason.errorMsg;
//set the form inputs to invalid
vm.loginForm.username.$setValidity("auth", false);
vm.loginForm.password.$setValidity("auth", false);
}
userService._retryRequestQueue();
});
//setup a watch for both of the model values changing, if they change
// while the form is invalid, then revalidate them so that the form can
// be submitted again.
vm.loginForm.username.$viewChangeListeners.push(function () {
if (vm.loginForm.$invalid) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
});
vm.loginForm.password.$viewChangeListeners.push(function () {
if (vm.loginForm.$invalid) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
});
vm.loginForm.password.$viewChangeListeners.push(function() {
if (vm.loginForm.$invalid) {
vm.loginForm.username.$setValidity('auth', true);
vm.loginForm.password.$setValidity('auth', true);
}
});
}
}
function requestPasswordResetSubmit(email) {
@@ -206,7 +206,7 @@ Use this directive to construct a header inside the main editor window.
(function () {
'use strict';
function EditorHeaderDirective(editorService, localizationService, editorState) {
function EditorHeaderDirective(editorService, localizationService, editorState, $rootScope) {
function link(scope, $injector) {
@@ -224,27 +224,9 @@ Use this directive to construct a header inside the main editor window.
if (editorState.current) {
//to do make work for user create/edit
// to do make it work for user group create/ edit
// to do make it work for language edit/create
// to do make it work for log viewer
scope.isNew = editorState.current.id === 0 ||
editorState.current.id === "0" ||
editorState.current.id === -1 ||
editorState.current.id === 0 ||
editorState.current.id === "-1";
var localizeVars = [
scope.isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit",
"visuallyHiddenTexts_name",
scope.isNew ? "general_new" : "general_edit"
];
if (scope.editorfor) {
localizeVars.push(scope.editorfor);
}
localizationService.localizeMany(localizeVars).then(function(data) {
setAccessibilityForEditor(data);
scope.loading = false;
});
// to make it work for language edit/create
setAccessibilityForEditorState();
scope.loading = false;
} else {
scope.loading = false;
}
@@ -283,59 +265,91 @@ Use this directive to construct a header inside the main editor window.
editorService.iconPicker(iconPicker);
};
function setAccessibilityForEditor(data) {
if (editorState.current) {
if (scope.nameLocked) {
scope.accessibility.a11yName = scope.name;
SetPageTitle(scope.name);
} else {
scope.accessibility.a11yMessage = data[0];
scope.accessibility.a11yName = data[1];
var title = data[2] + ":";
if (!scope.isNew) {
scope.accessibility.a11yMessage += " " + scope.name;
title += " " + scope.name;
} else {
var name = "";
if (editorState.current.contentTypeName) {
name = editorState.current.contentTypeName;
} else if (scope.editorfor) {
name = data[3];
}
if (name !== "") {
scope.accessibility.a11yMessage += " " + name;
scope.accessibility.a11yName = name + " " + scope.accessibility.a11yName;
title += " " + name;
}
}
if (title !== data[2] + ":") {
SetPageTitle(title);
}
}
scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage);
scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName);
function setAccessibilityForEditorState() {
var isNew = editorState.current.id === 0 ||
editorState.current.id === "0" ||
editorState.current.id === -1 ||
editorState.current.id === 0 ||
editorState.current.id === "-1";
var contentTypeName = "";
if (editorState.current.contentTypeName) {
contentTypeName = editorState.current.contentTypeName;
}
var setTitle = false;
if (scope.setpagetitle !== undefined) {
setTitle = scope.setpagetitle;
}
setAccessibilityHeaderDirective(isNew, scope.editorfor, scope.nameLocked, scope.name, contentTypeName, setTitle);
}
function setAccessibilityHeaderDirective(isNew, editorFor, nameLocked, entityName, contentTypeName, setTitle) {
var localizeVars = [
isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit",
"visuallyHiddenTexts_name",
isNew ? "general_new" : "general_edit"
];
if (editorFor) {
localizeVars.push(editorFor);
}
localizationService.localizeMany(localizeVars).then(function(data) {
if (nameLocked) {
scope.accessibility.a11yName = entityName;
if (setTitle) {
SetPageTitle(entityName);
}
} else {
scope.accessibility.a11yMessage = data[0];
scope.accessibility.a11yName = data[1];
var title = data[2] + ":";
if (!isNew) {
scope.accessibility.a11yMessage += " " + entityName;
title += " " + entityName;
} else {
var name = "";
if (contentTypeName) {
name = editorState.current.contentTypeName;
} else if (editorFor) {
name = data[3];
}
if (name !== "") {
scope.accessibility.a11yMessage += " " + name;
scope.accessibility.a11yName = name + " " + scope.accessibility.a11yName;
title += " " + name;
}
}
if (setTitle && title !== data[2] + ":") {
SetPageTitle(title);
}
}
scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage);
scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName);
});
}
function isEmptyOrSpaces(str) {
return str === null || str===undefined || str.trim ==='';
}
function SetPageTitle(title) {
var setTitle = false;
if (scope.setpagetitle !== undefined) {
setTitle = scope.setpagetitle;
}
if (setTitle) {
scope.$emit("$changeTitle", title);
}
}
$rootScope.$on('$setAccessibleHeader', function (event, isNew, editorFor, nameLocked, name, contentTypeName, setTitle) {
setAccessibilityHeaderDirective(isNew, editorFor, nameLocked, name, contentTypeName, setTitle);
});
}
var directive = {
transclude: true,
restrict: 'E',
@@ -32,6 +32,7 @@
@param {boolean} required Set the checkbox to be required.
@param {callback} onChange Callback when the value of the checkbox change by interaction.
@param {string} cssClass Set a css class modifier
@param {boolean} disableDirtyCheck Disable checking if the model is dirty
**/
@@ -84,7 +85,8 @@
required: "<",
onChange: "&?",
cssClass: "@?",
iconClass: "@?"
iconClass: "@?",
disableDirtyCheck: "=?"
}
};
@@ -16,10 +16,15 @@ angular.module("umbraco.directives")
replace: true,
templateUrl: 'views/components/property/umb-property.html',
link: function (scope) {
userService.getCurrentUser().then(function (u) {
var isAdmin = u.userGroups.indexOf('admin') !== -1;
scope.propertyAlias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.property.alias : null;
});
scope.controlLabelTitle = null;
if(Umbraco.Sys.ServerVariables.isDebuggingEnabled) {
userService.getCurrentUser().then(function (u) {
if(u.allowedSections.indexOf("settings") !== -1 ? true : false) {
scope.controlLabelTitle = scope.property.alias;
}
});
}
},
//Define a controller for this directive to expose APIs to other directives
controller: function ($scope) {
@@ -102,10 +102,15 @@
if (!scope.editLabelKey) {
scope.editLabelKey = "general_edit";
}
userService.getCurrentUser().then(function (u) {
var isAdmin = u.userGroups.indexOf('admin') !== -1;
scope.alias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.alias : null;
});
scope.nodeNameTitle = null;
if(Umbraco.Sys.ServerVariables.isDebuggingEnabled) {
userService.getCurrentUser().then(function (u) {
if (u.allowedSections.indexOf("settings") !== -1 ? true : false) {
scope.nodeNameTitle = scope.alias;
}
});
}
}
var directive = {
@@ -26,6 +26,7 @@
fileManager.setFiles({
propertyAlias: vm.propertyAlias,
culture: vm.culture,
segment: vm.segment,
files: []
});
//clear the current files
@@ -92,6 +93,11 @@
vm.culture = null;
}
//normalize segment to null if it's not there
if (!vm.segment) {
vm.segment = null;
}
// TODO: need to figure out what we can do for things like Nested Content
var existingClientFiles = checkPendingClientFiles();
@@ -134,11 +140,16 @@
vm.culture = null;
}
//normalize segment to null if it's not there
if (!vm.segment) {
vm.segment = null;
}
//check the file manager to see if there's already local files pending for this editor
var existingClientFiles = _.map(
_.filter(fileManager.getFiles(),
function (f) {
return f.alias === vm.propertyAlias && f.culture === vm.culture;
return f.alias === vm.propertyAlias && f.culture === vm.culture && f.segment === vm.segment;
}),
function (f) {
return f.file;
@@ -264,7 +275,8 @@
fileManager.setFiles({
propertyAlias: vm.propertyAlias,
files: args.files,
culture: vm.culture
culture: vm.culture,
segment: vm.segment
});
updateModelFromSelectedFiles(args.files).then(function(newVal) {
@@ -287,6 +299,7 @@
templateUrl: 'views/components/upload/umb-property-file-upload.html',
bindings: {
culture: "@?",
segment: "@?",
propertyAlias: "@",
value: "<",
hideSelection: "<",
@@ -39,18 +39,22 @@ function fileManager($rootScope) {
args.culture = null;
}
if (!args.segment) {
args.segment = null;
}
var metaData = [];
if (Utilities.isArray(args.metaData)) {
metaData = args.metaData;
}
//this will clear the files for the current property/culture and then add the new ones for the current property
//this will clear the files for the current property/culture/segment and then add the new ones for the current property
fileCollection = _.reject(fileCollection, function (item) {
return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture);
return item.alias === args.propertyAlias && (!args.culture || args.culture === item.culture) && (!args.segment || args.segment === item.segment);
});
for (var i = 0; i < args.files.length; i++) {
//save the file object to the files collection
fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, metaData: metaData });
fileCollection.push({ alias: args.propertyAlias, file: args.files[i], culture: args.culture, segment: args.segment, metaData: metaData });
}
},
@@ -252,12 +252,13 @@ function umbRequestHelper($http, $q, notificationsService, eventsService, formHe
for (var f in args.files) {
//each item has a property alias and the file object, we'll ensure that the alias is suffixed to the key
// so we know which property it belongs to on the server side
var fileKey = "file_" + args.files[f].alias + "_" + (args.files[f].culture ? args.files[f].culture : "");
var file = args.files[f];
var fileKey = "file_" + file.alias + "_" + (file.culture ? file.culture : "") + "_" + (file.segment ? file.segment : "");
if (Utilities.isArray(args.files[f].metaData) && args.files[f].metaData.length > 0) {
fileKey += ("_" + args.files[f].metaData.join("_"));
if (Utilities.isArray(file.metaData) && file.metaData.length > 0) {
fileKey += ("_" + file.metaData.join("_"));
}
formData.append(fileKey, args.files[f].file);
formData.append(fileKey, file.file);
}
}).then(function (response) {
//success callback
@@ -227,6 +227,7 @@
@import "dashboards/umbraco-forms.less";
@import "dashboards/examine-management.less";
@import "dashboards/healthcheck.less";
@import "dashboards/content-templates.less";
@import "dashboards/nucache.less";
@import "typeahead.less";
@@ -18,6 +18,8 @@ label.umb-form-check--checkbox{
}
.umb-form-check__info {
margin-left:20px;
position: relative;
top: 3px;
}
@@ -0,0 +1,22 @@
.content-templates-dashboard{
p{
line-height: 1.6em;
margin-bottom: 30px;
&:last-child{
margin-bottom: 0;
}
}
ul{
margin-bottom: 15px;
}
li{
margin-bottom: 5px;
&:last-child{
margin-bottom: 0;
}
}
}
@@ -123,6 +123,7 @@
position: relative;
text-align: right;
user-select: none;
margin-left: auto;
a {
opacity: .5;
@@ -134,8 +135,8 @@
.password-text {
background-repeat: no-repeat;
background-size: 18px;
background-position: left center;
padding-left: 26px;
background-position: 0px 1px;
padding-left: 24px;
&.show {
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' viewBox='0 0 32 32'%3E%3Cpath fill='%23444' d='M16 6C9 6 3 10 0 16c3 6 9 10 16 10s13-4 16-10c-3-6-9-10-16-10zm8 5.3c1.8 1.2 3.4 2.8 4.6 4.7-1.2 2-2.8 3.5-4.7 4.7-3 1.5-6 2.3-8 2.3s-6-.8-8-2.3C6 19.5 4 18 3 16c1.5-2 3-3.5 5-4.7l.6-.2C8 12 8 13 8 14c0 4.5 3.5 8 8 8s8-3.5 8-8c0-1-.3-2-.6-2.6l.4.3zM16 13c0 1.7-1.3 3-3 3s-3-1.3-3-3 1.3-3 3-3 3 1.3 3 3z'/%3E%3C/svg%3E");
@@ -863,11 +863,11 @@
.bootstrap-datetimepicker-widget .picker-switch .btn{ background: none; border: none;}
.umb-datepicker .input-append .add-on{cursor: pointer;}
.umb-datepicker .input-append .on-top {
border: 0 none;
position: absolute;
margin-left: -31px;
margin-top: 1px;
display: inline-block;
height: 22px;
padding: 5px 6px 3px 6px;
font-size: @baseFontSize;
font-weight: normal;
@@ -56,12 +56,14 @@ function MainController($scope, $location, appState, treeService, notificationsS
appState.setSearchState("show", false);
};
$scope.showLoginScreen = function(isTimedOut) {
$scope.showLoginScreen = function (isTimedOut) {
$scope.login.pageTitle = $scope.$root.locationTitle;
$scope.login.isTimedOut = isTimedOut;
$scope.login.show = true;
};
$scope.hideLoginScreen = function() {
$scope.hideLoginScreen = function () {
$scope.$root.locationTitle = $scope.login.pageTitle;
$scope.login.show = false;
};
@@ -18,15 +18,14 @@
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<div ng-if="!vm.loading">
<a href="" tabindex="0" umb-auto-focus></a>
<ul class="umb-card-grid -three-in-row">
<li ng-repeat="dataTypeConfig in vm.configs | orderBy:'name'"
data-element="datatypeconfig-{{dataTypeConfig.name}}"
ng-click="vm.pickDataType(dataTypeConfig)">
data-element="datatypeconfig-{{dataTypeConfig.name}}">
<div ng-if="dataTypeConfig.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{ dataTypeConfig.name }}">
<a class="umb-card-grid-item" href="" ng-click="vm.pickDataType(dataTypeConfig)" title="{{ dataTypeConfig.name }}">
<span>
<i class="{{ dataTypeConfig.icon }}" ng-class="{'icon-autofill': dataTypeConfig.icon == null}"></i>
{{ dataTypeConfig.name }}
@@ -35,8 +34,8 @@
</li>
</ul>
<ul class="umb-card-grid -three-in-row">
<li ng-click="vm.newDataType()">
<a class="umb-card-grid-item-slot" href="" title="Create a new configuration of {{ model.dataType.name }}">
<li>
<a class="umb-card-grid-item-slot" href="" ng-click="vm.newDataType()" title="Create a new configuration of {{ model.editor.name }}">
<span>
<i class="icon icon-add"></i>
Create new
@@ -37,9 +37,8 @@
<h5>{{key | umbCmsTitleCase}}</h5>
<ul class="umb-card-grid -six-in-row">
<li ng-repeat="dataType in value | orderBy:'name'"
data-element="datatype-{{dataType.name}}"
ng-click="vm.viewOptionsForEditor(dataType)">
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
data-element="datatype-{{dataType.name}}">
<a class="umb-card-grid-item" href="" ng-click="vm.viewOptionsForEditor(dataType)" title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
@@ -58,12 +57,11 @@
<h5>{{result.group | umbCmsTitleCase}}</h5>
<ul class="umb-card-grid -six-in-row">
<li ng-repeat="dataType in result.entries | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)">
ng-mouseover="vm.showDetailsOverlay(dataType)">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
<a class="umb-card-grid-item" href="" ng-click="vm.pickDataType(dataType)" title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
@@ -78,9 +76,8 @@
<div ng-if="result.entries.length > 0">
<h5>{{result.group | umbCmsTitleCase}}</h5>
<ul class="umb-card-grid -six-in-row">
<li ng-repeat="dataType in result.entries | orderBy:'name'"
ng-click="vm.pickEditor(dataType)">
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
<li ng-repeat="dataType in result.entries | orderBy:'name'">
<a class="umb-card-grid-item" href="" ng-click="vm.pickEditor(dataType)" title="{{ dataType.name }}">
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
@@ -0,0 +1,15 @@
(function () {
"use strict";
function ConfirmController($scope, userService) {
var vm = this;
vm.userEmailAddress = "";
userService.getCurrentUser().then(function(user){
vm.userEmailAddress = user.email;
});
}
angular.module("umbraco").controller("Umbraco.Tours.UmbEmailMarketing.ConfirmController", ConfirmController);
})();
@@ -0,0 +1,15 @@
<div ng-controller="Umbraco.Tours.UmbEmailMarketing.ConfirmController as vm">
<umb-tour-step-header title="model.currentStep.title"></umb-tour-step-header>
<umb-tour-step on-close="model.completeTour()">
<p>We have sent a welcome email to your email address <strong>{{ vm.userEmailAddress }}</strong></p>
</umb-tour-step>
<umb-tour-step-footer>
<div class="flex justify-end">
<umb-button type="button" button-style="link" action="model.completeTour()" label-key="general_close" shortcut="esc"></umb-button>
</div>
</umb-tour-step-footer>
</div>
@@ -13,10 +13,7 @@
userService.addUserToEmailMarketing(user);
});
// Mark Tour as complete
// This is also can help us indicate that the user accepted
// Where disabled is set if user closes modal or chooses NO
$scope.model.completeTour();
$scope.model.nextStep();
}
}
@@ -149,16 +149,16 @@
<form method="POST" name="vm.loginForm" ng-submit="vm.loginSubmit()">
<div ng-messages="vm.loginForm.$error" class="control-group" aria-live="assertive">
<p ng-message="auth" class="text-error" role="alert">{{vm.errorMsg}}</p>
<p ng-message="auth" class="text-error" role="alert" tabindex="0">{{vm.errorMsg}}</p>
</div>
<div class="control-group" ng-class="{error: vm.loginForm.username.$invalid}">
<label for="umb-username">{{vm.labels.usernameLabel}}</label>
<input type="text" ng-model="vm.login" name="username" id="umb-username" class="-full-width-input" placeholder="{{vm.labels.usernamePlaceholder}}" focus-when="{{vm.view === 'login'}}" />
<input type="text" ng-model="vm.login" name="username" id="umb-username" class="-full-width-input" placeholder="{{vm.labels.usernamePlaceholder}}" focus-when="{{vm.view === 'login'}}" aria-required="true"/>
</div>
<div class="control-group" ng-class="{error: vm.loginForm.password.$invalid}">
<label for="umb-passwordTwo"><localize key="general_password">Password</localize></label>
<input type="password" ng-model="vm.password" name="password" id="umb-passwordTwo" class="-full-width-input" localize="placeholder" placeholder="@placeholders_password" />
<input type="password" ng-model="vm.password" name="password" id="umb-passwordTwo" class="-full-width-input" localize="placeholder" placeholder="@placeholders_password" aria-required="true" />
<div class="password-toggle">
<a href="#" prevent-default ng-click="vm.togglePassword()">
<span class="password-text show"><localize key="login_showPassword">Show password</localize></span>
@@ -52,7 +52,7 @@
{{vm.buttonLabel}}
<span ng-if="vm.showCaret" class="umb-button__caret caret" aria-hidden="true"></span>
</span>
</button
</button>
</div>
@@ -15,7 +15,14 @@
<umb-dropdown-item class="umb-action" ng-class="{'sep':action.separatorm, '-opens-dialog': action.opensDialog}" ng-repeat="action in actions">
<button type="button" ng-click="executeMenuItem(action)">
<i class="icon icon-{{action.cssclass}}" aria-hidden="true"></i>
<span class="menu-label">{{action.name}}</span>
<!-- Render the text that will be visually displayed -->
<span class="menu-label" aria-hidden="true">{{action.name}}</span>
<!-- Render the textDescription from the language files if it's attached to the action object-->
<span class="sr-only" ng-if="action.textDescription">
<localize key="visuallyHiddenTexts_{{action.alias}}Description" tokens="[currentNode.name]"></localize>
</span>
<!-- Otherwise render a combination of the nodename and the currentNode name-->
<span class="sr-only" ng-if="!action.textDescription">{{action.name}} {{currentNode.name}}</span>
</button>
</umb-dropdown-item>
</umb-dropdown>
@@ -1,7 +1,7 @@
<label class="checkbox umb-form-check umb-form-check--checkbox {{vm.cssClass}}" ng-class="{ 'umb-form-check--disabled': vm.disabled }">
<div class="umb-form-check__symbol">
<input type="checkbox"
<input ng-if="vm.disableDirtyCheck"
type="checkbox"
id="{{vm.inputId}}"
name="{{vm.name}}"
value="{{vm.value}}"
@@ -10,7 +10,20 @@
ng-model="vm.model"
ng-disabled="vm.disabled"
ng-required="vm.required"
ng-change="vm.change()"/>
ng-change="vm.change()"
no-dirty-check />
<input ng-if="!vm.disableDirtyCheck"
type="checkbox"
id="{{vm.inputId}}"
name="{{vm.name}}"
value="{{vm.value}}"
class="umb-form-check__input"
val-server-field="{{vm.serverValidationField}}"
ng-model="vm.model"
ng-disabled="vm.disabled"
ng-required="vm.required"
ng-change="vm.change()"/>
<span class="umb-form-check__state" aria-hidden="true">
<span class="umb-form-check__check">
@@ -11,7 +11,7 @@
<localize key="contentTypeEditor_inheritedFrom"></localize> {{inheritsFrom}}
</small>
<label class="control-label" for="{{property.alias}}" ng-attr-title="{{propertyAlias}}">
<label class="control-label" for="{{property.alias}}" ng-attr-title="{{controlLabelTitle}}">
{{property.label}}
@@ -3,7 +3,7 @@
<i ng-if="icon" class="umb-node-preview__icon {{ icon }}" aria-hidden="true"></i>
<div class="umb-node-preview__content">
<div class="umb-node-preview__name" ng-attr-title="{{alias}}">{{ name }}</div>
<div class="umb-node-preview__name" ng-attr-title="{{nodeNameTitle}}">{{ name }}</div>
<div class="umb-node-preview__description" ng-if="description">{{ description }}</div>
<div class="umb-user-group-preview__permissions" ng-if="permissions">
@@ -12,21 +12,32 @@
<umb-editor-container>
<umb-box>
<umb-box-content>
<h3>What are Content Templates?</h3>
<p style="line-height: 1.6em; margin-bottom: 30px;">Content Templates are pre-defined content that can be selected when creating a new content node.</p>
<umb-box-content class="content-templates-dashboard">
<h3>
<localize key="contentTemplatesDashboard_whatHeadline">What are Content Templates?</localize>
</h3>
<p>
<localise key="contentTemplatesDashboard_whatDescription">Content Templates are pre-defined content that can be selected when creating a new content node.</localize>
</p>
<h4 class="bold">How do I create a Content Template?</h4>
<p style="line-height: 1.6em;">There are two ways to create a Content Template:</p>
<ul style="margin-bottom: 15px;">
<li style="margin-bottom: 5px;">Right-click a content node and select "Create Content Template" to create a new Content Template.</li>
<li>Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.</li>
</ul>
<p style="line-height: 1.6em; margin-bottom: 30px;">Once given a name, editors can start using the Content Template as a foundation for their new page.</p>
<h4 class="bold">
<localize key="contentTemplatesDashboard_createHeadline">How do I create a Content Template?</localize>
</h4>
<localize key="contentTemplatesDashboard_createDescription">
<p>There are two ways to create a Content Template:</p>
<ul>
<li>Right-click a content node and select "Create Content Template" to create a new Content Template.</li>
<li>Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.</li>
</ul>
<p>Once given a name, editors can start using the Content Template as a foundation for their new page.</p>
</localize>
<h4 class="bold">How do I manage Content Templates</h4>
<p style="line-height: 1.6em;">You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Just expand the Document Type which the
Content Template is based on and click it to edit or delete it.</p>
<h4 class="bold">
<localize key="contentTemplatesDashboard_manageHeadline">How do I manage Content Templates?</localize>
</h4>
<p>
<localize key="contentTemplatesDashboard_manageDescription">You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Just expand the Document Type which the Content Template is based on and click it to edit or delete it.</localize>
</p>
</umb-box-content>
</umb-box>
@@ -20,10 +20,11 @@
ng-required="model.validation.mandatory"
val-server="value"
class="datepickerinput">
<span class="on-top" ng-click="clearDate()" ng-show="hasDatetimePickerValue === true || datePickerForm.datepicker.$error.pickerError === true">
<i class="icon-delete"></i>
</span>
<span class="add-on">
<button type="button" class="on-top" ng-click="clearDate()" ng-show="hasDatetimePickerValue === true || datePickerForm.datepicker.$error.pickerError === true">
<i class="icon-delete" aria-hidden="true"></i>
<span class="sr-only"><localize key="content_removeDate">Clear date</localize></span>
</button>
<span class="add-on" aria-hidden="true">
<i class="icon-calendar"></i>
</span>
</div>
@@ -15,7 +15,14 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo
//ensure this is a bool, old data could store zeros/ones or string versions
$scope.model.config.multiple = Object.toBoolean($scope.model.config.multiple);
//ensure when form is saved that we don't store [] or [null] as string values in the database when no items are selected
$scope.$on("formSubmitting", function () {
if ($scope.model.value.length === 0 || $scope.model.value[0] === null) {
$scope.model.value = null;
}
});
function convertArrayToDictionaryArray(model){
//now we need to format the items in the dictionary because we always want to have an array
var newItems = [];
@@ -34,6 +34,7 @@
fileManager.setFiles({
propertyAlias: $scope.model.alias,
culture: $scope.model.culture,
segment: $scope.model.segment,
files: []
});
}
@@ -1,5 +1,6 @@
<div ng-controller="Umbraco.PropertyEditors.FileUploadController">
<umb-property-file-upload culture="{{model.culture}}"
segment="{{model.segment}}"
property-alias="{{model.alias}}"
value="model.value"
required="model.validation.mandatory"
@@ -55,6 +55,7 @@ angular.module('umbraco')
fileManager.setFiles({
propertyAlias: $scope.model.alias,
culture: $scope.model.culture,
segment: $scope.model.segment,
files: []
});
}
@@ -189,6 +190,7 @@ angular.module('umbraco')
fileManager.setFiles({
propertyAlias: $scope.model.alias,
culture: $scope.model.culture,
segment: $scope.model.segment,
files: []
});
@@ -3,6 +3,7 @@
<ng-form name="imageCropperForm">
<umb-property-file-upload culture="{{model.culture}}"
segment="{{model.segment}}"
property-alias="{{model.alias}}"
value="model.value.src"
required="model.validation.mandatory"
@@ -1,6 +1,6 @@
<div ng-controller="Umbraco.PropertyEditors.textAreaController">
<ng-form name="textareaFieldForm">
<textarea ng-model="model.value" id="{{model.alias}}" name="textarea" rows="{{model.config.rows || 10}}" class="umb-property-editor umb-textarea textstring" val-server="value" ng-keyup="model.change()" ng-required="model.validation.mandatory" aria-required="{{model.validation.mandatory}}"></textarea>
<textarea ng-model="model.value" id="{{model.alias}}" name="textarea" rows="{{model.config.rows || 10}}" class="umb-property-editor umb-textarea textstring" val-server="value" ng-keyup="model.change()" ng-trim="false" ng-required="model.validation.mandatory" aria-required="{{model.validation.mandatory}}"></textarea>
<span ng-messages="textareaFieldForm.textarea.$error" show-validation-on-submit >
<span class="help-inline" ng-message="required">{{mandatoryMessage}}</span>
@@ -6,6 +6,7 @@
<umb-editor-header
name="vm.relationType.name"
alias="vm.relationType.alias"
alias-locked="vm.relationType.isSystemRelationType"
hide-description="true"
hide-icon="true"
navigation="vm.page.navigation"
@@ -88,9 +88,10 @@
//the user has a password if they are not states: Invited, NoCredentials
vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
vm.changePasswordModel.config.disableToggle = true;
vm.loading = false;
vm.changePasswordModel.config.disableToggle = true;
$scope.$emit("$setAccessibleHeader", false, "general_user", false, vm.user.name, "", true);
vm.loading = false;
});
});
}
@@ -814,6 +814,7 @@
vm.newUser.message = "";
// clear button state
vm.page.createButtonState = "init";
$scope.$emit("$setAccessibleHeader", true, "general_user", false, "", "", true);
}
init();
+2 -2
View File
@@ -16,7 +16,7 @@ gulp.task('docs', [], function (cb) {
var options = {
html5Mode: false,
startPage: '/api',
title: "Umbraco Backoffice UI API Documentation",
title: "Umbraco 8 Backoffice UI API Documentation",
dest: './api',
styles: ['./umb-docs.css'],
image: "https://our.umbraco.com/assets/images/logo.svg"
@@ -53,4 +53,4 @@ gulp.task('open:docs', function (cb) {
gulp.src(__filename)
.pipe(open(options));
cb();
});
});
+24 -15
View File
@@ -4,7 +4,7 @@ html {
}
body {
font-family: 'Segoe UI', Tahoma, Helvetica, sans-serif;
}
.container, .navbar-static-top .container, .navbar-fixed-top .container, .navbar-fixed-bottom .container {
@@ -33,37 +33,43 @@ body {
font-family: inherit;
}
.navbar .container{
min-height: inherit;
display: flex;
align-items: center;
}
.navbar .brand {
display: block;
float: left;
padding: 10px 20px 10px;
margin-left: -20px;
font-size: 20px;
font-weight: 200;
color: rgba(0,0,0,.8);
color: white;
text-shadow: none;
}
.navbar-fixed-top .navbar-inner {
min-height: 50px;
background: #a3db78;
background: #3544b1;
}
.form-search .well {
background-color: #f5fbf1;
background-color: #f7f7f7;
}
.form-search > ul.nav > li.module {
background-color: #daf0c9;
background-color: #3544b1;
}
.breadcrumb {
background-color: #f5fbf1;
.form-search > ul.nav > li.module a {
color: white;
}
a {
color: #f36f21;
.form-search > ul.nav > li.section {
background-color: #ccc;
}
.breadcrumb {
background-color: #f7f7f7;
}
a:hover {
text-decoration: none;
color: rgba(0,0,0,.8);
@@ -87,9 +93,12 @@ a:hover {
color: #000;
}
.form-search > ul.nav > li.module > a:hover{
color: #fff;
}
.header img {
width: 50px;
margin-top: 5px;
}
.content .methods code {
@@ -247,7 +247,7 @@
<key alias="routeErrorCannotRoute">Dette dokument er udgivet, men dets URL kan ikke dirigeres</key>
<key alias="publish">Udgiv</key>
<key alias="published">Udgivet</key>
<key alias="publishedPendingChanges">Udgivet (Afventende ændringer)</key>
<key alias="publishedPendingChanges">Udgivet (Ventede ændringer)</key>
<key alias="publishStatus">Udgivelsesstatus</key>
<key alias="publishDescendantsHelp"><![CDATA[Klik <em>Udgiv med undersider</em> for at udgive <strong>%0%</strong> og alle sider under og dermed gøre deres indhold offentligt tilgængelige.]]></key>
<key alias="publishDescendantsWithVariantsHelp"><![CDATA[Klik <em>Udgiv med undersider</em> for at udgive <strong>de valgte sprog</strong> og de samme sprog for sider under og dermed gøre deres indhold offentligt tilgængelige.]]></key>
@@ -426,6 +426,7 @@
<key alias="linkinternal">Internt link:</key>
<key alias="linklocaltip">Ved lokalt link, indsæt da en "#" foran linket</key>
<key alias="linknewwindow">Åben i nyt vindue?</key>
<key alias="macroContainerSettings">Makroindstillinger</key>
<key alias="macroDoesNotHaveProperties">Denne makro har ingen egenskaber du kan redigere</key>
<key alias="paste">Indsæt tekst</key>
<key alias="permissionsEdit">Rediger rettigheder for</key>
@@ -663,6 +664,7 @@
<key alias="icon">Ikon</key>
<key alias="id">Id</key>
<key alias="import">Importer</key>
<key alias="includeFromsubFolders">Inkludér undermapper i søgning</key>
<key alias="excludeFromSubFolders">Søg kun i denne mappe</key>
<key alias="info">Info</key>
<key alias="innerMargin">Indre margen</key>
@@ -1749,6 +1751,12 @@ Mange hilsner fra Umbraco robotten
<key alias="openBackofficeSearch">Åben backoffice søgning</key>
<key alias="openCloseBackofficeHelp">Åben/Luk backoffice hjælp</key>
<key alias="openCloseBackofficeProfileOptions">Åben/Luk dine profil indstillinger</key>
<key alias="assignDomainDescription">Tilføj domæne på %0%</key>
<key alias="createDescription">Opret ny node under %0%</key>
<key alias="protectDescription">Opsæt offentlig adgang på %0%</key>
<key alias="rightsDescription">Opsæt rettigheder på %0%</key>
<key alias="sortDescription">Juster soterings rækkefølgen for %0%</key>
<key alias="createblueprintDescription">Opret indholds skabelon baseret på %0%</key>
<key alias="currentLanguage">Aktivt sprog</key>
<key alias="switchLanguage">Skift sprog til</key>
<key alias="createNewFolder">Opret ny mappe</key>
+28 -1
View File
@@ -141,6 +141,7 @@
<key alias="saveToPublish">Save and send for approval</key>
<key alias="saveListView">Save list view</key>
<key alias="schedulePublish">Schedule</key>
<key alias="showPage">Preview</key>
<key alias="saveAndPreview">Save and preview</key>
<key alias="showPageDisabled">Preview is disabled because there's no template assigned</key>
<key alias="styleChoose">Choose style</key>
@@ -168,6 +169,7 @@
<key alias="sendtopublish">Content sent for publishing</key>
<key alias="sendtopublishvariant">Content sent for publishing for languages: %0%</key>
<key alias="sort">Sort child items performed by user</key>
<key alias="custom">%0%</key>
<key alias="smallCopy">Copy</key>
<key alias="smallPublish">Publish</key>
<key alias="smallPublishVariant">Publish</key>
@@ -180,6 +182,7 @@
<key alias="smallSendToPublish">Send To Publish</key>
<key alias="smallSendToPublishVariant">Send To Publish</key>
<key alias="smallSort">Sort</key>
<key alias="smallCustom">Custom</key>
<key alias="historyIncludingVariants">History (all variants)</key>
</area>
<area alias="changeDocType">
@@ -684,6 +687,7 @@
<key alias="icon">Icon</key>
<key alias="id">Id</key>
<key alias="import">Import</key>
<key alias="includeFromsubFolders">Include subfolders in search</key>
<key alias="excludeFromSubFolders">Search only this folder</key>
<key alias="info">Info</key>
<key alias="innerMargin">Inner margin</key>
@@ -2226,6 +2230,12 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="openBackofficeSearch">Open backoffice search</key>
<key alias="openCloseBackofficeHelp">Open/Close backoffice help</key>
<key alias="openCloseBackofficeProfileOptions">Open/Close your profile options</key>
<key alias="assignDomainDescription">Setup Culture and Hostnames for %0%</key>
<key alias="createDescription">Create new node under %0%</key>
<key alias="protectDescription">Setup Public access on %0%</key>
<key alias="rightsDescription">Setup Permissions on %0%</key>
<key alias="sortDescription">Change sort order for %0%</key>
<key alias="createblueprintDescription">Create Content Template based on %0%</key>
<key alias="openContextMenu">Open context menu for</key>
<key alias="currentLanguage">Current language</key>
<key alias="switchLanguage">Switch language to</key>
@@ -2241,7 +2251,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="create">Create</key>
<key alias="edit">Edit</key>
<key alias="name">Name</key>
<key alias="addNewRow">Add new row</key>
<key alias="addNewRow">Add new row</key>
<key alias="tabExpand">View more options</key>
</area>
<area alias="references">
@@ -2413,4 +2423,21 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="formsHeadline">Umbraco Forms</key>
<key alias="formsDescription">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</key>
</area>
<area alias="contentTemplatesDashboard">
<key alias="whatHeadline">What are Content Templates?</key>
<key alias="whatDescription">Content Templates are pre-defined content that can be selected when creating a new content node.</key>
<key alias="createHeadline">How do I create a Content Template?</key>
<key alias="createDescription">
<![CDATA[
<p>There are two ways to create a Content Template:</p>
<ul>
<li>Right-click a content node and select "Create Content Template" to create a new Content Template.</li>
<li>Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.</li>
</ul>
<p>Once given a name, editors can start using the Content Template as a foundation for their new page.</p>
]]>
</key>
<key alias="manageHeadline">How do I manage Content Templates?</key>
<key alias="manageDescription">You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Expand the Document Type which the Content Template is based on and click it to edit or delete it.</key>
</area>
</language>
@@ -140,6 +140,7 @@
<key alias="saveToPublish">Send for approval</key>
<key alias="saveListView">Save list view</key>
<key alias="schedulePublish">Schedule</key>
<key alias="showPage">Preview</key>
<key alias="saveAndPreview">Save and preview</key>
<key alias="showPageDisabled">Preview is disabled because there's no template assigned</key>
<key alias="styleChoose">Choose style</key>
@@ -170,6 +171,7 @@
<key alias="sendtopublish">Content sent for publishing</key>
<key alias="sendtopublishvariant">Content sent for publishing for languages: %0%</key>
<key alias="sort">Sort child items performed by user</key>
<key alias="custom">%0%</key>
<key alias="smallCopy">Copy</key>
<key alias="smallPublish">Publish</key>
<key alias="smallPublishVariant">Publish</key>
@@ -183,6 +185,7 @@
<key alias="smallSendToPublish">Send To Publish</key>
<key alias="smallSendToPublishVariant">Send To Publish</key>
<key alias="smallSort">Sort</key>
<key alias="smallCustom">Custom</key>
<key alias="historyIncludingVariants">History (all variants)</key>
</area>
<area alias="changeDocType">
@@ -367,7 +370,8 @@
<key alias="noDocumentTypesAllowedAtRoot"><![CDATA[There are no allowed document types available for creating content here. You must enable these in <strong>Document Types</strong> within the <strong>Settings</strong> section, by changing the <strong>Allow as root</strong> option under <strong>Permissions</strong>.]]></key>
<key alias="noMediaTypes" version="7.0"><![CDATA[There are no allowed media types available for creating media here. You must enable these in <strong>Media Types Types</strong> within the <strong>Settings</strong> section, by editing the <strong>Allowed child node types</strong> under <strong>Permissions</strong>.]]></key>
<key alias="noMediaTypesWithNoSettingsAccess">The selected media in the tree doesn't allow for any other media to be created below it.</key>
<key alias="noMediaTypesEditPermissions">Edit permissions for this media type</key> <key alias="documentTypeWithoutTemplate">Document Type without a template</key>
<key alias="noMediaTypesEditPermissions">Edit permissions for this media type</key>
<key alias="documentTypeWithoutTemplate">Document Type without a template</key>
<key alias="newFolder">New folder</key>
<key alias="newDataType">New data type</key>
<key alias="newJavascriptFile">New JavaScript file</key>
@@ -568,6 +572,9 @@
<key alias="anchor">#value or ?key=value</key>
<key alias="enterAlias">Enter alias...</key>
<key alias="generatingAlias">Generating alias...</key>
<key alias="a11yCreateItem">Create item</key>
<key alias="a11yEdit">Edit</key>
<key alias="a11yName">Name</key>
</area>
<area alias="editcontenttype">
<key alias="createListView" version="7.2">Create custom list view</key>
@@ -688,6 +695,7 @@
<key alias="icon">Icon</key>
<key alias="id">Id</key>
<key alias="import">Import</key>
<key alias="includeFromsubFolders">Include subfolders in search</key>
<key alias="excludeFromSubFolders">Search only this folder</key>
<key alias="info">Info</key>
<key alias="innerMargin">Inner margin</key>
@@ -2242,6 +2250,12 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="openBackofficeSearch">Open backoffice search</key>
<key alias="openCloseBackofficeHelp">Open/Close backoffice help</key>
<key alias="openCloseBackofficeProfileOptions">Open/Close your profile options</key>
<key alias="assignDomainDescription">Setup Culture and Hostnames for %0%</key>
<key alias="createDescription">Create new node under %0%</key>
<key alias="protectDescription">Setup Public access on %0%</key>
<key alias="rightsDescription">Setup Permissions on %0%</key>
<key alias="sortDescription">Change sort order for %0%</key>
<key alias="createblueprintDescription">Create Content Template based on %0%</key>
<key alias="openContextMenu">Open context menu for</key>
<key alias="currentLanguage">Current language</key>
<key alias="switchLanguage">Switch language to</key>
@@ -2429,4 +2443,21 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="formsHeadline">Umbraco Forms</key>
<key alias="formsDescription">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</key>
</area>
<area alias="contentTemplatesDashboard">
<key alias="whatHeadline">What are Content Templates?</key>
<key alias="whatDescription">Content Templates are pre-defined content that can be selected when creating a new content node.</key>
<key alias="createHeadline">How do I create a Content Template?</key>
<key alias="createDescription">
<![CDATA[
<p>There are two ways to create a Content Template:</p>
<ul>
<li>Right-click a content node and select "Create Content Template" to create a new Content Template.</li>
<li>Right-click the Content Templates tree in the Settings section and select the Document Type you want to create a Content Template for.</li>
</ul>
<p>Once given a name, editors can start using the Content Template as a foundation for their new page.</p>
]]>
</key>
<key alias="manageHeadline">How do I manage Content Templates?</key>
<key alias="manageDescription">You can edit and delete Content Templates from the "Content Templates" tree in the Settings section. Expand the Document Type which the Content Template is based on and click it to edit or delete it.</key>
</area>
</language>
@@ -14,6 +14,10 @@
"content": "<p>Thank you for using Umbraco! Would you like to stay up-to-date with Umbraco product updates, security advisories, community news and special offers? Sign up for our newsletter and never miss out on the latest Umbraco news.</p> <p class='notice'>By signing up, you agree that we can use your info according to our <a href='https://umbraco.com/about-us/privacy/'>privacy policy</a>.</p>",
"view": "emails",
"type": "promotion"
},
{
"title": "Thank you for subscribing to our mailing list",
"view": "confirm"
}
]
},
@@ -51,7 +51,19 @@ namespace Umbraco.Web.Editors.Binders
}
}
// TODO: anything after 3 parts we can put in metadata
//if there are 4 parts part 4 is always segment
string segment = null;
if (parts.Length > 3)
{
segment = parts[3];
//normalize to null if empty
if (segment.IsNullOrWhiteSpace())
{
segment = null;
}
}
// TODO: anything after 4 parts we can put in metadata
var fileName = file.Headers.ContentDisposition.FileName.Trim('\"');
@@ -60,6 +72,7 @@ namespace Umbraco.Web.Editors.Binders
TempFilePath = file.LocalFileName,
PropertyAlias = propAlias,
Culture = culture,
Segment = segment,
FileName = fileName
});
}
@@ -93,7 +93,7 @@ namespace Umbraco.Web.Editors
// prepare files, if any matching property and culture
var files = contentItem.UploadedFiles
.Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture)
.Where(x => x.PropertyAlias == propertyDto.Alias && x.Culture == propertyDto.Culture && x.Segment == propertyDto.Segment)
.ToArray();
foreach (var file in files)
@@ -108,7 +108,6 @@ namespace Umbraco.Web.Runtime
composition.WithCollectionBuilder<UmbracoApiControllerTypeCollectionBuilder>()
.Add(umbracoApiControllerTypes);
// add all known factories, devs can then modify this list on application
// startup either by binding to events or in their own global.asax
composition.FilteredControllerFactory()
@@ -11,6 +11,7 @@ using Umbraco.Core.Persistence;
using Umbraco.Core.Services;
using Umbraco.Web.Actions;
using Umbraco.Web.Routing;
using Umbraco.Core.Models;
namespace Umbraco.Web.Trees
{
@@ -40,8 +41,6 @@ namespace Umbraco.Web.Trees
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
//TODO: Do not allow deleting built in types
var menu = _menuItemCollectionFactory.Create();
if (id == Constants.System.RootString)
@@ -58,7 +57,10 @@ namespace Umbraco.Web.Trees
var relationType = Services.RelationService.GetRelationTypeById(int.Parse(id));
if (relationType == null) return menu;
menu.Items.Add<ActionDelete>(Services.TextService);
if (relationType.IsSystemRelationType() == false)
{
menu.Items.Add<ActionDelete>(Services.TextService);
}
return menu;
}
+3 -1
View File
@@ -5,6 +5,8 @@ using System.Threading;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Cache;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging.Serilog;
using Umbraco.Core.Runtime;
using Umbraco.Core.Configuration;
using Umbraco.Core.Hosting;
@@ -32,7 +34,7 @@ namespace Umbraco.Web
// Determine if we should use the sql main dom or the default
var appSettingMainDomLock = globalSettings.MainDomLock;
var mainDomLock = appSettingMainDomLock == "SqlMainDomLock"
? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connectionStrings, dbProviderFactoryCreator)
? (IMainDomLock)new SqlMainDomLock(logger, globalSettings, connectionStrings, dbProviderFactoryCreator, hostingEnvironment)
: new MainDomSemaphoreLock(logger, hostingEnvironment);
var mainDom = new MainDom(logger, mainDomLock);