Merge branch 'v8/contrib' into v8/dev

# Conflicts:
#	src/Umbraco.Web.UI.Client/package-lock.json
This commit is contained in:
Sebastiaan Janssen
2020-05-26 16:27:09 +02:00
27 changed files with 542 additions and 161 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);
}
}
}
}
@@ -0,0 +1,12 @@
namespace Umbraco.Core.Models
{
internal static class RelationTypeExtensions
{
internal 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;
}
}
+13 -1
View File
@@ -3,8 +3,10 @@ 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 System.Web;
using Umbraco.Core.Logging;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.Dtos;
@@ -16,7 +18,7 @@ namespace Umbraco.Core.Runtime
internal 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 IUmbracoDatabase _db;
@@ -126,6 +128,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; } = MainDomKeyPrefix + "-" + (NetworkHelper.MachineName + MainDom.GetMainDomId()).GenerateHash<SHA1>();
private void ListeningLoop()
{
while (true)
+1
View File
@@ -136,6 +136,7 @@
<Compile Include="Models\ContentDataIntegrityReportEntry.cs" />
<Compile Include="Models\ContentDataIntegrityReportOptions.cs" />
<Compile Include="Models\InstallLog.cs" />
<Compile Include="Models\RelationTypeExtensions.cs" />
<Compile Include="Persistence\Repositories\IInstallationRepository.cs" />
<Compile Include="Persistence\Repositories\Implement\InstallationRepository.cs" />
<Compile Include="Services\Implement\InstallationService.cs" />
@@ -332,6 +332,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 { }
@@ -352,5 +370,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;
}
}
}
}
@@ -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',
@@ -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");
@@ -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 }}
@@ -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 @@ 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 = [];
@@ -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"
@@ -103,6 +103,7 @@
vm.changePasswordModel.config.allowManuallyChangingPassword = 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();
@@ -168,6 +168,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 +181,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">
@@ -170,6 +170,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 +184,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">
@@ -13,6 +13,9 @@ namespace Umbraco.Web.Models.ContentEditing
Notifications = new List<Notification>();
}
[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)
{
@@ -179,7 +179,7 @@ namespace Umbraco.Web.Runtime
.Remove<TinyMceValueConverter>()
.Remove<TextStringValueConverter>()
.Remove<MarkdownEditorValueConverter>();
// 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()
@@ -203,8 +203,12 @@ namespace Umbraco.Web.Runtime
.Append<ContentFinderByUrl>()
.Append<ContentFinderByIdPath>()
//.Append<ContentFinderByUrlAndTemplate>() // disabled, this is an odd finder
.Append<ContentFinderByUrlAlias>()
.Append<ContentFinderByRedirectUrl>();
.Append<ContentFinderByUrlAlias>();
//only append ContentFinderByRedirectUrl if RedirectUrlTracking is not disabled
if (composition.Configs.Settings().WebRouting.DisableRedirectUrlTracking == false)
{
composition.ContentFinders().Append<ContentFinderByRedirectUrl>();
}
composition.RegisterUnique<ISiteDomainHelper, SiteDomainHelper>();
@@ -5,6 +5,7 @@ using Umbraco.Web.WebApi.Filters;
using Umbraco.Core;
using Umbraco.Core.Services;
using Umbraco.Web.Actions;
using Umbraco.Core.Models;
namespace Umbraco.Web.Trees
{
@@ -16,8 +17,6 @@ namespace Umbraco.Web.Trees
{
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
{
//TODO: Do not allow deleting built in types
var menu = new MenuItemCollection();
if (id == Constants.System.RootString)
@@ -34,7 +33,10 @@ namespace Umbraco.Web.Trees
var relationType = Services.RelationService.GetRelationTypeById(int.Parse(id));
if (relationType == null) return new MenuItemCollection();
menu.Items.Add<ActionDelete>(Services.TextService.Localize("actions", ActionDelete.ActionAlias));
if (relationType.IsSystemRelationType() == false)
{
menu.Items.Add<ActionDelete>(Services.TextService.Localize("actions", ActionDelete.ActionAlias));
}
return menu;
}
+12 -3
View File
@@ -2,6 +2,7 @@
using System.Threading;
using System.Web;
using Umbraco.Core;
using Umbraco.Core.Logging;
using Umbraco.Core.Logging.Serilog;
using Umbraco.Core.Runtime;
using Umbraco.Web.Runtime;
@@ -17,16 +18,24 @@ namespace Umbraco.Web
{
var logger = SerilogLogger.CreateWithDefaultConfiguration();
var runtime = new WebRuntime(this, logger, GetMainDom(logger));
return runtime;
}
/// <summary>
/// Returns a new MainDom
/// </summary>
protected IMainDom GetMainDom(ILogger logger)
{
// Determine if we should use the sql main dom or the default
var appSettingMainDomLock = ConfigurationManager.AppSettings[Constants.AppSettings.MainDomLock];
var mainDomLock = appSettingMainDomLock == "SqlMainDomLock"
? (IMainDomLock)new SqlMainDomLock(logger)
: new MainDomSemaphoreLock(logger);
var runtime = new WebRuntime(this, logger, new MainDom(logger, mainDomLock));
return runtime;
return new MainDom(logger, mainDomLock);
}
/// <summary>