Merge remote-tracking branch 'origin/dev-v7' into temp8
# Conflicts: # .editorconfig # .gitignore # src/Umbraco.Core/Persistence/Repositories/EntityRepository.cs # src/Umbraco.Core/Persistence/Repositories/UserRepository.cs # src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js # src/Umbraco.Web.UI.Client/src/less/components/buttons/umb-era-button.less # src/Umbraco.Web.UI.Client/src/views/common/dialogs/login.html # src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/compositions/compositions.controller.js # src/Umbraco.Web.UI.Client/src/views/common/overlays/contenttypeeditor/compositions/compositions.html # src/Umbraco.Web.UI.Client/src/views/content/content.delete.controller.js # src/Umbraco.Web.UI.Client/src/views/content/content.restore.controller.js # src/Umbraco.Web.UI.Client/src/views/content/restore.html # src/Umbraco.Web.UI.Client/src/views/dashboard/developer/healthcheck.html # src/Umbraco.Web.UI.Client/src/views/media/media.move.controller.js # src/Umbraco.Web.UI.Client/src/views/media/move.html # src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/layoutconfig.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/grid/dialogs/rowconfig.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.controller.js # src/Umbraco.Web.UI.Client/src/views/propertyeditors/listview/listview.html # src/Umbraco.Web.UI/Umbraco/config/lang/da.xml # src/Umbraco.Web.UI/config/umbracoSettings.Release.config # src/Umbraco.Web.UI/umbraco/config/lang/en.xml # src/Umbraco.Web.UI/umbraco/config/lang/en_us.xml # src/Umbraco.Web/Controllers/UmbLoginController.cs # src/Umbraco.Web/Controllers/UmbLoginStatusController.cs # src/Umbraco.Web/Controllers/UmbProfileController.cs # src/Umbraco.Web/Controllers/UmbRegisterController.cs # src/Umbraco.Web/Editors/ContentController.cs # src/Umbraco.Web/Editors/ContentTypeControllerBase.cs # src/Umbraco.Web/HtmlHelperRenderExtensions.cs # src/Umbraco.Web/Trees/ContentTreeController.cs # src/Umbraco.Web/Trees/MediaTreeController.cs # src/Umbraco.Web/umbraco.presentation/umbraco/create/XsltTasks.cs # src/Umbraco.Web/umbraco.presentation/umbraco/create/xslt.ascx.cs # src/Umbraco.Web/umbraco.presentation/umbraco/developer/Xslt/editXslt.aspx.cs # src/Umbraco.Web/umbraco.presentation/umbraco/webservices/codeEditorSave.asmx.cs
This commit is contained in:
@@ -34,3 +34,4 @@ dotnet_naming_style.prefix_underscore.required_prefix = _
|
||||
csharp_style_var_for_built_in_types = true:suggestion
|
||||
csharp_style_var_when_type_is_apparent = true:suggestion
|
||||
csharp_style_var_elsewhere = true:suggestion
|
||||
csharp_prefer_braces = false : none
|
||||
|
||||
1
.github/CONTRIBUTING.md
vendored
1
.github/CONTRIBUTING.md
vendored
@@ -82,7 +82,6 @@ You can get in touch with [the PR team](#the-pr-team) in multiple ways, we love
|
||||
|
||||
- If there's an existing issue on the issue tracker then that's a good place to leave questions and discuss how to start or move forward
|
||||
- Unsure where to start? Did something not work as expected? Try leaving a note in the ["Contributing to Umbraco"](https://our.umbraco.com/forum/contributing-to-umbraco-cms/) forum, the team monitors that one closely
|
||||
- We're also [active in the Gitter chatroom](https://gitter.im/umbraco/Umbraco-CMS)
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -107,6 +107,7 @@ src/Umbraco.Web.UI.Client/[Bb]uild/[Bb]elle/
|
||||
src/Umbraco.Web.UI/[Uu]ser[Cc]ontrols/
|
||||
|
||||
src/Umbraco.Web.UI.Client/src/[Ll]ess/*.css
|
||||
src/Umbraco.Web.UI.Client/vwd.webinfo
|
||||
|
||||
src/Umbraco.Web.UI/App_Plugins/*
|
||||
src/*.psess
|
||||
|
||||
@@ -262,7 +262,7 @@ Use this directive to construct a header inside the main editor window.
|
||||
icon: "=",
|
||||
hideIcon: "@",
|
||||
alias: "=",
|
||||
hideAlias: "@",
|
||||
hideAlias: "=",
|
||||
description: "=",
|
||||
hideDescription: "@",
|
||||
descriptionLocked: "@",
|
||||
|
||||
@@ -242,4 +242,5 @@
|
||||
.umb-healthcheck-group__details-status-action-description {
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
padding-left: 165px;
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
<div class="alert alert-success">
|
||||
<localize key="notify_notificationsSavedFor"></localize><strong> {{currentNode.name}}</strong>
|
||||
</div>
|
||||
<button class="btn btn-primary" ng-click="vm.cancel()"><localize key="general_ok">Ok</localize></button>
|
||||
</div>
|
||||
<div ng-cloak>
|
||||
<div ng-hide="vm.saveSuccces || vm.saveError" ng-cloak>
|
||||
<div class="block-form" ng-show="!vm.loading">
|
||||
<h5><localize key="notify_notifySet">Set your notification for</localize> {{ currentNode.name }}</h5>
|
||||
<localize key="notify_notifySet">Set your notification for</localize> <strong>{{ currentNode.name }}</strong>
|
||||
<umb-control-group>
|
||||
<umb-permission ng-repeat="option in vm.notifyOptions"
|
||||
name="option.name"
|
||||
@@ -31,7 +32,7 @@
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar">
|
||||
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="vm.saveSuccces || vm.saveError">
|
||||
<umb-button label-key="general_cancel"
|
||||
action="vm.cancel()"
|
||||
type="button"
|
||||
@@ -40,6 +41,7 @@
|
||||
<umb-button label-key="buttons_save"
|
||||
type="button"
|
||||
action="vm.save(vm.notifyOptions)"
|
||||
state="vm.saveState"
|
||||
button-style="success">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
@@ -25,16 +25,35 @@
|
||||
<div ng-hide="success">
|
||||
|
||||
<div ng-hide="miniListView">
|
||||
<umb-tree
|
||||
section="media"
|
||||
hideheader="{{treeModel.hideHeader}}"
|
||||
hideoptions="true"
|
||||
isdialog="true"
|
||||
<umb-tree-search-box
|
||||
hide-search-callback="hideSearch"
|
||||
search-callback="onSearchResults"
|
||||
search-from-id="{{searchInfo.searchFromId}}"
|
||||
search-from-name="{{searchInfo.searchFromName}}"
|
||||
show-search="{{searchInfo.showSearch}}"
|
||||
section="media">
|
||||
</umb-tree-search-box>
|
||||
|
||||
<br />
|
||||
|
||||
<umb-tree-search-results
|
||||
ng-if="searchInfo.showSearch"
|
||||
results="searchInfo.results"
|
||||
select-result-callback="selectResult">
|
||||
</umb-tree-search-results>
|
||||
|
||||
<div ng-hide="searchInfo.showSearch">
|
||||
<umb-tree
|
||||
section="media"
|
||||
hideheader="{{treeModel.hideHeader}}"
|
||||
hideoptions="true"
|
||||
isdialog="true"
|
||||
api="dialogTreeApi"
|
||||
on-init="onTreeInit()"
|
||||
enablelistviewexpand="true"
|
||||
enablecheckboxes="true">
|
||||
</umb-tree>
|
||||
enablelistviewexpand="true"
|
||||
enablecheckboxes="true">
|
||||
</umb-tree>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<umb-mini-list-view
|
||||
|
||||
@@ -24,16 +24,6 @@ angular.module("umbraco")
|
||||
return ((spans / $scope.columns) * 100).toFixed(8);
|
||||
};
|
||||
|
||||
$scope.toggleCollection = function(collection, toggle){
|
||||
if(toggle){
|
||||
collection = [];
|
||||
}else{
|
||||
collection = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
/****************
|
||||
Section
|
||||
*****************/
|
||||
@@ -47,8 +37,18 @@ angular.module("umbraco")
|
||||
}
|
||||
|
||||
$scope.currentSection = section;
|
||||
$scope.currentSection.allowAll = section.allowAll || !section.allowed || !section.allowed.length;
|
||||
};
|
||||
|
||||
$scope.toggleAllowed = function (section) {
|
||||
if (section.allowed) {
|
||||
delete section.allowed;
|
||||
}
|
||||
else {
|
||||
section.allowed = [];
|
||||
}
|
||||
}
|
||||
|
||||
$scope.deleteSection = function(section, template) {
|
||||
if ($scope.currentSection === section) {
|
||||
$scope.currentSection = undefined;
|
||||
|
||||
@@ -61,8 +61,7 @@
|
||||
<input type="checkbox"
|
||||
ng-model="currentSection.allowAll"
|
||||
style="float: left; margin-right: 10px;"
|
||||
ng-checked="currentSection.allowed === undefined"
|
||||
ng-change="toggleCollection(currentSection.allowed, currentSection.allowAll)" />
|
||||
ng-change="toggleAllowed(currentSection)" />
|
||||
<localize key="grid_allowAllRowConfigurations"/>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
@@ -22,16 +22,6 @@ function RowConfigController($scope) {
|
||||
return ((spans / $scope.columns) * 100).toFixed(8);
|
||||
};
|
||||
|
||||
$scope.toggleCollection = function(collection, toggle) {
|
||||
if (toggle) {
|
||||
collection = [];
|
||||
}
|
||||
else {
|
||||
collection = null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/****************
|
||||
area
|
||||
*****************/
|
||||
@@ -55,9 +45,19 @@ function RowConfigController($scope) {
|
||||
row.areas.push(cell);
|
||||
}
|
||||
$scope.currentCell = cell;
|
||||
$scope.currentCell.allowAll = cell.allowAll || !cell.allowed || !cell.allowed.length;
|
||||
}
|
||||
};
|
||||
|
||||
$scope.toggleAllowed = function (cell) {
|
||||
if (cell.allowed) {
|
||||
delete cell.allowed;
|
||||
}
|
||||
else {
|
||||
cell.allowed = [];
|
||||
}
|
||||
}
|
||||
|
||||
$scope.deleteArea = function (cell, row) {
|
||||
if ($scope.currentCell === cell) {
|
||||
$scope.currentCell = undefined;
|
||||
|
||||
@@ -72,8 +72,7 @@
|
||||
<label>
|
||||
<input type="checkbox"
|
||||
ng-model="currentCell.allowAll"
|
||||
ng-checked="currentCell.allowed === undefined"
|
||||
ng-change="toggleCollection(currentCell.allowed, currentCell.allowAll)" />
|
||||
ng-change="toggleAllowed(currentCell)" />
|
||||
<localize key="grid_allowAllEditors"/>
|
||||
</label>
|
||||
</li>
|
||||
|
||||
@@ -701,13 +701,17 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
|
||||
}
|
||||
|
||||
getContentTypesCallback(id).then(function (listViewAllowedTypes) {
|
||||
var blueprints = false;
|
||||
$scope.listViewAllowedTypes = listViewAllowedTypes;
|
||||
|
||||
angular.forEach(listViewAllowedTypes, function (allowedType) {
|
||||
angular.forEach(allowedType.blueprints, function (value, key) {
|
||||
var blueprints = false;
|
||||
_.each(listViewAllowedTypes, function (allowedType) {
|
||||
if (_.isEmpty(allowedType.blueprints)) {
|
||||
// this helps the view understand that there are no blueprints available
|
||||
allowedType.blueprints = null;
|
||||
}
|
||||
else {
|
||||
blueprints = true;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (listViewAllowedTypes.length === 1 && blueprints === false) {
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
<umb-dropdown-item ng-repeat="contentType in listViewAllowedTypes track by contentType.key | orderBy:'name':false">
|
||||
<a ng-click="createBlank(entityType,contentType.alias)" prevent-default ng-href="">
|
||||
<i class="{{::contentType.icon}}"></i>
|
||||
{{::contentType.name}} <span ng-show="contentType.blueprints && contentType.blueprints.length != 0" style="text-transform: lowercase;">(<localize key="blueprints_blankBlueprint">blank</localize>)</span>
|
||||
{{::contentType.name}} <span ng-show="contentType.blueprints" style="text-transform: lowercase;">(<localize key="blueprints_blankBlueprint">blank</localize>)</span>
|
||||
</a>
|
||||
<a href="" ng-repeat="(key, value) in contentType.blueprints track by key" ng-click="createFromBlueprint(entityType,contentType.alias , key)" prevent-default>
|
||||
<i class="{{::contentType.icon}}"></i>
|
||||
|
||||
@@ -1379,7 +1379,7 @@ Mange hilsner fra Umbraco robotten
|
||||
<key alias="userInvited">er blevet inviteret</key>
|
||||
<key alias="userInvitedSuccessHelp">En invitation er blevet sendt til den nye bruger med oplysninger om, hvordan man logger ind i Umbraco.</key>
|
||||
<key alias="userinviteWelcomeMessage">Hej og velkommen til Umbraco! På bare 1 minut vil du være klar til at komme i gang, vi skal bare have dig til at oprette en adgangskode og tilføje et billede til din avatar.</key>
|
||||
<key alias="userinviteAvatarMessage">Upload et billede for at gøre det nemt for andre brugere at genkende dig.</key>
|
||||
<key alias="userinviteAvatarMessage">Hvis du uploader et billede af dig selv, gør du det nemt for andre brugere at genkende dig. Klik på cirklen ovenfor for at uploade et billede.</key>
|
||||
<key alias="writer">Forfatter</key>
|
||||
<key alias="change">Skift</key>
|
||||
<key alias="yourProfile">Din profil</key>
|
||||
@@ -1434,8 +1434,8 @@ Mange hilsner fra Umbraco robotten
|
||||
<area alias="recycleBin">
|
||||
<key alias="contentTrashed">Slettet indhold med Id: {0} Relateret til original "parent" med id: {1}</key>
|
||||
<key alias="mediaTrashed">Slettet medie med Id: {0} relateret til original "parent" / mappe med id: {1}</key>
|
||||
<key alias="itemCannotBeRestored">Kan ikke automatisk genoprette dette dokument/medie</key>
|
||||
<key alias="noRestoreRelation">Der findes ikke nogen "Genopret" relation for dette dokument/medie. Brug "Flyt" muligheden fra menuen for at flytte det manuelt.</key>
|
||||
<key alias="restoreUnderRecycled">Det dokument/medie du ønsker at genoprette under ('%0%') er i skraldespanden. Brug "Flyt" muligheden fra menuen for at flytte det manuelt.</key>
|
||||
<key alias="itemCannotBeRestored">Kan ikke automatisk genoprette dette element</key>
|
||||
<key alias="itemCannotBeRestoredHelpText">Der er ikke nogen placering hvor dette element automatisk kan genoprettes. Du kan flytte elementet manuelt i træet nedenfor.</key>
|
||||
<key alias="wasRestored">blev genoprettet under</key>
|
||||
</area>
|
||||
</language>
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
<notifications>
|
||||
<!-- the email that should be used as from mail when umbraco sends a notification -->
|
||||
<!-- you can add a display name to the email like thist: <email>Your display name here <your@email.here></email> -->
|
||||
<email>your@email.here</email>
|
||||
</notifications>
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
</errors>
|
||||
<notifications>
|
||||
<!-- the email that should be used as from mail when umbraco sends a notification -->
|
||||
<!-- you can add a display name to the email like this: <email>Your display name here <your@email.here></email> -->
|
||||
<email>your@email.here</email>
|
||||
</notifications>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace Umbraco.Web.Controllers
|
||||
public class UmbLoginController : SurfaceController
|
||||
{
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public ActionResult HandleLogin([Bind(Prefix = "loginModel")]LoginModel model)
|
||||
{
|
||||
if (ModelState.IsValid == false)
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Umbraco.Web.Controllers
|
||||
public class UmbLoginStatusController : SurfaceController
|
||||
{
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public ActionResult HandleLogout([Bind(Prefix = "logoutModel")]PostRedirectModel model)
|
||||
{
|
||||
if (ModelState.IsValid == false)
|
||||
|
||||
@@ -11,6 +11,7 @@ namespace Umbraco.Web.Controllers
|
||||
public class UmbProfileController : SurfaceController
|
||||
{
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public ActionResult HandleUpdateProfile([Bind(Prefix = "profileModel")] ProfileModel model)
|
||||
{
|
||||
var provider = Core.Security.MembershipProviderExtensions.GetMembersMembershipProvider();
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace Umbraco.Web.Controllers
|
||||
public class UmbRegisterController : SurfaceController
|
||||
{
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public ActionResult HandleRegisterMember([Bind(Prefix = "registerModel")]RegisterModel model)
|
||||
{
|
||||
if (ModelState.IsValid == false)
|
||||
|
||||
@@ -354,6 +354,9 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var emptyContent = Services.ContentService.Create("", parentId, contentType.Alias, Security.GetUserId().ResultOr(0));
|
||||
var mapped = MapToDisplay(emptyContent);
|
||||
// translate the content type name if applicable
|
||||
mapped.ContentTypeName = Services.TextService.UmbracoDictionaryTranslate(mapped.ContentTypeName);
|
||||
mapped.DocumentType.Name = Services.TextService.UmbracoDictionaryTranslate(mapped.DocumentType.Name);
|
||||
|
||||
//remove the listview app if it exists
|
||||
mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList();
|
||||
|
||||
@@ -89,6 +89,25 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
var availableCompositions = Services.ContentTypeService.GetAvailableCompositeContentTypes(source, allContentTypes, filterContentTypes, filterPropertyTypes);
|
||||
|
||||
Func<IContentTypeComposition, IEnumerable<EntityContainer>> getEntityContainers = contentType =>
|
||||
{
|
||||
if (contentType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
switch (type)
|
||||
{
|
||||
case UmbracoObjectTypes.DocumentType:
|
||||
return Services.ContentTypeService.GetContentTypeContainers(contentType as IContentType);
|
||||
case UmbracoObjectTypes.MediaType:
|
||||
return Services.ContentTypeService.GetMediaTypeContainers(contentType as IMediaType);
|
||||
case UmbracoObjectTypes.MemberType:
|
||||
return new EntityContainer[0];
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException("The entity type was not a content type");
|
||||
}
|
||||
};
|
||||
|
||||
var currCompositions = source == null ? new IContentTypeComposition[] { } : source.ContentTypeComposition.ToArray();
|
||||
var compAliases = currCompositions.Select(x => x.Alias).ToArray();
|
||||
var ancestors = availableCompositions.Ancestors.Select(x => x.Alias);
|
||||
@@ -97,9 +116,6 @@ namespace Umbraco.Web.Editors
|
||||
.Select(x => new Tuple<EntityBasic, bool>(Mapper.Map<IContentTypeComposition, EntityBasic>(x.Composition), x.Allowed))
|
||||
.Select(x =>
|
||||
{
|
||||
//translate the name
|
||||
x.Item1.Name = TranslateItem(x.Item1.Name);
|
||||
|
||||
//we need to ensure that the item is enabled if it is already selected
|
||||
// but do not allow it if it is any of the ancestors
|
||||
if (compAliases.Contains(x.Item1.Alias) && ancestors.Contains(x.Item1.Alias) == false)
|
||||
@@ -108,6 +124,14 @@ namespace Umbraco.Web.Editors
|
||||
x = new Tuple<EntityBasic, bool>(x.Item1, true);
|
||||
}
|
||||
|
||||
//translate the name
|
||||
x.Item1.Name = TranslateItem(x.Item1.Name);
|
||||
|
||||
var contentType = allContentTypes.FirstOrDefault(c => c.Key == x.Item1.Key);
|
||||
var containers = getEntityContainers(contentType)?.ToArray();
|
||||
var containerPath = $"/{(containers != null && containers.Any() ? $"{string.Join("/", containers.Select(c => c.Name))}/" : null)}";
|
||||
x.Item1.AdditionalData["containerPath"] = containerPath;
|
||||
|
||||
return x;
|
||||
})
|
||||
.ToList();
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Web;
|
||||
using System.Web.Helpers;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.Mvc.Html;
|
||||
using System.Web.Routing;
|
||||
@@ -222,6 +223,7 @@ namespace Umbraco.Web
|
||||
{
|
||||
_viewContext = viewContext;
|
||||
_method = method;
|
||||
_controllerName = controllerName;
|
||||
_encryptedString = UmbracoHelper.CreateEncryptedRouteString(controllerName, controllerAction, area, additionalRouteVals);
|
||||
}
|
||||
|
||||
@@ -229,6 +231,7 @@ namespace Umbraco.Web
|
||||
private readonly FormMethod _method;
|
||||
private bool _disposed;
|
||||
private readonly string _encryptedString;
|
||||
private readonly string _controllerName;
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
@@ -236,6 +239,16 @@ namespace Umbraco.Web
|
||||
return;
|
||||
this._disposed = true;
|
||||
|
||||
//Detect if the call is targeting UmbRegisterController/UmbProfileController/UmbLoginStatusController/UmbLoginController and if it is we automatically output a AntiForgeryToken()
|
||||
// We have a controllerName and area so we can match
|
||||
if (_controllerName == "UmbRegister"
|
||||
|| _controllerName == "UmbProfile"
|
||||
|| _controllerName == "UmbLoginStatus"
|
||||
|| _controllerName == "UmbLogin")
|
||||
{
|
||||
_viewContext.Writer.Write(AntiForgery.GetHtml().ToString());
|
||||
}
|
||||
|
||||
//write out the hidden surface form routes
|
||||
_viewContext.Writer.Write("<input name='ufprt' type='hidden' value='" + _encryptedString + "' />");
|
||||
|
||||
|
||||
@@ -263,6 +263,7 @@ namespace Umbraco.Web.Trees
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
menu.Items.Add<ActionRestore>(Services.TextService, opensDialog: true);
|
||||
menu.Items.Add<ActionMove>(Services.TextService, opensDialog: true);
|
||||
menu.Items.Add<ActionDelete>(Services.TextService, opensDialog: true);
|
||||
|
||||
menu.Items.Add(new RefreshNode(Services.TextService, true));
|
||||
|
||||
Reference in New Issue
Block a user