Merge remote-tracking branch 'origin/dev-v7.8' into temp-U4-10803
# Conflicts: # src/Umbraco.Web.UI.Client/bower.json
This commit is contained in:
@@ -32,7 +32,7 @@
|
||||
<file src="$BuildTmp$\WebApp\bin\amd64\**" target="UmbracoFiles\bin\amd64" />
|
||||
<file src="$BuildTmp$\WebApp\bin\x86\**" target="UmbracoFiles\bin\x86" />
|
||||
<file src="$BuildTmp$\WebApp\config\splashes\**" target="UmbracoFiles\Config\splashes" />
|
||||
<file src="$BuildTmp$\WebApp\config\BackOfficeTours\**" target="UmbracoFiles\Config\BackOfficeTours" />
|
||||
<file src="$BuildTmp$\WebApp\config\BackOfficeTours\**" target="Content\Config\BackOfficeTours" />
|
||||
<file src="$BuildTmp$\WebApp\umbraco\**" target="UmbracoFiles\umbraco" />
|
||||
<file src="$BuildTmp$\WebApp\umbraco_client\**" target="UmbracoFiles\umbraco_client" />
|
||||
<file src="$BuildTmp$\WebApp\Media\Web.config" target="Content\Media\Web.config" />
|
||||
|
||||
@@ -79,14 +79,14 @@
|
||||
<add sortOrder="1" alias="dataTypes" application="developer"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes(sortOrder)" />
|
||||
|
||||
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2"
|
||||
|
||||
<add initialize="true" sortOrder="2" alias="macros" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MacroTreeController, umbraco"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
<add application="developer" alias="xslt" title="XSLT Files" type="umbraco.loadXslt, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="5"
|
||||
<add initialize="true" sortOrder="5" alias="xslt" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.XsltTreeController, umbraco"
|
||||
xdt:Locator="Match(application,alias)"
|
||||
xdt:Transform="SetAttributes()" />
|
||||
|
||||
|
||||
+1
-1
@@ -12,4 +12,4 @@ using System.Resources;
|
||||
[assembly: AssemblyVersion("1.0.*")]
|
||||
|
||||
[assembly: AssemblyFileVersion("7.8.0")]
|
||||
[assembly: AssemblyInformationalVersion("7.8.0-beta")]
|
||||
[assembly: AssemblyInformationalVersion("7.8.0-beta005")]
|
||||
@@ -146,7 +146,7 @@ namespace Umbraco.Core.Cache
|
||||
#region Insert
|
||||
#endregion
|
||||
|
||||
private class NoopLocker : DisposableObject
|
||||
private class NoopLocker : DisposableObjectSlim
|
||||
{
|
||||
protected override void DisposeResources()
|
||||
{ }
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Umbraco.Core.Collections
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a list of types.
|
||||
/// </summary>
|
||||
/// <remarks>Types in the list are, or derive from, or implement, the base type.</remarks>
|
||||
/// <typeparam name="TBase">The base type.</typeparam>
|
||||
internal class TypeList<TBase>
|
||||
{
|
||||
private readonly List<Type> _list = new List<Type>();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a type to the list.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type to add.</typeparam>
|
||||
public void Add<T>()
|
||||
where T : TBase
|
||||
{
|
||||
_list.Add(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a type is in the list.
|
||||
/// </summary>
|
||||
public bool Contains(Type type)
|
||||
{
|
||||
return _list.Contains(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,11 @@
|
||||
/// </summary>
|
||||
public const string Media = "media";
|
||||
|
||||
/// <summary>
|
||||
/// alias for the macro tree.
|
||||
/// </summary>
|
||||
public const string Macros = "macros";
|
||||
|
||||
/// <summary>
|
||||
/// alias for the datatype tree.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,60 +2,60 @@ using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract implementation of IDisposable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can also be used as a pattern for when inheriting is not possible.
|
||||
///
|
||||
{
|
||||
/// <summary>
|
||||
/// Abstract implementation of IDisposable.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Can also be used as a pattern for when inheriting is not possible.
|
||||
///
|
||||
/// See also: https://msdn.microsoft.com/en-us/library/b1yfkh5e%28v=vs.110%29.aspx
|
||||
/// See also: https://lostechies.com/chrispatterson/2012/11/29/idisposable-done-right/
|
||||
///
|
||||
/// Note: if an object's ctor throws, it will never be disposed, and so if that ctor
|
||||
/// has allocated disposable objects, it should take care of disposing them.
|
||||
/// </remarks>
|
||||
public abstract class DisposableObject : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly object _locko = new object();
|
||||
|
||||
// gets a value indicating whether this instance is disposed.
|
||||
/// </remarks>
|
||||
public abstract class DisposableObject : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly object _locko = new object();
|
||||
|
||||
// gets a value indicating whether this instance is disposed.
|
||||
// for internal tests only (not thread safe)
|
||||
//TODO make this internal + rename "Disposed" when we can break compatibility
|
||||
public bool IsDisposed { get { return _disposed; } }
|
||||
|
||||
// implements IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// finalizer
|
||||
~DisposableObject()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
public bool IsDisposed { get { return _disposed; } }
|
||||
|
||||
// implements IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
// finalizer
|
||||
~DisposableObject()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
|
||||
//TODO make this private, non-virtual when we can break compatibility
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
}
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
DisposeUnmanagedResources();
|
||||
|
||||
if (disposing)
|
||||
DisposeResources();
|
||||
}
|
||||
|
||||
protected abstract void DisposeResources();
|
||||
|
||||
protected virtual void DisposeUnmanagedResources()
|
||||
{ }
|
||||
}
|
||||
DisposeResources();
|
||||
}
|
||||
|
||||
protected abstract void DisposeResources();
|
||||
|
||||
protected virtual void DisposeUnmanagedResources()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// This should not be use if there are ubmanaged resources to be disposed, use DisposableObject instead
|
||||
/// </summary>
|
||||
public abstract class DisposableObjectSlim : IDisposable
|
||||
{
|
||||
private bool _disposed;
|
||||
private readonly object _locko = new object();
|
||||
|
||||
// gets a value indicating whether this instance is disposed.
|
||||
// for internal tests only (not thread safe)
|
||||
internal bool Disposed { get { return _disposed; } }
|
||||
|
||||
// implements IDisposable
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
lock (_locko)
|
||||
{
|
||||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
if (disposing)
|
||||
DisposeResources();
|
||||
}
|
||||
|
||||
protected abstract void DisposeResources();
|
||||
}
|
||||
}
|
||||
@@ -11,8 +11,8 @@ namespace Umbraco.Core
|
||||
/// <summary>
|
||||
/// Starts the timer and invokes a callback upon disposal. Provides a simple way of timing an operation by wrapping it in a <code>using</code> (C#) statement.
|
||||
/// </summary>
|
||||
public class DisposableTimer : DisposableObject
|
||||
{
|
||||
public class DisposableTimer : DisposableObjectSlim
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly LogType? _logType;
|
||||
private readonly IProfiler _profiler;
|
||||
@@ -209,13 +209,13 @@ namespace Umbraco.Core
|
||||
loggerType,
|
||||
startMessage(),
|
||||
completeMessage());
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
|
||||
/// <summary>
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObjectSlim"/> which handles common required locking logic.
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
if (_profiler != null)
|
||||
{
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace Umbraco.Core.Events
|
||||
/// <summary>
|
||||
/// Event messages collection
|
||||
/// </summary>
|
||||
public sealed class EventMessages : DisposableObject
|
||||
public sealed class EventMessages : DisposableObjectSlim
|
||||
{
|
||||
private readonly List<EventMessage> _msgs = new List<EventMessage>();
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Umbraco.Core
|
||||
/// This will use the crypto libs to generate the hash and will try to ensure that
|
||||
/// strings, etc... are not re-allocated so it's not consuming much memory.
|
||||
/// </remarks>
|
||||
internal class HashGenerator : DisposableObject
|
||||
internal class HashGenerator : DisposableObjectSlim
|
||||
{
|
||||
public HashGenerator()
|
||||
{
|
||||
|
||||
@@ -6,7 +6,7 @@ using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Core.Manifest
|
||||
{
|
||||
internal class ManifestWatcher : DisposableObject
|
||||
internal class ManifestWatcher : DisposableObjectSlim
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly List<FileSystemWatcher> _fws = new List<FileSystemWatcher>();
|
||||
|
||||
@@ -394,7 +394,7 @@ namespace Umbraco.Core.ObjectResolution
|
||||
/// WARNING! Do not use this unless you know what you are doing, clear all types registered and instances
|
||||
/// created. Typically only used if a resolver is no longer used in an application and memory is to be GC'd
|
||||
/// </summary>
|
||||
internal void ResetCollections()
|
||||
internal virtual void ResetCollections()
|
||||
{
|
||||
using (new WriteLock(_lock))
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Core.Persistence
|
||||
/// it will create one per context, otherwise it will be a global singleton object which is NOT thread safe
|
||||
/// since we need (at least) a new instance of the database object per thread.
|
||||
/// </remarks>
|
||||
internal class DefaultDatabaseFactory : DisposableObject, IDatabaseFactory2
|
||||
internal class DefaultDatabaseFactory : DisposableObjectSlim, IDatabaseFactory2
|
||||
{
|
||||
private readonly string _connectionStringName;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace Umbraco.Core.Persistence.Repositories
|
||||
/// <remarks>
|
||||
/// This is limited to objects that are based in the umbracoNode-table.
|
||||
/// </remarks>
|
||||
internal class EntityRepository : DisposableObject, IEntityRepository
|
||||
internal class EntityRepository : DisposableObjectSlim, IEntityRepository
|
||||
{
|
||||
private readonly IDatabaseUnitOfWork _work;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ using Umbraco.Core.Persistence.UnitOfWork;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal abstract class FileRepository<TId, TEntity> : DisposableObject, IUnitOfWorkRepository, IRepository<TId, TEntity>
|
||||
internal abstract class FileRepository<TId, TEntity> : DisposableObjectSlim, IUnitOfWorkRepository, IRepository<TId, TEntity>
|
||||
where TEntity : IFile
|
||||
{
|
||||
private IUnitOfWork _work;
|
||||
|
||||
@@ -10,7 +10,7 @@ using Umbraco.Core.Scoping;
|
||||
|
||||
namespace Umbraco.Core.Persistence.Repositories
|
||||
{
|
||||
internal abstract class RepositoryBase : DisposableObject
|
||||
internal abstract class RepositoryBase : DisposableObjectSlim
|
||||
{
|
||||
private readonly IScopeUnitOfWork _work;
|
||||
private readonly CacheHelper _globalCache;
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Umbraco.Core.Persistence.UnitOfWork
|
||||
/// <summary>
|
||||
/// Represents a scoped unit of work.
|
||||
/// </summary>
|
||||
internal class ScopeUnitOfWork : DisposableObject, IScopeUnitOfWork
|
||||
internal class ScopeUnitOfWork : DisposableObjectSlim, IScopeUnitOfWork
|
||||
{
|
||||
private readonly Queue<Operation> _operations = new Queue<Operation>();
|
||||
private readonly IsolationLevel _isolationLevel;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Security;
|
||||
using System.Security.Permissions;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("Umbraco.Core")]
|
||||
@@ -12,8 +10,8 @@ using System.Security.Permissions;
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyProduct("Umbraco CMS")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
@@ -50,6 +48,8 @@ using System.Security.Permissions;
|
||||
[assembly: InternalsVisibleTo("Umbraco.Forms.Core.Providers")]
|
||||
[assembly: InternalsVisibleTo("Umbraco.Forms.Web")]
|
||||
|
||||
[assembly: InternalsVisibleTo("Umbraco.Headless")]
|
||||
|
||||
//allow this to be mocked in our unit tests
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ using Task = System.Threading.Tasks.Task;
|
||||
|
||||
namespace Umbraco.Core.Security
|
||||
{
|
||||
public class BackOfficeUserStore : DisposableObject,
|
||||
public class BackOfficeUserStore : DisposableObjectSlim,
|
||||
IUserStore<BackOfficeIdentityUser, int>,
|
||||
IUserPasswordStore<BackOfficeIdentityUser, int>,
|
||||
IUserEmailStore<BackOfficeIdentityUser, int>,
|
||||
@@ -58,7 +58,7 @@ namespace Umbraco.Core.Security
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObjectSlim"/> which handles common required locking logic.
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
|
||||
@@ -178,6 +178,7 @@
|
||||
<Compile Include="CodeAnnotations\UmbracoProposedPublicAttribute.cs" />
|
||||
<Compile Include="Collections\DeepCloneableList.cs" />
|
||||
<Compile Include="Collections\ListCloneBehavior.cs" />
|
||||
<Compile Include="Collections\TypeList.cs" />
|
||||
<Compile Include="ConcurrentHashSet.cs" />
|
||||
<Compile Include="Configuration\BaseRest\IBaseRestSection.cs" />
|
||||
<Compile Include="Configuration\BaseRest\IExtension.cs" />
|
||||
@@ -348,6 +349,7 @@
|
||||
<Compile Include="Deploy\Direction.cs" />
|
||||
<Compile Include="Deploy\IFileType.cs" />
|
||||
<Compile Include="Deploy\IFileTypeCollection.cs" />
|
||||
<Compile Include="DisposableObjectSlim.cs" />
|
||||
<Compile Include="EmailSender.cs" />
|
||||
<Compile Include="Events\EventDefinitionFilter.cs" />
|
||||
<Compile Include="Events\IDeletingMediaFilesEventArgs.cs" />
|
||||
|
||||
@@ -548,7 +548,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
runner.Shutdown(false, true);
|
||||
|
||||
// check that task has been disposed (timer has been killed, etc)
|
||||
Assert.IsTrue(task.IsDisposed);
|
||||
Assert.IsTrue(task.Disposed);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Tests.TestHelpers.ControllerTesting;
|
||||
using Umbraco.Tests.TestHelpers.Entities;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Features;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
using IUser = Umbraco.Core.Models.Membership.IUser;
|
||||
|
||||
@@ -26,6 +27,11 @@ namespace Umbraco.Tests.Web.Controllers
|
||||
[TestFixture]
|
||||
public class UsersControllerTests : BaseDatabaseFactoryTest
|
||||
{
|
||||
protected override void FreezeResolution()
|
||||
{
|
||||
FeaturesResolver.Current = new FeaturesResolver(new UmbracoFeatures());
|
||||
base.FreezeResolution();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async void Save_User()
|
||||
|
||||
@@ -33,56 +33,44 @@
|
||||
"clipboard": "1.7.1",
|
||||
"font-awesome": "~4.2"
|
||||
},
|
||||
|
||||
"install": {
|
||||
|
||||
"path": "lib-bower",
|
||||
|
||||
"ignore": [
|
||||
"font-awesome",
|
||||
"angular",
|
||||
"bootstrap",
|
||||
"codemirror"
|
||||
],
|
||||
|
||||
"sources": {
|
||||
"moment": "bower_components/moment/min/moment-with-locales.js",
|
||||
|
||||
"underscore": [
|
||||
"bower_components/underscore/underscore-min.js",
|
||||
"bower_components/underscore/underscore-min.map"
|
||||
],
|
||||
|
||||
"jquery": [
|
||||
"bower_components/jquery/dist/jquery.min.js",
|
||||
"bower_components/jquery/dist/jquery.min.map"
|
||||
],
|
||||
|
||||
"angular-dynamic-locale": [
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js",
|
||||
"bower_components/angular-dynamic-locale/tmhDynamicLocale.min.js.map"
|
||||
],
|
||||
|
||||
"angular-local-storage": [
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js",
|
||||
"bower_components/angular-local-storage/dist/angular-local-storage.min.js.map"
|
||||
],
|
||||
|
||||
"tinymce": [
|
||||
"bower_components/tinymce/tinymce.min.js"
|
||||
],
|
||||
|
||||
"typeahead.js": "bower_components/typeahead.js/dist/typeahead.bundle.min.js",
|
||||
|
||||
"rgrove-lazyload":"bower_components/rgrove-lazyload/lazyload.js",
|
||||
|
||||
"ng-file-upload":"bower_components/ng-file-upload/ng-file-upload.min.js",
|
||||
|
||||
"jquery-ui":"bower_components/jquery-ui/jquery-ui.min.js",
|
||||
|
||||
"jquery-migrate":"bower_components/jquery-migrate/jquery-migrate.min.js",
|
||||
|
||||
"rgrove-lazyload": "bower_components/rgrove-lazyload/lazyload.js",
|
||||
"ng-file-upload": "bower_components/ng-file-upload/ng-file-upload.min.js",
|
||||
"jquery-ui": "bower_components/jquery-ui/jquery-ui.min.js",
|
||||
"jquery-migrate": "bower_components/jquery-migrate/jquery-migrate.min.js",
|
||||
"clipboard": "bower_components/clipboard/dist/clipboard.min.js"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"moment": ">=2.19.3 <3.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,52 +16,16 @@ function eventsService($q, $rootScope) {
|
||||
|
||||
return {
|
||||
|
||||
/** raise an event with a given name, returns an array of promises for each listener */
|
||||
emit: function (name, args) {
|
||||
/** raise an event with a given name */
|
||||
emit: function (name, args) {
|
||||
|
||||
//there are no listeners
|
||||
if (!$rootScope.$$listeners[name]) {
|
||||
return;
|
||||
//return [];
|
||||
}
|
||||
|
||||
//send the event
|
||||
$rootScope.$emit(name, args);
|
||||
|
||||
|
||||
//PP: I've commented out the below, since we currently dont
|
||||
// expose the eventsService as a documented api
|
||||
// and think we need to figure out our usecases for this
|
||||
// since the below modifies the return value of the then on() method
|
||||
/*
|
||||
//setup a deferred promise for each listener
|
||||
var deferred = [];
|
||||
for (var i = 0; i < $rootScope.$$listeners[name].length; i++) {
|
||||
deferred.push($q.defer());
|
||||
}*/
|
||||
|
||||
//create a new event args object to pass to the
|
||||
// $emit containing methods that will allow listeners
|
||||
// to return data in an async if required
|
||||
/*
|
||||
var eventArgs = {
|
||||
args: args,
|
||||
reject: function (a) {
|
||||
deferred.pop().reject(a);
|
||||
},
|
||||
resolve: function (a) {
|
||||
deferred.pop().resolve(a);
|
||||
}
|
||||
};*/
|
||||
|
||||
|
||||
|
||||
/*
|
||||
//return an array of promises
|
||||
var promises = _.map(deferred, function(p) {
|
||||
return p.promise;
|
||||
});
|
||||
return promises;*/
|
||||
},
|
||||
|
||||
/** subscribe to a method, or use scope.$on = same thing */
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* Registers all tours from the server and returns a promise
|
||||
*/
|
||||
function registerAllTours() {
|
||||
tours = [];
|
||||
return tourResource.getTours().then(function(tourFiles) {
|
||||
angular.forEach(tourFiles, function (tourFile) {
|
||||
angular.forEach(tourFile.tours, function(newTour) {
|
||||
@@ -218,7 +219,38 @@
|
||||
var deferred = $q.defer();
|
||||
var tours = getTours();
|
||||
setTourStatuses(tours).then(function() {
|
||||
var groupedTours = _.groupBy(tours, "group");
|
||||
var groupedTours = [];
|
||||
tours.forEach(function (item) {
|
||||
|
||||
var groupExists = false;
|
||||
var newGroup = {
|
||||
"group": "",
|
||||
"tours": []
|
||||
};
|
||||
|
||||
groupedTours.forEach(function(group){
|
||||
// extend existing group if it is already added
|
||||
if(group.group === item.group) {
|
||||
if(item.groupOrder) {
|
||||
group.groupOrder = item.groupOrder
|
||||
}
|
||||
groupExists = true;
|
||||
group.tours.push(item)
|
||||
}
|
||||
});
|
||||
|
||||
// push new group to array if it doesn't exist
|
||||
if(!groupExists) {
|
||||
newGroup.group = item.group;
|
||||
if(item.groupOrder) {
|
||||
newGroup.groupOrder = item.groupOrder
|
||||
}
|
||||
newGroup.tours.push(item);
|
||||
groupedTours.push(newGroup);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
deferred.resolve(groupedTours);
|
||||
});
|
||||
return deferred.promise;
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
|
||||
function showTourButton(index, tourGroup) {
|
||||
if(index !== 0) {
|
||||
var prevTour = tourGroup[index - 1];
|
||||
var prevTour = tourGroup.tours[index - 1];
|
||||
if(prevTour.completed) {
|
||||
return true;
|
||||
}
|
||||
@@ -147,12 +147,12 @@
|
||||
// Finding out, how many tours are completed for the progress circle
|
||||
angular.forEach(vm.tours, function(group){
|
||||
var completedTours = 0;
|
||||
angular.forEach(group, function(tour){
|
||||
angular.forEach(group.tours, function(tour){
|
||||
if(tour.completed) {
|
||||
completedTours++;
|
||||
}
|
||||
});
|
||||
group.completedPercentage = Math.round((completedTours/group.length)*100);
|
||||
group.completedPercentage = Math.round((completedTours/group.tours.length)*100);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,23 +12,23 @@
|
||||
|
||||
<h5 style="margin-bottom: 10px; margin-top: 0;">Tours</h5>
|
||||
|
||||
<div ng-repeat="(key,value) in vm.tours" style="margin-bottom: 5px;">
|
||||
<div ng-repeat="tourGroup in vm.tours | orderBy:'groupOrder'" style="margin-bottom: 5px;">
|
||||
|
||||
<div class="umb-help-list">
|
||||
|
||||
<a href="" class="umb-help-list-item umb-help-list-item__content flex items-center justify-between" style="text-decoration: none;" ng-click="value.open = !value.open">
|
||||
<h5 class="umb-help-list-item__group-title"><i style="margin-right: 2px;text-decoration: none;" ng-class="{'icon-navigation-right': !value.open, 'icon-navigation-down': value.open}"></i>
|
||||
<span ng-if="key !== 'undefined'">{{key}}</span>
|
||||
<span ng-if="key === 'undefined'">Other</span>
|
||||
<a href="" class="umb-help-list-item umb-help-list-item__content flex items-center justify-between" style="text-decoration: none;" ng-click="tourGroup.open = !tourGroup.open">
|
||||
<h5 class="umb-help-list-item__group-title"><i style="margin-right: 2px;text-decoration: none;" ng-class="{'icon-navigation-right': !tourGroup.open, 'icon-navigation-down': tourGroup.open}"></i>
|
||||
<span ng-if="tourGroup.group !== 'undefined'">{{tourGroup.group}}</span>
|
||||
<span ng-if="tourGroup.group === 'undefined'">Other</span>
|
||||
</h5>
|
||||
<umb-progress-circle
|
||||
percentage="{{value.completedPercentage}}"
|
||||
percentage="{{tourGroup.completedPercentage}}"
|
||||
size="40">
|
||||
</umb-progress-circle>
|
||||
</a>
|
||||
|
||||
<div ng-if="value.open">
|
||||
<div data-element="tour-{{tour.alias}}" class="umb-help-list-item" ng-repeat="tour in value">
|
||||
<div ng-if="tourGroup.open">
|
||||
<div data-element="tour-{{tour.alias}}" class="umb-help-list-item" ng-repeat="tour in tourGroup.tours">
|
||||
<div class="umb-help-list-item__content justify-between">
|
||||
<div class="flex items-center">
|
||||
<div ng-if="!tour.completed" class="umb-number-badge umb-number-badge--xs umb-help-list-item__icon">{{ $index + 1 }}</div>
|
||||
@@ -36,7 +36,7 @@
|
||||
<span ng-class="{'strike': tour.completed}" class="umb-help-list-item__title">{{ tour.name }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<umb-button ng-if="!tour.completed && vm.showTourButton($index, value)" button-style="primary" size="xxs" type="button" label="Start" action="vm.startTour(tour)"></umb-button>
|
||||
<umb-button ng-if="!tour.completed && vm.showTourButton($index, tourGroup)" button-style="primary" size="xxs" type="button" label="Start" action="vm.startTour(tour)"></umb-button>
|
||||
<umb-button ng-if="tour.completed" size="xxs" type="button" label="Rerun" action="vm.startTour(tour)"></umb-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function UserEditController($scope, $timeout, $location, $routeParams, formHelper, usersResource, userService, contentEditingHelper, localizationService, notificationsService, mediaHelper, Upload, umbRequestHelper, usersHelper, authResource, dateHelper) {
|
||||
function UserEditController($scope, eventsService, $q, $timeout, $location, $routeParams, formHelper, usersResource, userService, contentEditingHelper, localizationService, notificationsService, mediaHelper, Upload, umbRequestHelper, usersHelper, authResource, dateHelper) {
|
||||
|
||||
var vm = this;
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
vm.maxFileSize = Umbraco.Sys.ServerVariables.umbracoSettings.maxFileSize + "KB";
|
||||
vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
|
||||
vm.usernameIsEmail = Umbraco.Sys.ServerVariables.umbracoSettings.usernameIsEmail;
|
||||
|
||||
|
||||
//create the initial model for change password
|
||||
vm.changePasswordModel = {
|
||||
config: {},
|
||||
@@ -33,6 +33,7 @@
|
||||
vm.unlockUser = unlockUser;
|
||||
vm.clearAvatar = clearAvatar;
|
||||
vm.save = save;
|
||||
|
||||
vm.toggleChangePassword = toggleChangePassword;
|
||||
|
||||
function init() {
|
||||
@@ -117,38 +118,83 @@
|
||||
|
||||
function save() {
|
||||
|
||||
vm.page.saveButtonState = "busy";
|
||||
vm.user.resetPasswordValue = null;
|
||||
if (formHelper.submitForm({ scope: $scope, statusMessage: vm.labels.saving })) {
|
||||
|
||||
//anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here
|
||||
if(vm.user.changePassword) {
|
||||
vm.user.changePassword.reset = !vm.user.changePassword.oldPassword && !vm.user.isCurrentUser;
|
||||
//anytime a user is changing another user's password, we are in effect resetting it so we need to set that flag here
|
||||
if (vm.user.changePassword) {
|
||||
vm.user.changePassword.reset = !vm.user.changePassword.oldPassword && !vm.user.isCurrentUser;
|
||||
}
|
||||
|
||||
vm.page.saveButtonState = "busy";
|
||||
vm.user.resetPasswordValue = null;
|
||||
|
||||
//save current nav to be restored later so that the tabs dont change
|
||||
var currentNav = vm.user.navigation;
|
||||
|
||||
usersResource.saveUser(vm.user)
|
||||
.then(function (saved) {
|
||||
|
||||
//if the user saved, then try to execute all extended save options
|
||||
extendedSave(saved).then(function(result) {
|
||||
//if all is good, then reset the form
|
||||
formHelper.resetForm({ scope: $scope, notifications: saved.notifications });
|
||||
}, function(err) {
|
||||
//otherwise show the notifications for the user being saved
|
||||
formHelper.showNotifications(saved);
|
||||
});
|
||||
|
||||
vm.user = _.omit(saved, "navigation");
|
||||
//restore
|
||||
vm.user.navigation = currentNav;
|
||||
setUserDisplayState();
|
||||
formatDatesToLocal(vm.user);
|
||||
|
||||
vm.changePasswordModel.isChanging = false;
|
||||
//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.page.saveButtonState = "success";
|
||||
|
||||
}, function (err) {
|
||||
|
||||
contentEditingHelper.handleSaveError({
|
||||
redirectOnFailure: false,
|
||||
err: err
|
||||
});
|
||||
//show any notifications
|
||||
if (err.data) {
|
||||
formHelper.showNotifications(err.data);
|
||||
}
|
||||
vm.page.saveButtonState = "error";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
contentEditingHelper.contentEditorPerformSave({
|
||||
statusMessage: vm.labels.saving,
|
||||
saveMethod: usersResource.saveUser,
|
||||
scope: $scope,
|
||||
content: vm.user,
|
||||
// We do not redirect on failure for users - this is because it is not possible to actually save a user
|
||||
// when server side validation fails - as opposed to content where we are capable of saving the content
|
||||
// item if server side validation fails
|
||||
redirectOnFailure: false,
|
||||
rebindCallback: function (orignal, saved) { }
|
||||
}).then(function (saved) {
|
||||
/**
|
||||
* Used to emit the save event and await any async operations being performed by editor extensions
|
||||
* @param {any} savedUser
|
||||
*/
|
||||
function extendedSave(savedUser) {
|
||||
|
||||
vm.user = saved;
|
||||
setUserDisplayState();
|
||||
formatDatesToLocal(vm.user);
|
||||
//used to track any promises added by the event handlers to be awaited
|
||||
var promises = [];
|
||||
|
||||
var args = {
|
||||
//getPromise: getPromise,
|
||||
user: savedUser,
|
||||
//a promise can be added by the event handler if the handler needs an async operation to be awaited
|
||||
addPromise: function (p) {
|
||||
promises.push(p);
|
||||
}
|
||||
};
|
||||
|
||||
vm.changePasswordModel.isChanging = false;
|
||||
vm.page.saveButtonState = "success";
|
||||
|
||||
//the user has a password if they are not states: Invited, NoCredentials
|
||||
vm.changePasswordModel.config.hasPassword = vm.user.userState !== 3 && vm.user.userState !== 4;
|
||||
}, function (err) {
|
||||
vm.page.saveButtonState = "error";
|
||||
});
|
||||
//emit the event
|
||||
eventsService.emit("editors.user.editController.save", args);
|
||||
|
||||
//await all promises to complete
|
||||
var resultPromise = $q.all(promises);
|
||||
|
||||
return resultPromise;
|
||||
}
|
||||
|
||||
function goToPage(ancestor) {
|
||||
|
||||
@@ -1,406 +1,27 @@
|
||||
<div ng-controller="Umbraco.Editors.Users.UserController as vm" class="clearfix">
|
||||
|
||||
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
|
||||
|
||||
<form name="editUserForm" novalidate val-form-manager>
|
||||
|
||||
<umb-editor-view>
|
||||
|
||||
<umb-editor-header
|
||||
name="vm.user.name"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
hide-alias="true">
|
||||
<umb-editor-header name="vm.user.name"
|
||||
hide-icon="true"
|
||||
hide-description="true"
|
||||
hide-alias="true"
|
||||
navigation="vm.user.navigation">
|
||||
</umb-editor-header>
|
||||
|
||||
|
||||
<umb-editor-container>
|
||||
|
||||
<umb-load-indicator
|
||||
ng-if="vm.loading">
|
||||
</umb-load-indicator>
|
||||
|
||||
<div ng-if="!vm.loading" class="umb-packages-view-wrapper" style="padding: 0;">
|
||||
|
||||
<div class="umb-package-details">
|
||||
<div ng-if="!vm.loading" class="umb-packages-view-wrapper" style="padding: 0;">
|
||||
|
||||
<div class="umb-package-details__main-content">
|
||||
|
||||
<umb-box>
|
||||
|
||||
<umb-box-header title-key="user_profile"></umb-box-header>
|
||||
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-control-group label="@general_email" required="true">
|
||||
|
||||
<input
|
||||
type="email"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enteremail"
|
||||
class="input-block-level"
|
||||
ng-model="vm.user.email"
|
||||
umb-auto-focus name="email"
|
||||
val-email
|
||||
required
|
||||
val-server-field="Email" />
|
||||
<span class="help-inline" val-msg-for="email" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="email" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@general_username" ng-if="!vm.usernameIsEmail" required="true">
|
||||
<input
|
||||
type="text"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enterusername"
|
||||
class="input-block-level"
|
||||
ng-model="vm.user.username"
|
||||
umb-auto-focus name="username"
|
||||
required
|
||||
val-server-field="Username" />
|
||||
<span class="help-inline" val-msg-for="username" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="username" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@user_language" description="@user_languageHelp">
|
||||
<select
|
||||
class="input-block-level"
|
||||
ng-model="vm.user.culture"
|
||||
ng-options="key as value for (key, value) in vm.user.availableCultures"
|
||||
name="culture"
|
||||
required
|
||||
val-server-field="Culture">
|
||||
</select>
|
||||
<span class="help-inline" val-msg-for="culture" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="culture" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-header title-key="user_assignAccess"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-control-group style="margin-bottom: 25px;" label="@general_groups" description="@user_groupsHelp" required="true">
|
||||
|
||||
<umb-user-group-preview
|
||||
ng-repeat="userGroup in vm.user.userGroups"
|
||||
icon="userGroup.icon"
|
||||
name="userGroup.name"
|
||||
sections="userGroup.sections"
|
||||
content-start-node="userGroup.contentStartNode"
|
||||
media-start-node="userGroup.mediaStartNode"
|
||||
allow-remove="!vm.user.isCurrentUser"
|
||||
on-remove="vm.removeSelectedItem($index, vm.user.userGroups)">
|
||||
</umb-user-group-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="vm.openUserGroupPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group style="margin-bottom: 25px;" label="@user_startnodes" description="@user_startnodeshelp">
|
||||
|
||||
<umb-node-preview
|
||||
style="max-width: 100%;"
|
||||
ng-repeat="node in vm.user.startContentIds"
|
||||
icon="node.icon"
|
||||
name="node.name"
|
||||
allow-remove="!vm.user.isCurrentUser"
|
||||
on-remove="vm.removeSelectedItem($index, vm.user.startContentIds)">
|
||||
</umb-node-preview>
|
||||
|
||||
<umb-node-preview
|
||||
ng-if="vm.user.startContentIds.length === 0 && vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
name="vm.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="vm.openContentPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@user_mediastartnodes" description="@user_mediastartnodeshelp">
|
||||
|
||||
<umb-node-preview
|
||||
style="max-width: 100%;"
|
||||
ng-repeat="node in vm.user.startMediaIds"
|
||||
icon="node.icon"
|
||||
name="node.name"
|
||||
allow-remove="!vm.user.isCurrentUser"
|
||||
on-remove="vm.removeSelectedItem($index, vm.user.startMediaIds)">
|
||||
</umb-node-preview>
|
||||
|
||||
<umb-node-preview
|
||||
ng-if="vm.user.startMediaIds.length === 0 && vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
name="vm.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!vm.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="vm.openMediaPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-header title-key="user_access" description-key="user_accessHelp"></umb-box-header>
|
||||
<umb-box-content class="block-form" style="padding-bottom: 0;">
|
||||
|
||||
<umb-control-group label="@sections_content">
|
||||
<umb-node-preview
|
||||
style="max-width: 100%;"
|
||||
ng-repeat="node in vm.user.calculatedStartContentIds"
|
||||
icon="node.icon"
|
||||
name="node.name">
|
||||
</umb-node-preview>
|
||||
<umb-node-preview
|
||||
ng-if="vm.user.calculatedStartContentIds.length === 0"
|
||||
style="max-width: 100%;"
|
||||
name="vm.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@sections_media">
|
||||
<umb-node-preview
|
||||
style="max-width: 100%;"
|
||||
ng-repeat="node in vm.user.calculatedStartMediaIds"
|
||||
icon="node.icon"
|
||||
name="node.name">
|
||||
</umb-node-preview>
|
||||
<umb-node-preview
|
||||
ng-if="vm.user.calculatedStartMediaIds.length === 0"
|
||||
style="max-width: 100%;"
|
||||
name="vm.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
</umb-control-group>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__sidebar">
|
||||
|
||||
<div class="umb-package-details__section">
|
||||
|
||||
<!-- Avatar -->
|
||||
<div style="margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #d8d7d9;">
|
||||
<ng-form name="avatarForm" class="flex flex-column justify-center items-center">
|
||||
|
||||
<umb-avatar
|
||||
style="margin-bottom: 15px;"
|
||||
color="secondary"
|
||||
size="xxl"
|
||||
name="{{vm.user.name}}"
|
||||
img-src="{{vm.user.avatars[3]}}"
|
||||
img-srcset="{{vm.user.avatars[4]}} 2x, {{vm.user.avatars[4]}} 3x">
|
||||
</umb-avatar>
|
||||
|
||||
<umb-progress-bar
|
||||
style="max-width: 120px;"
|
||||
ng-if="vm.avatarFile.uploadStatus === 'uploading'"
|
||||
progress="{{ vm.avatarFile.uploadProgress }}"
|
||||
size="s">
|
||||
</umb-progress-bar>
|
||||
|
||||
<div class="flex items-center" ng-if="vm.avatarFile.uploadStatus !== 'uploading'">
|
||||
|
||||
<a href=""
|
||||
class="umb-user-group-preview__action"
|
||||
ngf-select ng-model="filesHolder"
|
||||
ngf-change="changeAvatar($files, $event)"
|
||||
ngf-multiple="false"
|
||||
ngf-pattern="{{vm.acceptedFileTypes}}"
|
||||
ngf-max-size="{{ vm.maxFileSize }}">
|
||||
<localize key="user_changePhoto">Change photo</localize>
|
||||
</a>
|
||||
|
||||
<a href=""
|
||||
ng-if="vm.user.avatars"
|
||||
class="umb-user-group-preview__action umb-user-group-preview__action--red"
|
||||
ng-click="vm.clearAvatar()"
|
||||
prevent-default>
|
||||
<localize key="user_removePhoto">Remove photo</localize>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="margin-bottom: 20px;">
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button
|
||||
ng-if="vm.user.userDisplayState.key === 'Disabled' && !vm.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[success,block]"
|
||||
state="vm.enableUserButtonState"
|
||||
action="vm.enableUser()"
|
||||
label="Enable"
|
||||
label-key="actions_enable"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button
|
||||
ng-if="vm.user.userDisplayState.key === 'LockedOut' && !vm.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[success,block]"
|
||||
state="vm.unlockUserButtonState"
|
||||
action="vm.unlockUser()"
|
||||
label="Unlock"
|
||||
label-key="actions_unlock"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button
|
||||
ng-if="vm.user.userDisplayState.key !== 'Disabled' && !vm.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[info,block]"
|
||||
action="vm.disableUser()"
|
||||
state="vm.disableUserButtonState"
|
||||
label="Disable"
|
||||
label-key="actions_disable"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<umb-button
|
||||
type="button"
|
||||
button-style="[info,block]"
|
||||
action="vm.toggleChangePassword()"
|
||||
label="Change password"
|
||||
label-key="general_changePassword"
|
||||
state="changePasswordButtonState"
|
||||
ng-if="vm.changePasswordModel.isChanging === false"
|
||||
size="s">
|
||||
</umb-button>
|
||||
|
||||
<ng-form ng-if="vm.changePasswordModel.isChanging" name="passwordForm" class="block-form" val-form-manager>
|
||||
|
||||
<change-password
|
||||
password-values="vm.user.changePassword"
|
||||
config="vm.changePasswordModel.config">
|
||||
</change-password>
|
||||
|
||||
<umb-button
|
||||
type="button"
|
||||
action="vm.toggleChangePassword()"
|
||||
label="Cancel"
|
||||
label-key="general_cancel"
|
||||
button-style="cancel">
|
||||
</umb-button>
|
||||
|
||||
</ng-form>
|
||||
|
||||
<div ng-if="vm.user.resetPasswordValue">
|
||||
<p><br />Password reset to value: <strong>{{vm.user.resetPasswordValue}}</strong></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- User stats -->
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="general_status">Status</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<umb-badge style="margin-top: 4px;" size="s" color="{{vm.user.userDisplayState.color}}">
|
||||
{{vm.user.userDisplayState.name}}
|
||||
</umb-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastLogin">Last login</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="vm.user.lastLoginDate">{{ vm.user.formattedLastLogin }}</span>
|
||||
<span ng-if="!vm.user.lastLoginDate">{{ vm.user.name | umbWordLimit:1 }} <localize key="user_noLogin">has not logged in yet</localize></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_failedPasswordAttempts">Failed login attempts</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ vm.user.failedPasswordAttempts }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastLockoutDate">Last lockout date</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="vm.user.lastLockoutDate === '0001-01-01T00:00:00'">
|
||||
{{ vm.user.name | umbWordLimit:1 }} <localize key="user_noLockouts">hasn't been locked out</localize>
|
||||
</span>
|
||||
<span ng-if="vm.user.lastLockoutDate !== '0001-01-01T00:00:00'">{{ vm.user.formattedLastLockoutDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastPasswordChangeDate">Password is last changed</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="vm.user.lastPasswordChangeDate === '0001-01-01T00:00:00'">
|
||||
<localize key="user_noPasswordChange">The password hasn't been changed</localize>
|
||||
</span>
|
||||
<span ng-if="vm.user.lastPasswordChangeDate !== '0001-01-01T00:00:00'">{{ vm.user.formattedLastPasswordChangeDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_createDate">User is created</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ vm.user.formattedCreateDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_updateDate">User is last updated</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ vm.user.formattedUpdateDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<umb-editor-sub-views
|
||||
ng-if="!vm.loading"
|
||||
sub-views="vm.user.navigation"
|
||||
model="vm">
|
||||
</umb-editor-sub-views>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -410,33 +31,30 @@
|
||||
|
||||
<umb-editor-footer-content-left>
|
||||
|
||||
<umb-breadcrumbs
|
||||
ancestors="vm.breadcrumbs"
|
||||
allow-on-open="true"
|
||||
on-open="vm.goToPage(ancestor)">
|
||||
<umb-breadcrumbs ancestors="vm.breadcrumbs"
|
||||
allow-on-open="true"
|
||||
on-open="vm.goToPage(ancestor)">
|
||||
</umb-breadcrumbs>
|
||||
|
||||
</umb-editor-footer-content-left>
|
||||
|
||||
<umb-editor-footer-content-right>
|
||||
|
||||
<umb-button
|
||||
type="button"
|
||||
action="vm.goToPage(vm.breadcrumbs[0])"
|
||||
label="Return to list"
|
||||
label-key="buttons_returnToList"
|
||||
disabled="vm.loading">
|
||||
<umb-button type="button"
|
||||
action="vm.goToPage(vm.breadcrumbs[0])"
|
||||
label="Return to list"
|
||||
label-key="buttons_returnToList"
|
||||
disabled="vm.loading">
|
||||
</umb-button>
|
||||
|
||||
<umb-button
|
||||
type="button"
|
||||
action="vm.save()"
|
||||
state="vm.page.saveButtonState"
|
||||
button-style="success"
|
||||
shortcut="ctrl+s"
|
||||
label="Save"
|
||||
label-key="buttons_save"
|
||||
disabled="vm.loading">
|
||||
<umb-button type="button"
|
||||
action="vm.save()"
|
||||
state="vm.page.saveButtonState"
|
||||
button-style="success"
|
||||
shortcut="ctrl+s"
|
||||
label="Save"
|
||||
label-key="buttons_save"
|
||||
disabled="vm.loading">
|
||||
</umb-button>
|
||||
|
||||
</umb-editor-footer-content-right>
|
||||
@@ -447,25 +65,22 @@
|
||||
|
||||
</form>
|
||||
|
||||
<umb-overlay
|
||||
ng-if="vm.userGroupPicker.show"
|
||||
model="vm.userGroupPicker"
|
||||
view="vm.userGroupPicker.view"
|
||||
position="right">
|
||||
<umb-overlay ng-if="vm.userGroupPicker.show"
|
||||
model="vm.userGroupPicker"
|
||||
view="vm.userGroupPicker.view"
|
||||
position="right">
|
||||
</umb-overlay>
|
||||
|
||||
<umb-overlay
|
||||
ng-if="vm.contentPicker.show"
|
||||
model="vm.contentPicker"
|
||||
view="vm.contentPicker.view"
|
||||
position="right">
|
||||
<umb-overlay ng-if="vm.contentPicker.show"
|
||||
model="vm.contentPicker"
|
||||
view="vm.contentPicker.view"
|
||||
position="right">
|
||||
</umb-overlay>
|
||||
|
||||
<umb-overlay
|
||||
ng-if="vm.mediaPicker.show"
|
||||
model="vm.mediaPicker"
|
||||
view="vm.mediaPicker.view"
|
||||
position="right">
|
||||
<umb-overlay ng-if="vm.mediaPicker.show"
|
||||
model="vm.mediaPicker"
|
||||
view="vm.mediaPicker.view"
|
||||
position="right">
|
||||
</umb-overlay>
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,361 @@
|
||||
<div class="umb-package-details">
|
||||
|
||||
<div class="umb-package-details__main-content">
|
||||
|
||||
<umb-box>
|
||||
|
||||
<umb-box-header title-key="user_profile"></umb-box-header>
|
||||
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-control-group label="@general_email" required="true">
|
||||
|
||||
<input type="email"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enteremail"
|
||||
class="input-block-level"
|
||||
ng-model="model.user.email"
|
||||
umb-auto-focus name="email"
|
||||
val-email
|
||||
required
|
||||
val-server-field="Email" />
|
||||
<span class="help-inline" val-msg-for="email" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="email" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@general_username" ng-if="!model.usernameIsEmail" required="true">
|
||||
<input type="text"
|
||||
localize="placeholder"
|
||||
placeholder="@placeholders_enterusername"
|
||||
class="input-block-level"
|
||||
ng-model="model.user.username"
|
||||
umb-auto-focus name="username"
|
||||
required
|
||||
val-server-field="Username" />
|
||||
<span class="help-inline" val-msg-for="username" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="username" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@user_language" description="@user_languageHelp">
|
||||
<select class="input-block-level"
|
||||
ng-model="model.user.culture"
|
||||
ng-options="key as value for (key, value) in model.user.availableCultures"
|
||||
name="culture"
|
||||
required
|
||||
val-server-field="Culture"></select>
|
||||
<span class="help-inline" val-msg-for="culture" val-toggle-msg="required"><localize key="general_required">Required</localize></span>
|
||||
<span class="help-inline" val-msg-for="culture" val-toggle-msg="valServerField"></span>
|
||||
</umb-control-group>
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-header title-key="user_assignAccess"></umb-box-header>
|
||||
<umb-box-content class="block-form">
|
||||
|
||||
<umb-control-group style="margin-bottom: 25px;" label="@general_groups" description="@user_groupsHelp" required="true">
|
||||
|
||||
<umb-user-group-preview ng-repeat="userGroup in model.user.userGroups"
|
||||
icon="userGroup.icon"
|
||||
name="userGroup.name"
|
||||
sections="userGroup.sections"
|
||||
content-start-node="userGroup.contentStartNode"
|
||||
media-start-node="userGroup.mediaStartNode"
|
||||
allow-remove="!model.user.isCurrentUser"
|
||||
on-remove="model.removeSelectedItem($index, model.user.userGroups)">
|
||||
</umb-user-group-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="model.openUserGroupPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group style="margin-bottom: 25px;" label="@user_startnodes" description="@user_startnodeshelp">
|
||||
|
||||
<umb-node-preview style="max-width: 100%;"
|
||||
ng-repeat="node in model.user.startContentIds"
|
||||
icon="node.icon"
|
||||
name="node.name"
|
||||
allow-remove="!model.user.isCurrentUser"
|
||||
on-remove="model.removeSelectedItem($index, model.user.startContentIds)">
|
||||
</umb-node-preview>
|
||||
|
||||
<umb-node-preview ng-if="model.user.startContentIds.length === 0 && model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
name="model.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="model.openContentPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@user_mediastartnodes" description="@user_mediastartnodeshelp">
|
||||
|
||||
<umb-node-preview style="max-width: 100%;"
|
||||
ng-repeat="node in model.user.startMediaIds"
|
||||
icon="node.icon"
|
||||
name="node.name"
|
||||
allow-remove="!model.user.isCurrentUser"
|
||||
on-remove="model.removeSelectedItem($index, model.user.startMediaIds)">
|
||||
</umb-node-preview>
|
||||
|
||||
<umb-node-preview ng-if="model.user.startMediaIds.length === 0 && model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
name="model.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
|
||||
<a href=""
|
||||
ng-if="!model.user.isCurrentUser"
|
||||
style="max-width: 100%;"
|
||||
class="umb-node-preview-add"
|
||||
ng-click="model.openMediaPicker()"
|
||||
prevent-default>
|
||||
<localize key="general_add">Add</localize>
|
||||
</a>
|
||||
|
||||
</umb-control-group>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
|
||||
<umb-box>
|
||||
<umb-box-header title-key="user_access" description-key="user_accessHelp"></umb-box-header>
|
||||
<umb-box-content class="block-form" style="padding-bottom: 0;">
|
||||
|
||||
<umb-control-group label="@sections_content">
|
||||
<umb-node-preview style="max-width: 100%;"
|
||||
ng-repeat="node in model.user.calculatedStartContentIds"
|
||||
icon="node.icon"
|
||||
name="node.name">
|
||||
</umb-node-preview>
|
||||
<umb-node-preview ng-if="model.user.calculatedStartContentIds.length === 0"
|
||||
style="max-width: 100%;"
|
||||
name="model.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
</umb-control-group>
|
||||
|
||||
<umb-control-group label="@sections_media">
|
||||
<umb-node-preview style="max-width: 100%;"
|
||||
ng-repeat="node in model.user.calculatedStartMediaIds"
|
||||
icon="node.icon"
|
||||
name="node.name">
|
||||
</umb-node-preview>
|
||||
<umb-node-preview ng-if="model.user.calculatedStartMediaIds.length === 0"
|
||||
style="max-width: 100%;"
|
||||
name="model.labels.noStartNodes">
|
||||
</umb-node-preview>
|
||||
</umb-control-group>
|
||||
|
||||
</umb-box-content>
|
||||
</umb-box>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__sidebar">
|
||||
|
||||
<div class="umb-package-details__section">
|
||||
|
||||
<!-- Avatar -->
|
||||
<div style="margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #d8d7d9;">
|
||||
<ng-form name="avatarForm" class="flex flex-column justify-center items-center">
|
||||
|
||||
<umb-avatar style="margin-bottom: 15px;"
|
||||
color="secondary"
|
||||
size="xxl"
|
||||
name="{{model.user.name}}"
|
||||
img-src="{{model.user.avatars[3]}}"
|
||||
img-srcset="{{model.user.avatars[4]}} 2x, {{model.user.avatars[4]}} 3x">
|
||||
</umb-avatar>
|
||||
|
||||
<umb-progress-bar style="max-width: 120px;"
|
||||
ng-if="model.avatarFile.uploadStatus === 'uploading'"
|
||||
progress="{{ model.avatarFile.uploadProgress }}"
|
||||
size="s">
|
||||
</umb-progress-bar>
|
||||
|
||||
<div class="flex items-center" ng-if="model.avatarFile.uploadStatus !== 'uploading'">
|
||||
|
||||
<a href=""
|
||||
class="umb-user-group-preview__action"
|
||||
ngf-select ng-model="filesHolder"
|
||||
ngf-change="changeAvatar($files, $event)"
|
||||
ngf-multiple="false"
|
||||
ngf-pattern="{{model.acceptedFileTypes}}"
|
||||
ngf-max-size="{{ model.maxFileSize }}">
|
||||
<localize key="user_changePhoto">Change photo</localize>
|
||||
</a>
|
||||
|
||||
<a href=""
|
||||
ng-if="model.user.avatars"
|
||||
class="umb-user-group-preview__action umb-user-group-preview__action--red"
|
||||
ng-click="model.clearAvatar()"
|
||||
prevent-default>
|
||||
<localize key="user_removePhoto">Remove photo</localize>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
|
||||
</ng-form>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<div style="margin-bottom: 20px;">
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button ng-if="model.user.userDisplayState.key === 'Disabled' && !model.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[success,block]"
|
||||
state="model.enableUserButtonState"
|
||||
action="model.enableUser()"
|
||||
label="Enable"
|
||||
label-key="actions_enable"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button ng-if="model.user.userDisplayState.key === 'LockedOut' && !model.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[success,block]"
|
||||
state="model.unlockUserButtonState"
|
||||
action="model.unlockUser()"
|
||||
label="Unlock"
|
||||
label-key="actions_unlock"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 10px;">
|
||||
<umb-button ng-if="model.user.userDisplayState.key !== 'Disabled' && !model.user.isCurrentUser"
|
||||
type="button"
|
||||
button-style="[info,block]"
|
||||
action="model.disableUser()"
|
||||
state="model.disableUserButtonState"
|
||||
label="Disable"
|
||||
label-key="actions_disable"
|
||||
size="s">
|
||||
</umb-button>
|
||||
</div>
|
||||
|
||||
<umb-button type="button"
|
||||
button-style="[info,block]"
|
||||
action="model.toggleChangePassword()"
|
||||
label="Change password"
|
||||
label-key="general_changePassword"
|
||||
state="changePasswordButtonState"
|
||||
ng-if="model.changePasswordModel.isChanging === false"
|
||||
size="s">
|
||||
</umb-button>
|
||||
|
||||
<ng-form ng-if="model.changePasswordModel.isChanging" name="passwordForm" class="block-form" val-form-manager>
|
||||
|
||||
<change-password password-values="model.user.changePassword"
|
||||
config="model.changePasswordModel.config">
|
||||
</change-password>
|
||||
|
||||
<umb-button type="button"
|
||||
action="model.toggleChangePassword()"
|
||||
label="Cancel"
|
||||
label-key="general_cancel"
|
||||
button-style="cancel">
|
||||
</umb-button>
|
||||
|
||||
</ng-form>
|
||||
|
||||
<div ng-if="model.user.resetPasswordValue">
|
||||
<p><br />Password reset to value: <strong>{{model.user.resetPasswordValue}}</strong></p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- User stats -->
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="general_status">Status</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<umb-badge style="margin-top: 4px;" size="s" color="{{model.user.userDisplayState.color}}">
|
||||
{{model.user.userDisplayState.name}}
|
||||
</umb-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastLogin">Last login</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="model.user.lastLoginDate">{{ model.user.formattedLastLogin }}</span>
|
||||
<span ng-if="!model.user.lastLoginDate">{{ model.user.name | umbWordLimit:1 }} <localize key="user_noLogin">has not logged in yet</localize></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_failedPasswordAttempts">Failed login attempts</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ model.user.failedPasswordAttempts }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastLockoutDate">Last lockout date</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="model.user.lastLockoutDate === '0001-01-01T00:00:00'">
|
||||
{{ model.user.name | umbWordLimit:1 }} <localize key="user_noLockouts">hasn't been locked out</localize>
|
||||
</span>
|
||||
<span ng-if="model.user.lastLockoutDate !== '0001-01-01T00:00:00'">{{ model.user.formattedLastLockoutDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_lastPasswordChangeDate">Password is last changed</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
<span ng-if="model.user.lastPasswordChangeDate === '0001-01-01T00:00:00'">
|
||||
<localize key="user_noPasswordChange">The password hasn't been changed</localize>
|
||||
</span>
|
||||
<span ng-if="model.user.lastPasswordChangeDate !== '0001-01-01T00:00:00'">{{ model.user.formattedLastPasswordChangeDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_createDate">User is created</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ model.user.formattedCreateDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="umb-package-details__information-item">
|
||||
<div class="umb-package-details__information-item-label">
|
||||
<localize key="user_updateDate">User is last updated</localize>:
|
||||
</div>
|
||||
<div class="umb-package-details__information-item-content">
|
||||
{{ model.user.formattedUpdateDate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -3,6 +3,7 @@
|
||||
"name": "Introduction",
|
||||
"alias": "umbIntroIntroduction",
|
||||
"group": "Getting Started",
|
||||
"groupOrder": 100,
|
||||
"allowDisable": true,
|
||||
"steps": [
|
||||
{
|
||||
@@ -86,113 +87,114 @@
|
||||
"name": "Create document type",
|
||||
"alias": "umbIntroCreateDocType",
|
||||
"group": "Getting Started",
|
||||
"groupOrder": 100,
|
||||
"steps": [
|
||||
{
|
||||
title: "Create your first Document Type",
|
||||
content: "<p>Step 1 of any site is to create a <strong>Document Type</strong>.<br> A Document Type is a template for content. For each <em>type</em> of content you want to create you'll create a Document Type. This will define were content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.</p><p>When you have at least one Document type in place you can start creating content and this content can the be used in a template.</p><p>In this tour you will learn how to set up a basic Document Type with a property to enter a short text.</p>",
|
||||
type: "intro"
|
||||
"title": "Create your first Document Type",
|
||||
"content": "<p>Step 1 of any site is to create a <strong>Document Type</strong>.<br> A Document Type is a template for content. For each <em>type</em> of content you want to create you'll create a Document Type. This will define were content based on this Document Type can be created, how many properties it holds and what the input method should be for these properties.</p><p>When you have at least one Document type in place you can start creating content and this content can the be used in a template.</p><p>In this tour you will learn how to set up a basic Document Type with a property to enter a short text.</p>",
|
||||
"type": "intro"
|
||||
},
|
||||
{
|
||||
element: "#applications [data-element='section-settings']",
|
||||
title: "Navigate to the Settings sections",
|
||||
content: "In the <b>Settings section</b> you can create and manage Document types.",
|
||||
event: "click",
|
||||
backdropOpacity: 0.6
|
||||
"element": "#applications [data-element='section-settings']",
|
||||
"title": "Navigate to the Settings sections",
|
||||
"content": "In the <b>Settings section</b> you can create and manage Document types.",
|
||||
"event": "click",
|
||||
"backdropOpacity": 0.6
|
||||
},
|
||||
{
|
||||
element: "#tree [data-element='tree-item-documentTypes']",
|
||||
title: "Create Document Type",
|
||||
content: "<p>Hover the Document Type tree and click the <b>three small dots</b> to open the <b>context menu</b>.</p>",
|
||||
event: "click",
|
||||
eventElement: "#tree [data-element='tree-item-documentTypes'] [data-element='tree-item-options']"
|
||||
"element": "#tree [data-element='tree-item-documentTypes']",
|
||||
"title": "Create Document Type",
|
||||
"content": "<p>Hover the Document Type tree and click the <b>three small dots</b> to open the <b>context menu</b>.</p>",
|
||||
"event": "click",
|
||||
"eventElement": "#tree [data-element='tree-item-documentTypes'] [data-element='tree-item-options']"
|
||||
},
|
||||
{
|
||||
element: "#dialog [data-element='action-documentType']",
|
||||
title: "Create Document Type",
|
||||
content: "<p>Click <b>Document Type</b> to create a new document type with a template. The template will be automatically created and set as the default template for this Document Type</p><p>You will use the template in a later tour render content.</p>",
|
||||
event: "click"
|
||||
"element": "#dialog [data-element='action-documentType']",
|
||||
"title": "Create Document Type",
|
||||
"content": "<p>Click <b>Document Type</b> to create a new document type with a template. The template will be automatically created and set as the default template for this Document Type</p><p>You will use the template in a later tour render content.</p>",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-name-field']",
|
||||
title: "Enter a name",
|
||||
content: "<p>Your Document Type needs a name. Enter <code>My Home Page</code> in the field and click <b>Next</b>.",
|
||||
view: "doctypename"
|
||||
"element": "[data-element='editor-name-field']",
|
||||
"title": "Enter a name",
|
||||
"content": "<p>Your Document Type needs a name. Enter <code>My Home Page</code> in the field and click <b>Next</b>.",
|
||||
"view": "doctypename"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-description']",
|
||||
title: "Enter a description",
|
||||
content: "<p>A description helps to pick the right document type when creating content.</p><p>Write a description to our Home page. It could be: <br/><pre>The home page of the website</pre></p>"
|
||||
"element": "[data-element='editor-description']",
|
||||
"title": "Enter a description",
|
||||
"content": "<p>A description helps to pick the right document type when creating content.</p><p>Write a description to our Home page. It could be: <br/><pre>The home page of the website</pre></p>"
|
||||
},
|
||||
{
|
||||
element: "[data-element='group-add']",
|
||||
title: "Add tab",
|
||||
content: "Tabs are used to organize properties on content in the Content section. Click <b>Add new tab</b> to add a tab.",
|
||||
event: "click"
|
||||
"element": "[data-element='group-add']",
|
||||
"title": "Add tab",
|
||||
"content": "Tabs are used to organize properties on content in the Content section. Click <b>Add new tab</b> to add a tab.",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element='group-name-field']",
|
||||
title: "Name the tab",
|
||||
content: "<p>Enter <code>Home</code> in the tab name.</p><p><em>You can name a tab anything you want and if you have a lot of properties it can be useful to add multiple tabs.</em></p>",
|
||||
view: "tabName"
|
||||
"element": "[data-element='group-name-field']",
|
||||
"title": "Name the tab",
|
||||
"content": "<p>Enter <code>Home</code> in the tab name.</p><p><em>You can name a tab anything you want and if you have a lot of properties it can be useful to add multiple tabs.</em></p>",
|
||||
"view": "tabName"
|
||||
},
|
||||
{
|
||||
element: "[data-element='property-add']",
|
||||
title: "Add a property",
|
||||
content: "<p>Properties are the different input fields on a content page.</p><p>On our Home Page we wan't to add a welcome text.</p><p>Click <b>Add property</b> to open the property dialog.</p>",
|
||||
event: "click"
|
||||
"element": "[data-element='property-add']",
|
||||
"title": "Add a property",
|
||||
"content": "<p>Properties are the different input fields on a content page.</p><p>On our Home Page we wan't to add a welcome text.</p><p>Click <b>Add property</b> to open the property dialog.</p>",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element~='overlay-property-settings'] [data-element='property-name']",
|
||||
title: "Name the property",
|
||||
content: "Enter <code>Welcome Text</code> as the name for the property.",
|
||||
view: "propertyname"
|
||||
"element": "[data-element~='overlay-property-settings'] [data-element='property-name']",
|
||||
"title": "Name the property",
|
||||
"content": "Enter <code>Welcome Text</code> as the name for the property.",
|
||||
"view": "propertyname"
|
||||
},
|
||||
{
|
||||
element: "[data-element~='overlay-property-settings'] [data-element='property-description']",
|
||||
title: "Enter a description",
|
||||
content: "<p>A description will help to fill in the right content.</p><p>Enter a description for the property editor. It could be:<br/> <pre>Write a nice introduction text so the visitors feel welcome</pre></p>"
|
||||
"element": "[data-element~='overlay-property-settings'] [data-element='property-description']",
|
||||
"title": "Enter a description",
|
||||
"content": "<p>A description will help to fill in the right content.</p><p>Enter a description for the property editor. It could be:<br/> <pre>Write a nice introduction text so the visitors feel welcome</pre></p>"
|
||||
},
|
||||
{
|
||||
element: "[data-element~='overlay-property-settings'] [data-element='editor-add']",
|
||||
title: "Add editor",
|
||||
content: "When you add an editor you choose what the input method for this property will be. Click <b>Add editor</b> to open the editor picker dialog.",
|
||||
event: "click"
|
||||
"element": "[data-element~='overlay-property-settings'] [data-element='editor-add']",
|
||||
"title": "Add editor",
|
||||
"content": "When you add an editor you choose what the input method for this property will be. Click <b>Add editor</b> to open the editor picker dialog.",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element~='overlay-editor-picker']",
|
||||
elementPreventClick: true,
|
||||
title: "Editor picker",
|
||||
content: "<p>In the editor picker dialog we can pick one of the many build in editor.</p><p><em>You can choose from preconfigured data types (Reuse) or create a new configuration (Available editors)</em></p>"
|
||||
"element": "[data-element~='overlay-editor-picker']",
|
||||
"elementPreventClick": true,
|
||||
"title": "Editor picker",
|
||||
"content": "<p>In the editor picker dialog we can pick one of the many build in editor.</p><p><em>You can choose from preconfigured data types (Reuse) or create a new configuration (Available editors)</em></p>"
|
||||
},
|
||||
{
|
||||
element: "[data-element~='overlay-editor-picker'] [data-element='editor-Textarea']",
|
||||
title: "Select editor",
|
||||
content: "Select the <b>Textarea</b> editor. This will add a textarea to the Welcome Text property.",
|
||||
event: "click"
|
||||
"element": "[data-element~='overlay-editor-picker'] [data-element='editor-Textarea']",
|
||||
"title": "Select editor",
|
||||
"content": "Select the <b>Textarea</b> editor. This will add a textarea to the Welcome Text property.",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element~='overlay-editor-settings']",
|
||||
elementPreventClick: true,
|
||||
title: "Editor settings",
|
||||
content: "Each property editor can have individual settings. For the textarea editor you can set a charachter limit but in this case it is not needed"
|
||||
"element": "[data-element~='overlay-editor-settings']",
|
||||
"elementPreventClick": true,
|
||||
"title": "Editor settings",
|
||||
"content": "Each property editor can have individual settings. For the textarea editor you can set a charachter limit but in this case it is not needed"
|
||||
},
|
||||
{
|
||||
element: "[data-element~='overlay-editor-settings'] [data-element='button-overlaySubmit']",
|
||||
title: "Save editor",
|
||||
content: "Click <b>Submit</b> to save the editor.",
|
||||
event: "click"
|
||||
"element": "[data-element~='overlay-editor-settings'] [data-element='button-overlaySubmit']",
|
||||
"title": "Save editor",
|
||||
"content": "Click <b>Submit</b> to save the editor.",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element~='overlay-property-settings'] [data-element='button-overlaySubmit']",
|
||||
title: "Add property to document type",
|
||||
content: "Click <b>Submit</b> to add the property to the document type.",
|
||||
event: "click"
|
||||
"element": "[data-element~='overlay-property-settings'] [data-element='button-overlaySubmit']",
|
||||
"title": "Add property to document type",
|
||||
"content": "Click <b>Submit</b> to add the property to the document type.",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element='button-save']",
|
||||
title: "Save the document type",
|
||||
content: "All we need now is to save the document type. Click <b>Save</b> to create and save your new document type.",
|
||||
event: "click"
|
||||
"element": "[data-element='button-save']",
|
||||
"title": "Save the document type",
|
||||
"content": "All we need now is to save the document type. Click <b>Save</b> to create and save your new document type.",
|
||||
"event": "click"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -200,48 +202,49 @@
|
||||
"name": "Create Content",
|
||||
"alias": "umbIntroCreateContent",
|
||||
"group": "Getting Started",
|
||||
"groupOrder": 100,
|
||||
"steps": [
|
||||
{
|
||||
title: "Creating your first content node",
|
||||
content: "<p>In this tour you will learn how to create the home page for your website. It will use the <b>Home Page</b> Document type you created in the previous tour.</p>",
|
||||
type: "intro"
|
||||
"title": "Creating your first content node",
|
||||
"content": "<p>In this tour you will learn how to create the home page for your website. It will use the <b>Home Page</b> Document type you created in the previous tour.</p>",
|
||||
"type": "intro"
|
||||
},
|
||||
{
|
||||
element: "#applications [data-element='section-content']",
|
||||
title: "Navigate to the Content section",
|
||||
content: "<p>In the <b>Content section</b> you can create and manage the content of the website.</p><p>The <b>Content section</b> contains the content of your website. Content is displayed as <b>nodes</b> in the content tree.</p>",
|
||||
event: "click",
|
||||
backdropOpacity: 0.6
|
||||
"element": "#applications [data-element='section-content']",
|
||||
"title": "Navigate to the Content section",
|
||||
"content": "<p>In the <b>Content section</b> you can create and manage the content of the website.</p><p>The <b>Content section</b> contains the content of your website. Content is displayed as <b>nodes</b> in the content tree.</p>",
|
||||
"event": "click",
|
||||
"backdropOpacity": 0.6
|
||||
},
|
||||
{
|
||||
element: "[data-element='tree-root']",
|
||||
title: "Open context menu",
|
||||
content: "<p>Open the context menu by hovering the root of the content section.</p><p>Now click the <b>three small dots</b> to the right.</p>",
|
||||
event: "click",
|
||||
eventElement: "[data-element='tree-root'] [data-element='tree-item-options']"
|
||||
"element": "[data-element='tree-root']",
|
||||
"title": "Open context menu",
|
||||
"content": "<p>Open the context menu by hovering the root of the content section.</p><p>Now click the <b>three small dots</b> to the right.</p>",
|
||||
"event": "click",
|
||||
"eventElement": "[data-element='tree-root'] [data-element='tree-item-options']"
|
||||
},
|
||||
{
|
||||
element: "[data-element='action-create-homePage']",
|
||||
title: "Create Home page",
|
||||
content: "<p>The context menu shows you all the actions that are available on a node</p><p>Click on <b>Home Page</b> to create a new page of type <b>Home Page</b>.</p>",
|
||||
event: "click"
|
||||
"element": "[data-element='action-create-homePage']",
|
||||
"title": "Create Home page",
|
||||
"content": "<p>The context menu shows you all the actions that are available on a node</p><p>Click on <b>Home Page</b> to create a new page of type <b>Home Page</b>.</p>",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-content'] [data-element='editor-name-field']",
|
||||
title: "Give your new page a name",
|
||||
content: "<p>Our new page needs a name. Enter <code>Home</code> in the field and click <b>Next</b>.</p>",
|
||||
view: "nodename"
|
||||
"element": "[data-element='editor-content'] [data-element='editor-name-field']",
|
||||
"title": "Give your new page a name",
|
||||
"content": "<p>Our new page needs a name. Enter <code>Home</code> in the field and click <b>Next</b>.</p>",
|
||||
"view": "nodename"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-content'] [data-element='property-welcomeText']",
|
||||
title: "Add a welcome text",
|
||||
content: "<p>Add content to the <b>Welcome Text</b> field</p><p>If you don't have any ideas here is a start:<br/> <pre>I am learning Umbraco. High Five I Rock #H5IR</pre>.</p>"
|
||||
"element": "[data-element='editor-content'] [data-element='property-welcomeText']",
|
||||
"title": "Add a welcome text",
|
||||
"content": "<p>Add content to the <b>Welcome Text</b> field</p><p>If you don't have any ideas here is a start:<br/> <pre>I am learning Umbraco. High Five I Rock #H5IR</pre>.</p>"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-content'] [data-element='button-saveAndPublish']",
|
||||
title: "Save and Publish",
|
||||
content: "<p>Now click the <b>Save and publish</b> button to save and publish your changes.</p>",
|
||||
event: "click"
|
||||
"element": "[data-element='editor-content'] [data-element='button-saveAndPublish']",
|
||||
"title": "Save and Publish",
|
||||
"content": "<p>Now click the <b>Save and publish</b> button to save and publish your changes.</p>",
|
||||
"event": "click"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -249,44 +252,45 @@
|
||||
"name": "Render in template",
|
||||
"alias": "umbIntroRenderInTemplate",
|
||||
"group": "Getting Started",
|
||||
"groupOrder": 100,
|
||||
"steps": [
|
||||
{
|
||||
title: "Render your content in a template",
|
||||
content: "<p>Templating in Umbraco builds on the concept of <b>Razor Views</b> from asp.net MVC. - This tour is a sneak peak on how to write templates in Umbraco.</p><p>In this tour you will learn how to render content from the <b>Home Page</b> document type so you can see the content added to our <b>Home</b> content page.</p>",
|
||||
type: "intro"
|
||||
"title": "Render your content in a template",
|
||||
"content": "<p>Templating in Umbraco builds on the concept of <b>Razor Views</b> from asp.net MVC. - This tour is a sneak peak on how to write templates in Umbraco.</p><p>In this tour you will learn how to render content from the <b>Home Page</b> document type so you can see the content added to our <b>Home</b> content page.</p>",
|
||||
"type": "intro"
|
||||
},
|
||||
{
|
||||
element: "#applications [data-element='section-settings']",
|
||||
title: "Navigate to the Settings section",
|
||||
content: "<p>In the <b>Settings</b> section you will find all the templates</p><p>It is of course also possible to edit all your code files in your favorite code editor.</p>",
|
||||
event: "click",
|
||||
backdropOpacity: 0.6
|
||||
"element": "#applications [data-element='section-settings']",
|
||||
"title": "Navigate to the Settings section",
|
||||
"content": "<p>In the <b>Settings</b> section you will find all the templates</p><p>It is of course also possible to edit all your code files in your favorite code editor.</p>",
|
||||
"event": "click",
|
||||
"backdropOpacity": 0.6
|
||||
},
|
||||
{
|
||||
element: "#tree [data-element='tree-item-templates']",
|
||||
title: "Expand the Templates node",
|
||||
content: "<p>To see all our templates click the <b>small triangle</b> to the left of the templates node.</p>",
|
||||
event: "click",
|
||||
eventElement: "#tree [data-element='tree-item-templates'] [data-element='tree-item-expand']",
|
||||
view: "templatetree"
|
||||
"element": "#tree [data-element='tree-item-templates']",
|
||||
"title": "Expand the Templates node",
|
||||
"content": "<p>To see all our templates click the <b>small triangle</b> to the left of the templates node.</p>",
|
||||
"event": "click",
|
||||
"eventElement": "#tree [data-element='tree-item-templates'] [data-element='tree-item-expand']",
|
||||
"view": "templatetree"
|
||||
},
|
||||
{
|
||||
element: "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page']",
|
||||
title: "Open Home template",
|
||||
content: "<p>Click the <b>Home Page</b> template to open and edit it.</p>",
|
||||
eventElement: "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page'] a.umb-tree-item__label",
|
||||
event: "click"
|
||||
"element": "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page']",
|
||||
"title": "Open Home template",
|
||||
"content": "<p>Click the <b>Home Page</b> template to open and edit it.</p>",
|
||||
"eventElement": "#tree [data-element='tree-item-templates'] [data-element='tree-item-Home Page'] a.umb-tree-item__label",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-templates'] [data-element='code-editor']",
|
||||
title: "Edit template",
|
||||
content: '<p>The template can be edited here or in your favorite code editor.</p><p>To render the field from the document type add the following to the template:<br/> <pre><h1>@Model.Content.Name</h1><br><p>@Model.Content.WelcomeText</p></pre></p>'
|
||||
"element": "[data-element='editor-templates'] [data-element='code-editor']",
|
||||
"title": "Edit template",
|
||||
"content": "<p>The template can be edited here or in your favorite code editor.</p><p>To render the field from the document type add the following to the template:<br/> <pre><h1>@Model.Content.Name</h1><br><p>@Model.Content.WelcomeText</p></pre></p>"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-templates'] [data-element='button-save']",
|
||||
title: "Save the template",
|
||||
content: "Click the <b>Save button</b> and your template will be saved.",
|
||||
event: "click"
|
||||
"element": "[data-element='editor-templates'] [data-element='button-save']",
|
||||
"title": "Save the template",
|
||||
"content": "Click the <b>Save button</b> and your template will be saved.",
|
||||
"event": "click"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -294,38 +298,39 @@
|
||||
"name": "View Home page",
|
||||
"alias": "umbIntroViewHomePage",
|
||||
"group": "Getting Started",
|
||||
"groupOrder": 100,
|
||||
"steps": [
|
||||
{
|
||||
title: "View your Umbraco site",
|
||||
content: "<p>Our three main components to a page is done: <b>Document type, Template, and Content</b> - it is now time to see the result.</p><p>In this tour you will learn how to see your published website.</p>",
|
||||
type: "intro"
|
||||
"title": "View your Umbraco site",
|
||||
"content": "<p>Our three main components to a page is done: <b>Document type, Template, and Content</b> - it is now time to see the result.</p><p>In this tour you will learn how to see your published website.</p>",
|
||||
"type": "intro"
|
||||
},
|
||||
{
|
||||
element: "#applications [data-element='section-content']",
|
||||
title: "Navigate to the content sections",
|
||||
content: "In the <b>Content section</b> you will find the content of our website.",
|
||||
event: "click",
|
||||
backdropOpacity: 0.6
|
||||
"element": "#applications [data-element='section-content']",
|
||||
"title": "Navigate to the content sections",
|
||||
"content": "In the <b>Content section</b> you will find the content of our website.",
|
||||
"event": "click",
|
||||
"backdropOpacity": 0.6
|
||||
},
|
||||
{
|
||||
element: "#tree [data-element='tree-item-Home']",
|
||||
title: "Open the Home page",
|
||||
content: "<p>Click the <b>Home</b> page to open it</p>",
|
||||
event: "click",
|
||||
eventElement: "#tree [data-element='tree-item-Home'] a.umb-tree-item__label"
|
||||
"element": "#tree [data-element='tree-item-Home']",
|
||||
"title": "Open the Home page",
|
||||
"content": "<p>Click the <b>Home</b> page to open it</p>",
|
||||
"event": "click",
|
||||
"eventElement": "#tree [data-element='tree-item-Home'] a.umb-tree-item__label"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-content'] [data-element='tab-_umb_infoTab']",
|
||||
title: "Info",
|
||||
content: "<p>Under the info tab you will find the default information about a content item.</p>",
|
||||
event: "click"
|
||||
"element": "[data-element='editor-content'] [data-element='tab-_umb_infoTab']",
|
||||
"title": "Info",
|
||||
"content": "<p>Under the info tab you will find the default information about a content item.</p>",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-content'] [data-element='node-info-urls']",
|
||||
title: "Open page",
|
||||
content: "<p>Click the <b>Link to document</b> <i class='icon-out'></i> to view your page.</p><p>Tip: Click the preview button in the bottom right corner to preview changes without publishing them.</p>",
|
||||
event: "click",
|
||||
eventElement: "[data-element='editor-content'] [data-element='node-info-urls'] a[target='_blank']"
|
||||
"element": "[data-element='editor-content'] [data-element='node-info-urls']",
|
||||
"title": "Open page",
|
||||
"content": "<p>Click the <b>Link to document</b> <i class='icon-out'></i> to view your page.</p><p>Tip: Click the preview button in the bottom right corner to preview changes without publishing them.</p>",
|
||||
"event": "click",
|
||||
"eventElement": "[data-element='editor-content'] [data-element='node-info-urls'] a[target='_blank']"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -333,86 +338,87 @@
|
||||
"name": "The Media library",
|
||||
"alias": "umbIntroMediaSection",
|
||||
"group": "Getting Started",
|
||||
"groupOrder": 100,
|
||||
"steps": [
|
||||
{
|
||||
title: "How to use the media library",
|
||||
content: "<p>A website would be boring without media content. In Umbraco you can manage all your images, documents, videos etc. in the <b>Media section</b>. Here you can upload and organise your media items and see details about each item.</p><p>In this tour you will learn how to upload and organise your Media library in Umbraco. It will also show you how to view details about a specific media item.</p>",
|
||||
type: "intro"
|
||||
"title": "How to use the media library",
|
||||
"content": "<p>A website would be boring without media content. In Umbraco you can manage all your images, documents, videos etc. in the <b>Media section</b>. Here you can upload and organise your media items and see details about each item.</p><p>In this tour you will learn how to upload and organise your Media library in Umbraco. It will also show you how to view details about a specific media item.</p>",
|
||||
"type": "intro"
|
||||
},
|
||||
{
|
||||
element: "#applications [data-element='section-media']",
|
||||
title: "Navigate to the Media section",
|
||||
content: "The <b>media</b> section is where you manage all your media items.",
|
||||
event: "click",
|
||||
backdropOpacity: 0.6
|
||||
"element": "#applications [data-element='section-media']",
|
||||
"title": "Navigate to the Media section",
|
||||
"content": "The <b>media</b> section is where you manage all your media items.",
|
||||
"event": "click",
|
||||
"backdropOpacity": 0.6
|
||||
},
|
||||
{
|
||||
element: "#tree [data-element='tree-root']",
|
||||
title: "Create a new folder",
|
||||
content: "<p>First create a folder for your images. Hover the media root node and click the <b>three small dots</b> on the right side of the item.</p>",
|
||||
event: "click",
|
||||
eventElement: "#tree [data-element='tree-root'] [data-element='tree-item-options']"
|
||||
"element": "#tree [data-element='tree-root']",
|
||||
"title": "Create a new folder",
|
||||
"content": "<p>First create a folder for your images. Hover the media root node and click the <b>three small dots</b> on the right side of the item.</p>",
|
||||
"event": "click",
|
||||
"eventElement": "#tree [data-element='tree-root'] [data-element='tree-item-options']"
|
||||
},
|
||||
{
|
||||
element: "#dialog [data-element='action-Folder']",
|
||||
title: "Create a new folder",
|
||||
content: "<p>Select the <b>Folder</b> option to select the type folder.</p>",
|
||||
event: "click"
|
||||
"element": "#dialog [data-element='action-Folder']",
|
||||
"title": "Create a new folder",
|
||||
"content": "<p>Select the <b>Folder</b> option to select the type folder.</p>",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-media'] [data-element='editor-name-field']",
|
||||
title: "Enter a name",
|
||||
content: "<p>Enter <code>My Images</code> in the field.</p>"
|
||||
"element": "[data-element='editor-media'] [data-element='editor-name-field']",
|
||||
"title": "Enter a name",
|
||||
"content": "<p>Enter <code>My Images</code> in the field.</p>"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-media'] [data-element='button-save']",
|
||||
title: "Save the folder",
|
||||
content: "<p>Click the <b>Save</b> button to create the new folder</p>",
|
||||
event: "click"
|
||||
"element": "[data-element='editor-media'] [data-element='button-save']",
|
||||
"title": "Save the folder",
|
||||
"content": "<p>Click the <b>Save</b> button to create the new folder</p>",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-media'] [data-element='dropzone']",
|
||||
title: "Upload images",
|
||||
content: "<p>In the upload area you can upload your media items.</p><p>Click the <b>Click here to choose files</b>-button and select a couple of images on your computer and upload them.</p>",
|
||||
view: "uploadimages"
|
||||
"element": "[data-element='editor-media'] [data-element='dropzone']",
|
||||
"title": "Upload images",
|
||||
"content": "<p>In the upload area you can upload your media items.</p><p>Click the <b>Click here to choose files</b>-button and select a couple of images on your computer and upload them.</p>",
|
||||
"view": "uploadimages"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-media'] [data-element='media-grid-item-0']",
|
||||
title: "View media item details",
|
||||
content: "Hover the media item and <b>Click the purple bar</b> to view details about the media item",
|
||||
event: "click",
|
||||
eventElement: "[data-element='editor-media'] [data-element='media-grid-item-0'] [data-element='media-grid-item-edit']"
|
||||
"element": "[data-element='editor-media'] [data-element='media-grid-item-0']",
|
||||
"title": "View media item details",
|
||||
"content": "Hover the media item and <b>Click the purple bar</b> to view details about the media item",
|
||||
"event": "click",
|
||||
"eventElement": "[data-element='editor-media'] [data-element='media-grid-item-0'] [data-element='media-grid-item-edit']"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-media'] [data-element='property-umbracoFile']",
|
||||
title: "The uploaded image",
|
||||
content: "<p>Here you can see the image you have uploaded.</p>"
|
||||
"element": "[data-element='editor-media'] [data-element='property-umbracoFile']",
|
||||
"title": "The uploaded image",
|
||||
"content": "<p>Here you can see the image you have uploaded.</p>"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-media'] [data-element='property-umbracoBytes']",
|
||||
title: "Image size",
|
||||
content: "<p>You will also find other details about the image, like the size.</p><p>Media items work in much the same way as content. So you can add extra properties to an image by creating or editing the <b>Media types</b> in the Settings section.</p>"
|
||||
"element": "[data-element='editor-media'] [data-element='property-umbracoBytes']",
|
||||
"title": "Image size",
|
||||
"content": "<p>You will also find other details about the image, like the size.</p><p>Media items work in much the same way as content. So you can add extra properties to an image by creating or editing the <b>Media types</b> in the Settings section.</p>"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-media'] [data-element='tab-_umb_infoTab']",
|
||||
title: "Info",
|
||||
content: "Like the content section you can also find default information about the media item. You will find these under the info tab.",
|
||||
event: "click"
|
||||
"element": "[data-element='editor-media'] [data-element='tab-_umb_infoTab']",
|
||||
"title": "Info",
|
||||
"content": "Like the content section you can also find default information about the media item. You will find these under the info tab.",
|
||||
"event": "click"
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-media'] [data-element='node-info-urls']",
|
||||
title: "Link to media",
|
||||
content: "The path to the media item..."
|
||||
"element": "[data-element='editor-media'] [data-element='node-info-urls']",
|
||||
"title": "Link to media",
|
||||
"content": "The path to the media item..."
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-media'] [data-element='node-info-update-date']",
|
||||
title: "Last edited",
|
||||
content: "...and information about when the media item has been created and edited."
|
||||
"element": "[data-element='editor-media'] [data-element='node-info-update-date']",
|
||||
"title": "Last edited",
|
||||
"content": "...and information about when the media item has been created and edited."
|
||||
},
|
||||
{
|
||||
element: "[data-element='editor-container']",
|
||||
title: "Using media items",
|
||||
content: "You can reference a media item directly in a template by using the path or try adding a Media Picker to a document type property so you can select media items from the content section."
|
||||
"element": "[data-element='editor-container']",
|
||||
"title": "Using media items",
|
||||
"content": "You can reference a media item directly in a template by using the path or try adding a Media Picker to a document type property so you can select media items from the content section."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
<!--Developer-->
|
||||
<add initialize="true" sortOrder="0" alias="packager" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.PackagesTreeController, umbraco" />
|
||||
<add initialize="true" sortOrder="1" alias="dataTypes" application="developer" title="Data Types" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.DataTypeTreeController, umbraco" />
|
||||
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2" />
|
||||
<add initialize="true" sortOrder="2" alias="macros" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MacroTreeController, umbraco" />
|
||||
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
|
||||
<add application="developer" alias="xslt" title="XSLT Files" type="umbraco.loadXslt, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="5" />
|
||||
<add initialize="true" sortOrder="5" alias="xslt" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.XsltTreeController, umbraco" />
|
||||
<add application="developer" alias="partialViewMacros" type="Umbraco.Web.Trees.PartialViewMacrosTreeController, umbraco" silent="false" initialize="true" sortOrder="6" title="Partial View Macro Files" iconClosed="icon-folder" iconOpen="icon-folder" />
|
||||
|
||||
<!--Users-->
|
||||
|
||||
@@ -20,11 +20,11 @@
|
||||
<!--Developer-->
|
||||
<add initialize="true" sortOrder="0" alias="packager" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.PackagesTreeController, umbraco" />
|
||||
<add initialize="true" sortOrder="1" alias="dataTypes" application="developer" title="Data Types" iconClosed="icon-folder" iconOpen="icon-folder" type="Umbraco.Web.Trees.DataTypeTreeController, umbraco" />
|
||||
<add application="developer" alias="macros" title="Macros" type="umbraco.loadMacros, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="2" />
|
||||
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
|
||||
<add application="developer" alias="xslt" title="XSLT Files" type="umbraco.loadXslt, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="5" />
|
||||
<add application="developer" alias="partialViewMacros" type="Umbraco.Web.Trees.PartialViewMacrosTreeController, umbraco" silent="false" initialize="true" sortOrder="6" title="Partial View Macro Files" iconClosed="icon-folder" iconOpen="icon-folder" />
|
||||
<!--Users-->
|
||||
<add initialize="true" sortOrder="2" alias="macros" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MacroTreeController, umbraco" />
|
||||
<add application="developer" alias="relationTypes" title="Relation Types" type="umbraco.loadRelationTypes, umbraco" iconClosed="icon-folder" iconOpen="icon-folder" sortOrder="4" />
|
||||
<add initialize="true" sortOrder="5" alias="xslt" application="developer" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.XsltTreeController, umbraco" />
|
||||
<add application="developer" alias="partialViewMacros" type="Umbraco.Web.Trees.PartialViewMacrosTreeController, umbraco" silent="false" initialize="true" sortOrder="6" title="Partial View Macro Files" iconClosed="icon-folder" iconOpen="icon-folder" />
|
||||
<!--Users-->
|
||||
<add initialize="true" sortOrder="0" alias="users" application="users" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.UserTreeController, umbraco" />
|
||||
<!--Members-->
|
||||
<add initialize="true" sortOrder="0" alias="member" application="member" title="Members" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Web.Trees.MemberTreeController, umbraco" />
|
||||
@@ -39,5 +39,5 @@
|
||||
<add initialize="true" sortOrder="2" alias="datasource" application="forms" title="Datasources" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.DataSourceTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="0" alias="form" application="forms" title="Forms" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="3" alias="prevaluesource" application="forms" title="Prevalue sources" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.PreValueSourceTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="3" alias="formsecurity" application="users" title="Forms Security" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormSecurityTreeController, Umbraco.Forms.Web" />
|
||||
<add initialize="true" sortOrder="3" alias="formsecurity" application="users" title="Forms Security" iconClosed="icon-folder" iconOpen="icon-folder-open" type="Umbraco.Forms.Web.Trees.FormSecurityTreeController, Umbraco.Forms.Web" />
|
||||
</trees>
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
public sealed class EditorModelEventArgs<T> : EditorModelEventArgs
|
||||
{
|
||||
public EditorModelEventArgs(EditorModelEventArgs baseArgs)
|
||||
: base(baseArgs.Model, baseArgs.UmbracoContext)
|
||||
{
|
||||
Model = (T)baseArgs.Model;
|
||||
}
|
||||
|
||||
public EditorModelEventArgs(T model, UmbracoContext umbracoContext)
|
||||
: base(model, umbracoContext)
|
||||
{
|
||||
Model = model;
|
||||
}
|
||||
|
||||
public new T Model { get; private set; }
|
||||
}
|
||||
|
||||
public class EditorModelEventArgs : EventArgs
|
||||
{
|
||||
public EditorModelEventArgs(object model, UmbracoContext umbracoContext)
|
||||
{
|
||||
Model = model;
|
||||
UmbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
public object Model { get; private set; }
|
||||
public UmbracoContext UmbracoContext { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,9 @@
|
||||
using System;
|
||||
using System.Web.Http.Filters;
|
||||
using Umbraco.Core.Events;
|
||||
using Umbraco.Web.Models.ContentEditing;
|
||||
|
||||
namespace Umbraco.Web.Editors
|
||||
{
|
||||
public class EditorModelEventArgs : EventArgs
|
||||
{
|
||||
public EditorModelEventArgs(object model, UmbracoContext umbracoContext)
|
||||
{
|
||||
Model = model;
|
||||
UmbracoContext = umbracoContext;
|
||||
}
|
||||
|
||||
public object Model { get; private set; }
|
||||
public UmbracoContext UmbracoContext { get; private set; }
|
||||
}
|
||||
|
||||
public sealed class EditorModelEventArgs<T> : EditorModelEventArgs
|
||||
{
|
||||
public EditorModelEventArgs(EditorModelEventArgs baseArgs)
|
||||
: base(baseArgs.Model, baseArgs.UmbracoContext)
|
||||
{
|
||||
Model = (T)baseArgs.Model;
|
||||
}
|
||||
|
||||
public EditorModelEventArgs(T model, UmbracoContext umbracoContext)
|
||||
: base(model, umbracoContext)
|
||||
{
|
||||
Model = model;
|
||||
}
|
||||
|
||||
public new T Model { get; private set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to emit events for editor models in the back office
|
||||
/// </summary>
|
||||
@@ -42,6 +12,13 @@ namespace Umbraco.Web.Editors
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<ContentItemDisplay>> SendingContentModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<MediaItemDisplay>> SendingMediaModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<MemberDisplay>> SendingMemberModel;
|
||||
public static event TypedEventHandler<HttpActionExecutedContext, EditorModelEventArgs<UserDisplay>> SendingUserModel;
|
||||
|
||||
private static void OnSendingUserModel(HttpActionExecutedContext sender, EditorModelEventArgs<UserDisplay> e)
|
||||
{
|
||||
var handler = SendingUserModel;
|
||||
if (handler != null) handler(sender, e);
|
||||
}
|
||||
|
||||
private static void OnSendingContentModel(HttpActionExecutedContext sender, EditorModelEventArgs<ContentItemDisplay> e)
|
||||
{
|
||||
@@ -85,6 +62,12 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
OnSendingMemberModel(sender, new EditorModelEventArgs<MemberDisplay>(e));
|
||||
}
|
||||
|
||||
var userDisplay = e.Model as UserDisplay;
|
||||
if (userDisplay != null)
|
||||
{
|
||||
OnSendingUserModel(sender, new EditorModelEventArgs<UserDisplay>(e));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Umbraco.Core.Configuration;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Web.Models;
|
||||
@@ -25,24 +23,36 @@ namespace Umbraco.Web.Editors
|
||||
if (UmbracoConfig.For.UmbracoSettings().BackOffice.Tours.EnableTours == false)
|
||||
return result;
|
||||
|
||||
var coreTourFiles = Directory.GetFiles(
|
||||
Path.Combine(IOHelper.MapPath(SystemDirectories.Config), "BackOfficeTours"), "*.json");
|
||||
var filters = TourFilterResolver.Current.Filters.ToList();
|
||||
|
||||
//get all filters that will be applied to all tour aliases
|
||||
var aliasOnlyFilters = filters.Where(x => x.PluginName == null && x.TourFileName == null).ToList();
|
||||
|
||||
foreach (var tourFile in coreTourFiles)
|
||||
//don't pass in any filters for core tours that have a plugin name assigned
|
||||
var nonPluginFilters = filters.Where(x => x.PluginName == null).ToList();
|
||||
|
||||
//add core tour files
|
||||
var coreToursPath = Path.Combine(IOHelper.MapPath(SystemDirectories.Config), "BackOfficeTours");
|
||||
if (Directory.Exists(coreToursPath))
|
||||
{
|
||||
var contents = File.ReadAllText(tourFile);
|
||||
|
||||
result.Add(new BackOfficeTourFile
|
||||
foreach (var tourFile in Directory.EnumerateFiles(coreToursPath, "*.json"))
|
||||
{
|
||||
FileName = Path.GetFileNameWithoutExtension(tourFile),
|
||||
Tours = JsonConvert.DeserializeObject<BackOfficeTour[]>(contents)
|
||||
});
|
||||
TryParseTourFile(tourFile, result, nonPluginFilters, aliasOnlyFilters);
|
||||
}
|
||||
}
|
||||
|
||||
//collect all tour files in packges
|
||||
//collect all tour files in packages
|
||||
foreach (var plugin in Directory.EnumerateDirectories(IOHelper.MapPath(SystemDirectories.AppPlugins)))
|
||||
{
|
||||
var pluginName = Path.GetFileName(plugin.TrimEnd('\\'));
|
||||
var pluginFilters = filters.Where(x => x.PluginName != null && x.PluginName.IsMatch(pluginName)).ToList();
|
||||
|
||||
//If there is any filter applied to match the plugin only (no file or tour alias) then ignore the plugin entirely
|
||||
var isPluginFiltered = pluginFilters.Any(x => x.TourFileName == null && x.TourAlias == null);
|
||||
if (isPluginFiltered) continue;
|
||||
|
||||
//combine matched package filters with filters not specific to a package
|
||||
var combinedFilters = nonPluginFilters.Concat(pluginFilters).ToList();
|
||||
|
||||
foreach (var backofficeDir in Directory.EnumerateDirectories(plugin, "backoffice"))
|
||||
{
|
||||
@@ -50,13 +60,7 @@ namespace Umbraco.Web.Editors
|
||||
{
|
||||
foreach (var tourFile in Directory.EnumerateFiles(tourDir, "*.json"))
|
||||
{
|
||||
var contents = File.ReadAllText(tourFile);
|
||||
result.Add(new BackOfficeTourFile
|
||||
{
|
||||
FileName = Path.GetFileNameWithoutExtension(tourFile),
|
||||
PluginName = pluginName,
|
||||
Tours = JsonConvert.DeserializeObject<BackOfficeTour[]>(contents)
|
||||
});
|
||||
TryParseTourFile(tourFile, result, combinedFilters, aliasOnlyFilters, pluginName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,5 +68,54 @@ namespace Umbraco.Web.Editors
|
||||
|
||||
return result.OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase);
|
||||
}
|
||||
|
||||
private void TryParseTourFile(string tourFile,
|
||||
ICollection<BackOfficeTourFile> result,
|
||||
List<BackOfficeTourFilter> filters,
|
||||
List<BackOfficeTourFilter> aliasOnlyFilters,
|
||||
string pluginName = null)
|
||||
{
|
||||
var fileName = Path.GetFileNameWithoutExtension(tourFile);
|
||||
if (fileName == null) return;
|
||||
|
||||
//get the filters specific to this file
|
||||
var fileFilters = filters.Where(x => x.TourFileName != null && x.TourFileName.IsMatch(fileName)).ToList();
|
||||
|
||||
//If there is any filter applied to match the file only (no tour alias) then ignore the file entirely
|
||||
var isFileFiltered = fileFilters.Any(x => x.TourAlias == null);
|
||||
if (isFileFiltered) return;
|
||||
|
||||
//now combine all aliases to filter below
|
||||
var aliasFilters = aliasOnlyFilters.Concat(filters.Where(x => x.TourAlias != null))
|
||||
.Select(x => x.TourAlias)
|
||||
.ToList();
|
||||
|
||||
try
|
||||
{
|
||||
var contents = File.ReadAllText(tourFile);
|
||||
var tours = JsonConvert.DeserializeObject<BackOfficeTour[]>(contents);
|
||||
|
||||
var tour = new BackOfficeTourFile
|
||||
{
|
||||
FileName = Path.GetFileNameWithoutExtension(tourFile),
|
||||
PluginName = pluginName,
|
||||
Tours = tours
|
||||
.Where(x => aliasFilters.Count == 0 || aliasFilters.All(filter => filter.IsMatch(x.Alias)) == false)
|
||||
.ToArray()
|
||||
};
|
||||
|
||||
//don't add if all of the tours are filtered
|
||||
if (tour.Tours.Any())
|
||||
result.Add(tour);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new IOException("Error while trying to read file: " + tourFile, e);
|
||||
}
|
||||
catch (JsonReaderException e)
|
||||
{
|
||||
throw new JsonReaderException("Error while trying to parse content as tour data: " + tourFile, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -182,6 +182,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <returns></returns>
|
||||
[OutgoingEditorModelEvent]
|
||||
public UserDisplay GetById(int id)
|
||||
{
|
||||
var user = Services.UserService.GetUserById(id);
|
||||
@@ -494,6 +495,7 @@ namespace Umbraco.Web.Editors
|
||||
/// </summary>
|
||||
/// <param name="userSave"></param>
|
||||
/// <returns></returns>
|
||||
[OutgoingEditorModelEvent]
|
||||
public async Task<UserDisplay> PostSaveUser(UserSave userSave)
|
||||
{
|
||||
if (userSave == null) throw new ArgumentNullException("userSave");
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using Umbraco.Core.Collections;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents disabled features.
|
||||
/// </summary>
|
||||
internal class DisabledFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DisabledFeatures"/> class.
|
||||
/// </summary>
|
||||
public DisabledFeatures()
|
||||
{
|
||||
Controllers = new TypeList<UmbracoApiControllerBase>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disabled controllers.
|
||||
/// </summary>
|
||||
public TypeList<UmbracoApiControllerBase> Controllers { get; private set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
|
||||
namespace Umbraco.Web.Features
|
||||
{
|
||||
internal class FeaturesResolver : SingleObjectResolverBase<FeaturesResolver, UmbracoFeatures>
|
||||
{
|
||||
public FeaturesResolver(UmbracoFeatures value)
|
||||
: base(value)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Sets the features.
|
||||
/// </summary>
|
||||
/// <remarks>For developers, at application startup.</remarks>
|
||||
public void SetFeatures(UmbracoFeatures features)
|
||||
{
|
||||
Value = features;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the features.
|
||||
/// </summary>
|
||||
public UmbracoFeatures Features
|
||||
{
|
||||
get { return Value; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using Umbraco.Web.WebApi;
|
||||
|
||||
namespace Umbraco.Web.Features
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the Umbraco features.
|
||||
/// </summary>
|
||||
internal class UmbracoFeatures
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UmbracoFeatures"/> class.
|
||||
/// </summary>
|
||||
public UmbracoFeatures()
|
||||
{
|
||||
Disabled = new DisabledFeatures();
|
||||
}
|
||||
|
||||
// note
|
||||
// currently, the only thing a FeatureSet does is list disabled controllers,
|
||||
// but eventually we could enable and disable more parts of Umbraco. and then
|
||||
// we would need some logic to figure out what's enabled/disabled - hence it's
|
||||
// better to use IsEnabled, where the logic would go, rather than directly
|
||||
// accessing the Disabled collection.
|
||||
|
||||
/// <summary>
|
||||
/// Gets the disabled features.
|
||||
/// </summary>
|
||||
public DisabledFeatures Disabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a feature is enabled.
|
||||
/// </summary>
|
||||
public bool IsEnabled(Type feature)
|
||||
{
|
||||
if (typeof(UmbracoApiControllerBase).IsAssignableFrom(feature))
|
||||
return Disabled.Controllers.Contains(feature) == false;
|
||||
|
||||
throw new NotSupportedException("Not a supported feature type.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing a tour.
|
||||
/// </summary>
|
||||
[DataContract(Name = "tour", Namespace = "")]
|
||||
public class BackOfficeTour
|
||||
{
|
||||
@@ -15,6 +14,8 @@ namespace Umbraco.Web.Models
|
||||
public string Alias { get; set; }
|
||||
[DataMember(Name = "group")]
|
||||
public string Group { get; set; }
|
||||
[DataMember(Name = "groupOrder")]
|
||||
public int GroupOrder { get; set; }
|
||||
[DataMember(Name = "allowDisable")]
|
||||
public bool AllowDisable { get; set; }
|
||||
[DataMember(Name = "steps")]
|
||||
|
||||
@@ -3,6 +3,9 @@ using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing the file used to load a tour.
|
||||
/// </summary>
|
||||
[DataContract(Name = "tourFile", Namespace = "")]
|
||||
public class BackOfficeTourFile
|
||||
{
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
public class BackOfficeTourFilter
|
||||
{
|
||||
public Regex PluginName { get; private set; }
|
||||
public Regex TourFileName { get; private set; }
|
||||
public Regex TourAlias { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter to filter out a whole plugin's tours
|
||||
/// </summary>
|
||||
/// <param name="pluginName"></param>
|
||||
/// <returns></returns>
|
||||
public static BackOfficeTourFilter FilterPlugin(Regex pluginName)
|
||||
{
|
||||
return new BackOfficeTourFilter(pluginName, null, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter to filter out a whole tour file
|
||||
/// </summary>
|
||||
/// <param name="tourFileName"></param>
|
||||
/// <returns></returns>
|
||||
public static BackOfficeTourFilter FilterFile(Regex tourFileName)
|
||||
{
|
||||
return new BackOfficeTourFilter(null, tourFileName, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a filter to filter out a tour alias, this will filter out the same alias found in all files
|
||||
/// </summary>
|
||||
/// <param name="tourAlias"></param>
|
||||
/// <returns></returns>
|
||||
public static BackOfficeTourFilter FilterAlias(Regex tourAlias)
|
||||
{
|
||||
return new BackOfficeTourFilter(null, null, tourAlias);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructor to create a tour filter
|
||||
/// </summary>
|
||||
/// <param name="pluginName">Value to filter out tours by a plugin, can be null</param>
|
||||
/// <param name="tourFileName">Value to filter out a tour file, can be null</param>
|
||||
/// <param name="tourAlias">Value to filter out a tour alias, can be null</param>
|
||||
/// <remarks>
|
||||
/// Depending on what is null will depend on how the filter is applied.
|
||||
/// If pluginName is not NULL and it's matched then we check if tourFileName is not NULL and it's matched then we check tour alias is not NULL and then match it,
|
||||
/// if any steps is NULL then the filters upstream are applied.
|
||||
/// Example, pluginName = "hello", tourFileName="stuff", tourAlias=NULL = we will filter out the tour file "stuff" from the plugin "hello" but not from other plugins if the same file name exists.
|
||||
/// Example, tourAlias="test.*" = we will filter out all tour aliases that start with the word "test" regardless of the plugin or file name
|
||||
/// </remarks>
|
||||
public BackOfficeTourFilter(Regex pluginName, Regex tourFileName, Regex tourAlias)
|
||||
{
|
||||
PluginName = pluginName;
|
||||
TourFileName = tourFileName;
|
||||
TourAlias = tourAlias;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
namespace Umbraco.Web.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing a step in a tour.
|
||||
/// </summary>
|
||||
[DataContract(Name = "step", Namespace = "")]
|
||||
public class BackOfficeTourStep
|
||||
{
|
||||
@@ -16,7 +19,7 @@ namespace Umbraco.Web.Models
|
||||
[DataMember(Name = "elementPreventClick")]
|
||||
public bool ElementPreventClick { get; set; }
|
||||
[DataMember(Name = "backdropOpacity")]
|
||||
public float BackdropOpacity { get; set; }
|
||||
public float? BackdropOpacity { get; set; }
|
||||
[DataMember(Name = "event")]
|
||||
public string Event { get; set; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
/// <summary>
|
||||
/// A model representing the navigation ("apps") inside an editor in the back office
|
||||
/// </summary>
|
||||
[DataContract(Name = "user", Namespace = "")]
|
||||
public class EditorNavigation
|
||||
{
|
||||
[DataMember(Name = "name")]
|
||||
public string Name { get; set; }
|
||||
|
||||
[DataMember(Name = "alias")]
|
||||
public string Alias { get; set; }
|
||||
|
||||
[DataMember(Name = "icon")]
|
||||
public string Icon { get; set; }
|
||||
|
||||
[DataMember(Name = "view")]
|
||||
public string View { get; set; }
|
||||
|
||||
[DataMember(Name = "active")]
|
||||
public bool Active { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ using System.ComponentModel.DataAnnotations;
|
||||
using System.Runtime.Serialization;
|
||||
|
||||
namespace Umbraco.Web.Models.ContentEditing
|
||||
{
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents information for the current user
|
||||
/// </summary>
|
||||
@@ -29,7 +29,7 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
[Obsolete("This should not be used it exists for legacy reasons only, use user groups instead, it will be removed in future versions")]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[ReadOnly(true)]
|
||||
[DataMember(Name = "userType")]
|
||||
[DataMember(Name = "userType")]
|
||||
public string UserType { get; set; }
|
||||
|
||||
[ReadOnly(true)]
|
||||
@@ -64,8 +64,8 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
/// A list of sections the user is allowed to view.
|
||||
/// </summary>
|
||||
[DataMember(Name = "allowedSections")]
|
||||
public IEnumerable<string> AllowedSections { get; set; }
|
||||
|
||||
|
||||
public IEnumerable<string> AllowedSections { get; set; }
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,13 @@ namespace Umbraco.Web.Models.ContentEditing
|
||||
AvailableCultures = new Dictionary<string, string>();
|
||||
StartContentIds = new List<EntityBasic>();
|
||||
StartMediaIds = new List<EntityBasic>();
|
||||
Navigation = new List<EditorNavigation>();
|
||||
}
|
||||
|
||||
|
||||
[DataMember(Name = "navigation")]
|
||||
[ReadOnly(true)]
|
||||
public IEnumerable<EditorNavigation> Navigation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the available cultures (i.e. to populate a drop down)
|
||||
/// The key is the culture stored in the database, the value is the Name
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using AutoMapper;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models.Mapping;
|
||||
@@ -231,12 +232,14 @@ namespace Umbraco.Web.Models.Mapping
|
||||
});
|
||||
|
||||
//Important! Currently we are never mapping to multiple UserDisplay objects but if we start doing that
|
||||
// this will cause an N+1 and we'll need to change how this works.
|
||||
// this will cause an N+1 and we'll need to change how this works.
|
||||
|
||||
config.CreateMap<IUser, UserDisplay>()
|
||||
.ForMember(detail => detail.Avatars, opt => opt.MapFrom(user => user.GetUserAvatarUrls(applicationContext.ApplicationCache.RuntimeCache)))
|
||||
.ForMember(detail => detail.Username, opt => opt.MapFrom(user => user.Username))
|
||||
.ForMember(detail => detail.LastLoginDate, opt => opt.MapFrom(user => user.LastLoginDate == default(DateTime) ? null : (DateTime?) user.LastLoginDate))
|
||||
.ForMember(detail => detail.UserGroups, opt => opt.MapFrom(user => user.Groups))
|
||||
.ForMember(detail => detail.Navigation, opt => opt.MapFrom(user => CreateUserEditorNavigation(applicationContext.Services.TextService)))
|
||||
.ForMember(
|
||||
detail => detail.CalculatedStartContentIds,
|
||||
opt => opt.MapFrom(user => GetStartNodeValues(
|
||||
@@ -255,7 +258,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
"media/mediaRoot")))
|
||||
.ForMember(
|
||||
detail => detail.StartContentIds,
|
||||
opt => opt.MapFrom(user => GetStartNodeValues(
|
||||
opt => opt.MapFrom(user => GetStartNodeValues(
|
||||
user.StartContentIds.ToArray(),
|
||||
applicationContext.Services.TextService,
|
||||
applicationContext.Services.EntityService,
|
||||
@@ -263,7 +266,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
"content/contentRoot")))
|
||||
.ForMember(
|
||||
detail => detail.StartMediaIds,
|
||||
opt => opt.MapFrom(user => GetStartNodeValues(
|
||||
opt => opt.MapFrom(user => GetStartNodeValues(
|
||||
user.StartMediaIds.ToArray(),
|
||||
applicationContext.Services.TextService,
|
||||
applicationContext.Services.EntityService,
|
||||
@@ -330,7 +333,7 @@ namespace Umbraco.Web.Models.Mapping
|
||||
//the best we can do here is to return the user's first user group as a IUserType object
|
||||
//but we should attempt to return any group that is the built in ones first
|
||||
var groups = user.Groups.ToArray();
|
||||
detail.UserGroups = user.Groups.Select(x => x.Alias).ToArray();
|
||||
detail.UserGroups = user.Groups.Select(x => x.Alias).ToArray();
|
||||
|
||||
if (groups.Length == 0)
|
||||
{
|
||||
@@ -358,6 +361,21 @@ namespace Umbraco.Web.Models.Mapping
|
||||
.ForMember(detail => detail.UserId, opt => opt.MapFrom(profile => GetIntId(profile.Id)));
|
||||
}
|
||||
|
||||
private IEnumerable<EditorNavigation> CreateUserEditorNavigation(ILocalizedTextService textService)
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new EditorNavigation
|
||||
{
|
||||
Active = true,
|
||||
Alias = "details",
|
||||
Icon = "icon-umb-users",
|
||||
Name = textService.Localize("general/user"),
|
||||
View = "views/users/views/user/details.html"
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<EntityBasic> GetStartNodeValues(int[] startNodeIds,
|
||||
ILocalizedTextService textService, IEntityService entityService, UmbracoObjectTypes objectType,
|
||||
string localizedKey)
|
||||
|
||||
@@ -187,7 +187,7 @@ namespace Umbraco.Web.Models.Trees
|
||||
nodeType,
|
||||
item == null ? "" : item.Name, currentSection),
|
||||
action => LaunchDialogUrl(action.Url, action.DialogTitle))
|
||||
.OnFailure(() => LegacyTreeDataConverter.GetLegacyConfirmView(Action, currentSection),
|
||||
.OnFailure(() => LegacyTreeDataConverter.GetLegacyConfirmView(Action),
|
||||
view => LaunchDialogView(
|
||||
view,
|
||||
ui.GetText("defaultdialogs", "confirmdelete") + " '" + (item == null ? "" : item.Name) + "' ?"));
|
||||
|
||||
@@ -40,6 +40,7 @@ using System.Security;
|
||||
[assembly: InternalsVisibleTo("Umbraco.Deploy.Cloud")]
|
||||
[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder")]
|
||||
[assembly: InternalsVisibleTo("Umbraco.ModelsBuilder.AspNet")]
|
||||
[assembly: InternalsVisibleTo("Umbraco.Headless")]
|
||||
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
|
||||
|
||||
[assembly: InternalsVisibleTo("Umbraco.Forms.Core")]
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Umbraco.Web.Scheduling
|
||||
/// depending on whether the task is implemented as a sync or async method, and then
|
||||
/// optionnally overriding RunsOnShutdown, to indicate whether the latched task should run
|
||||
/// immediately on shutdown, or just be abandonned (default).</remarks>
|
||||
public abstract class LatchedBackgroundTaskBase : DisposableObject, ILatchedBackgroundTask
|
||||
public abstract class LatchedBackgroundTaskBase : DisposableObjectSlim, ILatchedBackgroundTask
|
||||
{
|
||||
private TaskCompletionSource<bool> _latch;
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Umbraco.Web.Scheduling
|
||||
if (_runner.TryAdd(this))
|
||||
_timer.Change(_periodMilliseconds, 0);
|
||||
else
|
||||
Dispose(true);
|
||||
Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Umbraco.Web.Security
|
||||
/// <summary>
|
||||
/// A utility class used for dealing with USER security in Umbraco
|
||||
/// </summary>
|
||||
public class WebSecurity : DisposableObject
|
||||
public class WebSecurity : DisposableObjectSlim
|
||||
{
|
||||
private HttpContextBase _httpContext;
|
||||
private ApplicationContext _applicationContext;
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.ObjectResolution;
|
||||
using Umbraco.Web.Models;
|
||||
|
||||
namespace Umbraco.Web
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows for adding filters for tours during startup
|
||||
/// </summary>
|
||||
public class TourFilterResolver : ManyObjectsResolverBase<TourFilterResolver, BackOfficeTourFilter>
|
||||
{
|
||||
public TourFilterResolver(IServiceProvider serviceProvider, ILogger logger) : base(serviceProvider, logger)
|
||||
{
|
||||
}
|
||||
|
||||
private readonly HashSet<BackOfficeTourFilter> _instances = new HashSet<BackOfficeTourFilter>();
|
||||
|
||||
public IEnumerable<BackOfficeTourFilter> Filters
|
||||
{
|
||||
get { return Values; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a filter instance
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
public void AddFilter(BackOfficeTourFilter filter)
|
||||
{
|
||||
using (Resolution.Configuration)
|
||||
_instances.Add(filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a filter instance
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
public void RemoveFilter(BackOfficeTourFilter filter)
|
||||
{
|
||||
using (Resolution.Configuration)
|
||||
_instances.Remove(filter);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a filter instance based on callback
|
||||
/// </summary>
|
||||
/// <param name="filter"></param>
|
||||
public void RemoveFilterWhere(Func<BackOfficeTourFilter, bool> filter)
|
||||
{
|
||||
using (Resolution.Configuration)
|
||||
_instances.RemoveWhere(new Predicate<BackOfficeTourFilter>(filter));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Overridden to return the combined created instances based on the resolved Types and the Concrete values added with AddFilter
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
protected override IEnumerable<BackOfficeTourFilter> CreateInstances()
|
||||
{
|
||||
var createdInstances = base.CreateInstances();
|
||||
return createdInstances.Concat(_instances);
|
||||
}
|
||||
|
||||
public override void Clear()
|
||||
{
|
||||
base.Clear();
|
||||
_instances.Clear();
|
||||
}
|
||||
|
||||
internal override void ResetCollections()
|
||||
{
|
||||
base.ResetCollections();
|
||||
_instances.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,10 +84,19 @@ namespace Umbraco.Web.Trees
|
||||
private async Task<TreeNode> GetRootForMultipleAppTree(ApplicationTree configTree, FormDataCollection queryStrings)
|
||||
{
|
||||
if (configTree == null) throw new ArgumentNullException("configTree");
|
||||
var byControllerAttempt = await configTree.TryGetRootNodeFromControllerTree(queryStrings, ControllerContext);
|
||||
if (byControllerAttempt.Success)
|
||||
{
|
||||
return byControllerAttempt.Result;
|
||||
try
|
||||
{
|
||||
var byControllerAttempt = await configTree.TryGetRootNodeFromControllerTree(queryStrings, ControllerContext);
|
||||
if (byControllerAttempt.Success)
|
||||
{
|
||||
return byControllerAttempt.Result;
|
||||
}
|
||||
}
|
||||
catch (HttpResponseException)
|
||||
{
|
||||
//if this occurs its because the user isn't authorized to view that tree, in this case since we are loading multiple trees we
|
||||
//will just return null so that it's not added to the list.
|
||||
return null;
|
||||
}
|
||||
|
||||
var legacyAttempt = configTree.TryGetRootNodeFromLegacyTree(queryStrings, Url, configTree.ApplicationAlias);
|
||||
|
||||
@@ -72,23 +72,64 @@ namespace Umbraco.Web.Trees
|
||||
return nodes;
|
||||
}
|
||||
|
||||
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
|
||||
protected virtual MenuItemCollection GetMenuForRootNode(FormDataCollection queryStrings)
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
//set the default to create
|
||||
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
|
||||
//create action
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
|
||||
//refresh action
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
protected virtual MenuItemCollection GetMenuForFolder(string path, FormDataCollection queryStrings)
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
//set the default to create
|
||||
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
|
||||
//create action
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
|
||||
|
||||
var hasChildren = FileSystem.GetFiles(path).Any() || FileSystem.GetDirectories(path).Any();
|
||||
|
||||
//We can only delete folders if it doesn't have any children (folders or files)
|
||||
if (hasChildren == false)
|
||||
{
|
||||
//delete action
|
||||
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)), true);
|
||||
}
|
||||
|
||||
//refresh action
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
protected virtual MenuItemCollection GetMenuForFile(string path, FormDataCollection queryStrings)
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
//if it's not a directory then we only allow to delete the item
|
||||
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
//if root node no need to visit the filesystem so lets just create the menu and return it
|
||||
if (id == Constants.System.Root.ToInvariantString())
|
||||
{
|
||||
//set the default to create
|
||||
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
|
||||
//create action
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
|
||||
//refresh action
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
|
||||
|
||||
return menu;
|
||||
return GetMenuForRootNode(queryStrings);
|
||||
}
|
||||
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
var path = string.IsNullOrEmpty(id) == false && id != Constants.System.Root.ToInvariantString()
|
||||
? HttpUtility.UrlDecode(id).TrimStart("/")
|
||||
: "";
|
||||
@@ -98,30 +139,10 @@ namespace Umbraco.Web.Trees
|
||||
|
||||
if (isDirectory)
|
||||
{
|
||||
//set the default to create
|
||||
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
|
||||
//create action
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)));
|
||||
|
||||
var hasChildren = FileSystem.GetFiles(path).Any() || FileSystem.GetDirectories(path).Any();
|
||||
|
||||
//We can only delete folders if it doesn't have any children (folders or files)
|
||||
if (hasChildren == false)
|
||||
{
|
||||
//delete action
|
||||
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)), true);
|
||||
}
|
||||
|
||||
//refresh action
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(Services.TextService.Localize(string.Format("actions/{0}", ActionRefresh.Instance.Alias)), true);
|
||||
}
|
||||
else if (isFile)
|
||||
{
|
||||
//if it's not a directory then we only allow to delete the item
|
||||
menu.Items.Add<ActionDelete>(Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
|
||||
return GetMenuForFolder(path, queryStrings);
|
||||
}
|
||||
|
||||
return menu;
|
||||
return isFile ? GetMenuForFile(path, queryStrings) : menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,7 +134,7 @@ namespace Umbraco.Web.Trees
|
||||
Attempt
|
||||
.Try(GetUrlAndTitleFromLegacyAction(currentAction, xmlTreeNode.NodeID, xmlTreeNode.NodeType, xmlTreeNode.Text, currentSection),
|
||||
action => menuItem.LaunchDialogUrl(action.Url, action.DialogTitle))
|
||||
.OnFailure(() => GetLegacyConfirmView(currentAction, currentSection),
|
||||
.OnFailure(() => GetLegacyConfirmView(currentAction),
|
||||
view => menuItem.LaunchDialogView(
|
||||
view,
|
||||
ui.GetText("defaultdialogs", "confirmdelete") + " '" + xmlTreeNode.Text + "' ?"))
|
||||
@@ -164,9 +164,8 @@ namespace Umbraco.Web.Trees
|
||||
/// This will look at the legacy IAction's JsFunctionName and convert it to a confirmation dialog view if possible
|
||||
/// </summary>
|
||||
/// <param name="action"></param>
|
||||
/// <param name="currentSection"></param>
|
||||
/// <returns></returns>
|
||||
internal static Attempt<string> GetLegacyConfirmView(IAction action, string currentSection)
|
||||
internal static Attempt<string> GetLegacyConfirmView(IAction action)
|
||||
{
|
||||
if (action.JsFunctionName.IsNullOrWhiteSpace())
|
||||
{
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Net.Http.Formatting;
|
||||
using umbraco;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Models;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Mvc;
|
||||
using System.Linq;
|
||||
using umbraco.BusinessLogic.Actions;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Umbraco.Core.Services;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Macros)]
|
||||
[Tree(Constants.Applications.Developer, Constants.Trees.Macros, null, sortOrder: 2)]
|
||||
[LegacyBaseTree(typeof(loadMacros))]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
public class MacroTreeController : TreeController
|
||||
{
|
||||
protected override TreeNodeCollection GetTreeNodes(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
var intId = id.TryConvertTo<int>();
|
||||
if (intId == false) throw new InvalidOperationException("Id must be an integer");
|
||||
|
||||
var nodes = new TreeNodeCollection();
|
||||
|
||||
nodes.AddRange(
|
||||
Services.MacroService.GetAll()
|
||||
.OrderBy(entity => entity.Name)
|
||||
.Select(macro =>
|
||||
{
|
||||
var node = CreateTreeNode(macro.Id.ToInvariantString(), id, queryStrings, macro.Name, "icon-settings-alt", false);
|
||||
node.Path = "-1," + macro.Id;
|
||||
node.AssignLegacyJsCallback("javascript:UmbClientMgr.contentFrame('developer/macros/editMacro.aspx?macroID=" + macro.Id + "');");
|
||||
return node;
|
||||
}));
|
||||
|
||||
return nodes;
|
||||
}
|
||||
|
||||
protected override MenuItemCollection GetMenuForNode(string id, FormDataCollection queryStrings)
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
if (id == Constants.System.Root.ToInvariantString())
|
||||
{
|
||||
//set the default to create
|
||||
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
|
||||
|
||||
// root actions
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)))
|
||||
.ConvertLegacyMenuItem(null, Constants.Trees.Macros, queryStrings.GetValue<string>("application"));
|
||||
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(ui.Text("actions", ActionRefresh.Instance.Alias), true);
|
||||
return menu;
|
||||
}
|
||||
|
||||
//TODO: This is all hacky ... don't have time to convert the tree, views and dialogs over properly so we'll keep using the legacy dialogs
|
||||
var menuItem = menu.Items.Add(ActionDelete.Instance, Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
|
||||
var legacyConfirmView = LegacyTreeDataConverter.GetLegacyConfirmView(ActionDelete.Instance);
|
||||
if (legacyConfirmView == false)
|
||||
throw new InvalidOperationException("Could not resolve the confirmation view for the legacy action " + ActionDelete.Instance.Alias);
|
||||
menuItem.LaunchDialogView(
|
||||
legacyConfirmView.Result,
|
||||
Services.TextService.Localize("general/delete"));
|
||||
|
||||
return menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ using Constants = Umbraco.Core.Constants;
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Scripts)]
|
||||
[Tree(Constants.Applications.Settings, "scripts", null, sortOrder: 4)]
|
||||
[Tree(Constants.Applications.Settings, Constants.Trees.Scripts, null, sortOrder: 4)]
|
||||
[LegacyBaseTree(typeof(loadScripts))]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Net.Http.Formatting;
|
||||
using umbraco;
|
||||
using umbraco.BusinessLogic.Actions;
|
||||
using Umbraco.Core.IO;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Models.Trees;
|
||||
using Umbraco.Web.Mvc;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
using Constants = Umbraco.Core.Constants;
|
||||
|
||||
namespace Umbraco.Web.Trees
|
||||
{
|
||||
[UmbracoTreeAuthorize(Constants.Trees.Xslt)]
|
||||
[Tree(Constants.Applications.Developer, Constants.Trees.Xslt, null, sortOrder: 5)]
|
||||
[LegacyBaseTree(typeof(loadXslt))]
|
||||
[PluginController("UmbracoTrees")]
|
||||
[CoreTree]
|
||||
public class XsltTreeController : FileSystemTreeController
|
||||
{
|
||||
protected override void OnRenderFileNode(ref TreeNode treeNode)
|
||||
{
|
||||
////TODO: This is all hacky ... don't have time to convert the tree, views and dialogs over properly so we'll keep using the legacy views
|
||||
treeNode.AssignLegacyJsCallback("javascript:UmbClientMgr.contentFrame('developer/xslt/editXslt.aspx?file=" + treeNode.Id + "');");
|
||||
}
|
||||
|
||||
protected override void OnRenderFolderNode(ref TreeNode treeNode)
|
||||
{
|
||||
//TODO: This is all hacky ... don't have time to convert the tree, views and dialogs over properly so we'll keep using the legacy views
|
||||
treeNode.AssignLegacyJsCallback("javascript:void(0);");
|
||||
}
|
||||
|
||||
protected override MenuItemCollection GetMenuForFile(string path, FormDataCollection queryStrings)
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
//TODO: This is all hacky ... don't have time to convert the tree, views and dialogs over properly so we'll keep using the legacy dialogs
|
||||
var menuItem = menu.Items.Add(ActionDelete.Instance, Services.TextService.Localize(string.Format("actions/{0}", ActionDelete.Instance.Alias)));
|
||||
var legacyConfirmView = LegacyTreeDataConverter.GetLegacyConfirmView(ActionDelete.Instance);
|
||||
if (legacyConfirmView == false)
|
||||
throw new InvalidOperationException("Could not resolve the confirmation view for the legacy action " + ActionDelete.Instance.Alias);
|
||||
menuItem.LaunchDialogView(
|
||||
legacyConfirmView.Result,
|
||||
Services.TextService.Localize("general/delete"));
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
protected override MenuItemCollection GetMenuForRootNode(FormDataCollection queryStrings)
|
||||
{
|
||||
var menu = new MenuItemCollection();
|
||||
|
||||
//set the default to create
|
||||
menu.DefaultMenuAlias = ActionNew.Instance.Alias;
|
||||
|
||||
// root actions
|
||||
menu.Items.Add<ActionNew>(Services.TextService.Localize(string.Format("actions/{0}", ActionNew.Instance.Alias)))
|
||||
.ConvertLegacyMenuItem(null, Constants.Trees.Xslt, queryStrings.GetValue<string>("application"));
|
||||
|
||||
menu.Items.Add<RefreshNode, ActionRefresh>(ui.Text("actions", ActionRefresh.Instance.Alias), true);
|
||||
return menu;
|
||||
}
|
||||
|
||||
protected override IFileSystem2 FileSystem
|
||||
{
|
||||
get { return FileSystemProviderManager.Current.XsltFileSystem; }
|
||||
}
|
||||
|
||||
private static readonly string[] ExtensionsStatic = { "xslt" };
|
||||
|
||||
protected override string[] Extensions
|
||||
{
|
||||
get { return ExtensionsStatic; }
|
||||
}
|
||||
protected override string FileIcon
|
||||
{
|
||||
get { return "icon-code"; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -315,9 +315,11 @@
|
||||
<Compile Include="Cache\TemplateCacheRefresher.cs" />
|
||||
<Compile Include="Cache\UnpublishedPageCacheRefresher.cs" />
|
||||
<Compile Include="Cache\UserCacheRefresher.cs" />
|
||||
<Compile Include="Features\DisabledFeatures.cs" />
|
||||
<Compile Include="Editors\BackOfficeNotificationsController.cs" />
|
||||
<Compile Include="Editors\BackOfficeServerVariables.cs" />
|
||||
<Compile Include="Editors\DashboardHelper.cs" />
|
||||
<Compile Include="Editors\EditorModelEventArgs.cs" />
|
||||
<Compile Include="Editors\EditorValidationResolver.cs" />
|
||||
<Compile Include="Editors\EditorValidator.cs" />
|
||||
<Compile Include="Editors\FromJsonPathAttribute.cs" />
|
||||
@@ -328,6 +330,8 @@
|
||||
<Compile Include="Editors\ParameterSwapControllerActionSelector.cs" />
|
||||
<Compile Include="Editors\CodeFileController.cs" />
|
||||
<Compile Include="Editors\TourController.cs" />
|
||||
<Compile Include="Models\BackOfficeTourFilter.cs" />
|
||||
<Compile Include="TourFilterResolver.cs" />
|
||||
<Compile Include="Editors\UserEditorAuthorizationHelper.cs" />
|
||||
<Compile Include="Editors\UserGroupAuthorizationAttribute.cs" />
|
||||
<Compile Include="Editors\UserGroupEditorAuthorizationHelper.cs" />
|
||||
@@ -335,6 +339,8 @@
|
||||
<Compile Include="Editors\UserGroupValidateAttribute.cs" />
|
||||
<Compile Include="ExamineStartup.cs" />
|
||||
<Compile Include="Extensions\UdiExtensions.cs" />
|
||||
<Compile Include="Features\UmbracoFeatures.cs" />
|
||||
<Compile Include="Features\FeaturesResolver.cs" />
|
||||
<Compile Include="HealthCheck\Checks\Config\AbstractConfigCheck.cs" />
|
||||
<Compile Include="HealthCheck\Checks\Config\AcceptableConfiguration.cs" />
|
||||
<Compile Include="HealthCheck\Checks\Config\ConfigurationService.cs" />
|
||||
@@ -376,6 +382,7 @@
|
||||
<Compile Include="Models\ContentEditing\AssignedContentPermissions.cs" />
|
||||
<Compile Include="Models\ContentEditing\AssignedUserGroupPermissions.cs" />
|
||||
<Compile Include="Models\ContentEditing\ContentRedirectUrl.cs" />
|
||||
<Compile Include="Models\ContentEditing\EditorNavigation.cs" />
|
||||
<Compile Include="Models\ContentEditing\GetAvailableCompositionsFilter.cs" />
|
||||
<Compile Include="Editors\IEditorValidator.cs" />
|
||||
<Compile Include="Editors\EditorModelEventManager.cs" />
|
||||
@@ -496,6 +503,7 @@
|
||||
<Compile Include="Models\Mapping\PropertyTypeGroupResolver.cs" />
|
||||
<Compile Include="Security\Identity\PreviewAuthenticationMiddleware.cs" />
|
||||
<Compile Include="SingletonHttpContextAccessor.cs" />
|
||||
<Compile Include="Trees\MacroTreeController.cs" />
|
||||
<Compile Include="Trees\UserTreeController.cs" />
|
||||
<Compile Include="Suspendable.cs" />
|
||||
<Compile Include="Trees\ContentBlueprintTreeController.cs" />
|
||||
@@ -504,6 +512,7 @@
|
||||
<Compile Include="Trees\MediaTypeTreeController.cs" />
|
||||
<Compile Include="Trees\MemberTypeTreeController.cs" />
|
||||
<Compile Include="Security\WebAuthExtensions.cs" />
|
||||
<Compile Include="Trees\XsltTreeController.cs" />
|
||||
<Compile Include="umbraco.presentation\SafeXmlReaderWriter.cs" />
|
||||
<Compile Include="Trees\ScriptTreeController.cs" />
|
||||
<Compile Include="umbraco.presentation\umbraco\developer\Packages\installer.aspx.cs">
|
||||
@@ -794,6 +803,7 @@
|
||||
<Compile Include="WebApi\Filters\ClearAngularAntiForgeryTokenAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\DisableBrowserCacheAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\EnableOverrideAuthorizationAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\FeatureAuthorizeAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\FilterGrouping.cs" />
|
||||
<Compile Include="WebApi\Filters\LegacyTreeAuthorizeAttribute.cs" />
|
||||
<Compile Include="WebApi\Filters\OutgoingEditorModelEventAttribute.cs" />
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Umbraco.Web
|
||||
/// <summary>
|
||||
/// Class that encapsulates Umbraco information of a specific HTTP request
|
||||
/// </summary>
|
||||
public class UmbracoContext : DisposableObject, IDisposeOnRequestEnd
|
||||
public class UmbracoContext : DisposableObjectSlim, IDisposeOnRequestEnd
|
||||
{
|
||||
internal const string HttpContextItemName = "Umbraco.Web.UmbracoContext";
|
||||
private static readonly object Locker = new object();
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Web.Http;
|
||||
using System.Web.Http.Controllers;
|
||||
using Umbraco.Web.Features;
|
||||
|
||||
namespace Umbraco.Web.WebApi.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// Ensures that the controller is an authorized feature.
|
||||
/// </summary>
|
||||
/// <remarks>Else returns unauthorized.</remarks>
|
||||
public sealed class FeatureAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
protected override bool IsAuthorized(HttpActionContext actionContext)
|
||||
{
|
||||
var controllerType = actionContext.ControllerContext.ControllerDescriptor.ControllerType;
|
||||
return FeaturesResolver.Current.Features.IsEnabled(controllerType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,12 +6,14 @@ using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Security;
|
||||
using Umbraco.Web.WebApi.Filters;
|
||||
|
||||
namespace Umbraco.Web.WebApi
|
||||
{
|
||||
/// <summary>
|
||||
/// The base class for API controllers that expose Umbraco services - THESE ARE NOT AUTO ROUTED
|
||||
/// </summary>
|
||||
[FeatureAuthorize]
|
||||
public abstract class UmbracoApiControllerBase : ApiController
|
||||
{
|
||||
protected UmbracoApiControllerBase()
|
||||
@@ -35,7 +37,7 @@ namespace Umbraco.Web.WebApi
|
||||
InstanceId = Guid.NewGuid();
|
||||
_umbraco = umbracoHelper;
|
||||
}
|
||||
|
||||
|
||||
private UmbracoHelper _umbraco;
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -38,6 +38,7 @@ using Umbraco.Core.Persistence.UnitOfWork;
|
||||
using Umbraco.Core.Scoping;
|
||||
using Umbraco.Core.Services;
|
||||
using Umbraco.Web.Editors;
|
||||
using Umbraco.Web.Features;
|
||||
using Umbraco.Web.HealthCheck;
|
||||
using Umbraco.Web.Profiling;
|
||||
using Umbraco.Web.Search;
|
||||
@@ -352,6 +353,10 @@ namespace Umbraco.Web
|
||||
protected override void InitializeResolvers()
|
||||
{
|
||||
base.InitializeResolvers();
|
||||
|
||||
FeaturesResolver.Current = new FeaturesResolver(new UmbracoFeatures());
|
||||
|
||||
TourFilterResolver.Current = new TourFilterResolver(ServiceProvider, LoggerResolver.Current.Logger);
|
||||
|
||||
SearchableTreeResolver.Current = new SearchableTreeResolver(ServiceProvider, LoggerResolver.Current.Logger, ApplicationContext.Services.ApplicationTreeService, () => PluginManager.ResolveSearchableTrees());
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
@@ -29,10 +30,8 @@ using Umbraco.Core;
|
||||
|
||||
namespace umbraco
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles loading of the cache application into the developer application tree
|
||||
/// </summary>
|
||||
[Tree(Constants.Applications.Developer, "macros", "Macros", sortOrder: 2)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
|
||||
public class loadMacros : BaseTree
|
||||
{
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
@@ -29,10 +30,8 @@ using Umbraco.Core;
|
||||
|
||||
namespace umbraco
|
||||
{
|
||||
/// <summary>
|
||||
/// Handles loading of the xslt files into the application tree
|
||||
/// </summary>
|
||||
[Tree(Constants.Applications.Developer, "xslt", "XSLT Files", sortOrder: 5)]
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[Obsolete("This is no longer used and will be removed from the codebase in the future")]
|
||||
public class loadXslt : FileSystemTree
|
||||
{
|
||||
|
||||
|
||||
@@ -42,8 +42,8 @@ namespace umbraco.cms.presentation.developer
|
||||
if (IsPostBack == false)
|
||||
{
|
||||
ClientTools
|
||||
.SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree<loadMacros>().Tree.Alias)
|
||||
.SyncTree("-1,init," + _macro.Id, false);
|
||||
.SetActiveTreeType(Constants.Trees.Macros)
|
||||
.SyncTree("-1," + _macro.Id, false);
|
||||
|
||||
string tempMacroAssembly = _macro.ControlAssembly ?? "";
|
||||
string tempMacroType = _macro.ControlType ?? "";
|
||||
@@ -321,8 +321,8 @@ namespace umbraco.cms.presentation.developer
|
||||
Page.Validate();
|
||||
|
||||
ClientTools
|
||||
.SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree<loadMacros>().Tree.Alias)
|
||||
.SyncTree("-1,init," + _macro.Id.ToInvariantString(), true); //true forces the reload
|
||||
.SetActiveTreeType(Constants.Trees.Macros)
|
||||
.SyncTree("-1," + _macro.Id.ToInvariantString(), true); //true forces the reload
|
||||
|
||||
var tempMacroAssembly = macroAssembly.Text;
|
||||
var tempMacroType = macroType.Text;
|
||||
|
||||
@@ -33,9 +33,9 @@ namespace umbraco.cms.presentation.developer
|
||||
if (!IsPostBack)
|
||||
{
|
||||
string file = Request.QueryString["file"];
|
||||
string path = DeepLink.GetTreePathFromFilePath(file);
|
||||
string path = DeepLink.GetTreePathFromFilePath(file, false, true);
|
||||
ClientTools
|
||||
.SetActiveTreeType(TreeDefinitionCollection.Instance.FindTree<loadXslt>().Tree.Alias)
|
||||
.SetActiveTreeType(Constants.Trees.Xslt)
|
||||
.SyncTree(path, false);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ using Umbraco.Core.IO;
|
||||
namespace umbraco.cms.businesslogic.packager.repositories
|
||||
{
|
||||
[Obsolete("This should not be used and will be removed in future Umbraco versions")]
|
||||
public class Repository : DisposableObject
|
||||
public class Repository : DisposableObjectSlim
|
||||
{
|
||||
public string Guid { get; private set; }
|
||||
|
||||
@@ -289,7 +289,7 @@ namespace umbraco.cms.businesslogic.packager.repositories
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObject"/> which handles common required locking logic.
|
||||
/// Handles the disposal of resources. Derived from abstract class <see cref="DisposableObjectSlim"/> which handles common required locking logic.
|
||||
/// </summary>
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using umbraco.BusinessLogic;
|
||||
using System.Web;
|
||||
@@ -11,15 +12,24 @@ namespace umbraco.cms.helpers
|
||||
public class DeepLink
|
||||
{
|
||||
public static string GetTreePathFromFilePath(string filePath)
|
||||
{
|
||||
return GetTreePathFromFilePath(filePath, true, false);
|
||||
}
|
||||
|
||||
internal static string GetTreePathFromFilePath(string filePath, bool includeInit, bool urlEncode)
|
||||
{
|
||||
List<string> treePath = new List<string>();
|
||||
treePath.Add("-1");
|
||||
treePath.Add("init");
|
||||
if (includeInit)
|
||||
treePath.Add("init");
|
||||
string[] pathPaths = filePath.Split('/');
|
||||
|
||||
for (int p = 0; p < pathPaths.Length; p++)
|
||||
{
|
||||
treePath.Add(string.Join("/", pathPaths.Take(p + 1).ToArray()));
|
||||
var s = string.Join("/", pathPaths.Take(p + 1).ToArray());
|
||||
if (urlEncode)
|
||||
s = WebUtility.UrlEncode(s);
|
||||
treePath.Add(s);
|
||||
}
|
||||
string sPath = string.Join(",", treePath.ToArray());
|
||||
return sPath;
|
||||
|
||||
Reference in New Issue
Block a user