Merge branch 'temp8' into temp8-tree-sections-no-more-xml

This commit is contained in:
Stephan
2019-01-18 09:23:55 +01:00
75 changed files with 1308 additions and 904 deletions
+1 -1
View File
@@ -6,7 +6,7 @@ _Ready to try out Version 8? [See the quick start guide](V8_GETTING_STARTED.md).
When is Umbraco 8 coming?
=========================
When it's ready. We're done with the major parts of the architecture work and are focusing on three seperate tracks to prepare Umbraco 8 for release:
When it's ready. We're done with the major parts of the architecture work and are focusing on three separate tracks to prepare Umbraco 8 for release:
1) Editor Track (_currently in progress_). Without editors, there's no market for Umbraco. So we want to make sure that Umbraco 8 is full of love for editors.
2) Partner Track. Without anyone implementing Umbraco, there's nothing for editors to update. So we want to make sure that Umbraco 8 is a joy to implement
3) Contributor Track. Without our fabulous ecosystem of both individual Umbracians and 3rd party ISVs, Umbraco wouldn't be as rich a platform as it is today. We want to make sure that it's easy, straight forward and as backwards-compatible as possible to create packages for Umbraco
+2 -1
View File
@@ -13,6 +13,7 @@
*.suo
*.vs10x
*.ndproj
*.ignorer.*
# common directories
.DS_Store
@@ -156,4 +157,4 @@ build/temp/
# eof
# eof
@@ -22,20 +22,17 @@ namespace Umbraco.Core.Deploy
/// Gets the value to be deployed from the control value as a string.
/// </summary>
/// <param name="gridControl">The control containing the value.</param>
/// <param name="property">The property where the control is located. Do not modify - only used for context</param>
/// <param name="dependencies">The dependencies of the property.</param>
/// <returns>The grid cell value to be deployed.</returns>
/// <remarks>Note that </remarks>
string GetValue(GridValue.GridControl gridControl, Property property, ICollection<ArtifactDependency> dependencies);
string GetValue(GridValue.GridControl gridControl, ICollection<ArtifactDependency> dependencies);
/// <summary>
/// Allows you to modify the value of a control being deployed.
/// </summary>
/// <param name="gridControl">The control being deployed.</param>
/// <param name="property">The property where the <paramref name="gridControl"/> is located. Do not modify - only used for context.</param>
/// <remarks>Follows the pattern of the property value connectors (<see cref="IValueConnector"/>). The SetValue method is used to modify the value of the <paramref name="gridControl"/>.</remarks>
/// <remarks>Note that only the <paramref name="gridControl"/> value should be modified - not the <paramref name="property"/>.</remarks>
/// <remarks>The <paramref name="property"/> should only be used to assist with context data relevant when setting the <paramref name="gridControl"/> value.</remarks>
void SetValue(GridValue.GridControl gridControl, Property property);
void SetValue(GridValue.GridControl gridControl);
}
}
+4 -2
View File
@@ -20,16 +20,18 @@ namespace Umbraco.Core.Deploy
/// Gets the deploy property value corresponding to a content property value, and gather dependencies.
/// </summary>
/// <param name="value">The content property value.</param>
/// <param name="propertyType">The value property type</param>
/// <param name="dependencies">The content dependencies.</param>
/// <returns>The deploy property value.</returns>
string ToArtifact(object value, ICollection<ArtifactDependency> dependencies);
string ToArtifact(object value, PropertyType propertyType, ICollection<ArtifactDependency> dependencies);
/// <summary>
/// Gets the content property value corresponding to a deploy property value.
/// </summary>
/// <param name="value">The deploy property value.</param>
/// <param name="propertyType">The value property type<</param>
/// <param name="currentValue">The current content property value.</param>
/// <returns>The content property value.</returns>
object FromArtifact(string value, object currentValue);
object FromArtifact(string value, PropertyType propertyType, object currentValue);
}
}
@@ -92,7 +92,7 @@ namespace Umbraco.Core.Logging.Serilog
/// <summary>
/// Reads settings from /config/serilog.user.config
/// That allows a seperate logging pipeline to be configured that wil not affect the main Umbraco log
/// That allows a separate logging pipeline to be configured that wil not affect the main Umbraco log
/// </summary>
/// <param name="logConfig">A Serilog LoggerConfiguration</param>
public static LoggerConfiguration ReadFromUserConfigFile(this LoggerConfiguration logConfig)
@@ -3,6 +3,7 @@ using System.Configuration;
using Semver;
using Umbraco.Core.Configuration;
using Umbraco.Core.Migrations.Upgrade.V_7_12_0;
using Umbraco.Core.Migrations.Upgrade.V_7_14_0;
using Umbraco.Core.Migrations.Upgrade.V_8_0_0;
namespace Umbraco.Core.Migrations.Upgrade
@@ -122,6 +123,7 @@ namespace Umbraco.Core.Migrations.Upgrade
To<MakeTagsVariant>("{C39BF2A7-1454-4047-BBFE-89E40F66ED63}");
To<MakeRedirectUrlVariant>("{64EBCE53-E1F0-463A-B40B-E98EFCCA8AE2}");
To<AddContentTypeIsElementColumn>("{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}");
To<UpdateMemberGroupPickerData>("{8A027815-D5CD-4872-8B88-9A51AB5986A6}"); // from 7.14.0
//FINAL
@@ -145,12 +147,23 @@ namespace Umbraco.Core.Migrations.Upgrade
// main chain, skipping the migrations
//
From("{init-7.12.0}");
// start stop target
// clone start / clone stop / target
ToWithClone("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}");
From("{init-7.12.1}").To("{init-7.10.0}"); // same as 7.12.0
From("{init-7.12.2}").To("{init-7.10.0}"); // same as 7.12.0
From("{init-7.12.3}").To("{init-7.10.0}"); // same as 7.12.0
From("{init-7.12.4}").To("{init-7.10.0}"); // same as 7.12.0
From("{init-7.13.0}").To("{init-7.10.0}"); // same as 7.12.0
From("{init-7.13.1}").To("{init-7.10.0}"); // same as 7.12.0
// 7.14.0 has migrations, handle it...
// clone going from 7.10 to 1350617A (the last one before we started to merge 7.12 migrations), then
// clone going from CF51B39B (after 7.12 migrations) to 0009109C (the last one before we started to merge 7.12 migrations),
// ending in 8A027815 (after 7.14 migrations)
From("{init-7.14.0}")
.ToWithClone("{init-7.10.0}", "{1350617A-4930-4D61-852F-E3AA9E692173}", "{9109B8AF-6B34-46EE-9484-7434196D0C79}")
.ToWithClone("{CF51B39B-9B9A-4740-BB7C-EAF606A7BFBF}", "{0009109C-A0B8-4F3F-8FEB-C137BBDDA268}", "{8A027815-D5CD-4872-8B88-9A51AB5986A6}");
}
}
}
@@ -0,0 +1,29 @@
namespace Umbraco.Core.Migrations.Upgrade.V_7_14_0
{
/// <summary>
/// Migrates member group picker properties from NVarchar to NText. See https://github.com/umbraco/Umbraco-CMS/issues/3268.
/// </summary>
public class UpdateMemberGroupPickerData : MigrationBase
{
/// <summary>
/// Migrates member group picker properties from NVarchar to NText. See https://github.com/umbraco/Umbraco-CMS/issues/3268.
/// </summary>
public UpdateMemberGroupPickerData(IMigrationContext context)
: base(context)
{ }
public override void Migrate()
{
Database.Execute($@"UPDATE umbracoPropertyData SET textValue = varcharValue, varcharValue = NULL
WHERE textValue IS NULL AND id IN (
SELECT id FROM umbracoPropertyData WHERE propertyTypeId in (
SELECT id from cmsPropertyType where dataTypeId IN (
SELECT nodeId FROM umbracoDataType WHERE propertyEditorAlias = '{Constants.PropertyEditors.Aliases.MemberGroupPicker}'
)
)
)");
Database.Execute($"UPDATE umbracoDataType SET dbType = 'Ntext' WHERE propertyEditorAlias = '{Constants.PropertyEditors.Aliases.MemberGroupPicker}'");
}
}
}
@@ -68,7 +68,7 @@ namespace Umbraco.Core.Models.PublishedContent
var typeName = attribute == null ? type.Name : attribute.ContentTypeAlias;
if (modelInfos.TryGetValue(typeName, out var modelInfo))
throw new InvalidOperationException($"Both types {type.FullName} and {modelInfo.ModelType.FullName} want to be a model type for content type with alias \"{typeName}\".");
throw new InvalidOperationException($"Both types '{type.AssemblyQualifiedName}' and '{modelInfo.ModelType.AssemblyQualifiedName}' want to be a model type for content type with alias \"{typeName}\".");
// have to use an unsafe ctor because we don't know the types, really
var modelCtor = ReflectionUtilities.EmitConstructorUnsafe<Func<object, object>>(constructor);
+1 -1
View File
@@ -892,7 +892,7 @@ namespace Umbraco.Core
}
/// <summary>
/// Ensures that the folder path ends with a DirectorySeperatorChar
/// Ensures that the folder path ends with a DirectorySeparatorChar
/// </summary>
/// <param name="currentFolder"></param>
/// <returns></returns>
+1
View File
@@ -367,6 +367,7 @@
<Compile Include="Migrations\Upgrade\V_7_12_0\RenameTrueFalseField.cs" />
<Compile Include="Migrations\Upgrade\V_7_12_0\SetDefaultTagsStorageType.cs" />
<Compile Include="Migrations\Upgrade\V_7_12_0\UpdateUmbracoConsent.cs" />
<Compile Include="Migrations\Upgrade\V_7_14_0\UpdateMemberGroupPickerData.cs" />
<Compile Include="Migrations\Upgrade\V_7_8_0\AddIndexToPropertyTypeAliasColumn.cs" />
<Compile Include="Migrations\Upgrade\V_7_8_0\AddInstructionCountColumn.cs" />
<Compile Include="Migrations\Upgrade\V_7_8_0\AddMediaVersionTable.cs" />
@@ -97,7 +97,7 @@ namespace Umbraco.Tests.PublishedContent
{
var doc = GetContent(true, 1);
//change a doc type alias
var c = (TestPublishedContent) doc.Children.ElementAt(0);
var c = (TestPublishedContent)doc.Children.ElementAt(0);
c.ContentType = new PublishedContentType(22, "DontMatch", PublishedItemType.Content, Enumerable.Empty<string>(), Enumerable.Empty<PublishedPropertyType>(), ContentVariation.Nothing);
var dt = doc.ChildrenAsTable(Current.Services, "Child");
@@ -4163,7 +4163,7 @@ html {
text-align: center;
}
.text--center .seperator {
.text--center .separator {
margin-right: auto;
margin-left: auto;
}
@@ -4998,4 +4998,4 @@ nav > ul li.active ul li a {
</PreValues>
</DataType>
</DataTypes>
</umbPackage>
</umbPackage>
@@ -8,10 +8,6 @@ namespace Umbraco.Tests.Testing.Objects.Accessors
public class TestVariationContextAccessor : IVariationContextAccessor
{
/// <inheritdoc />
public VariationContext VariationContext
{
get;
set;
}
public VariationContext VariationContext { get; set; }
}
}
+52
View File
@@ -0,0 +1,52 @@
'use strict';
module.exports = {
sources: {
//less files used by backoffice and preview
//processed in the less task
less: {
installer: { files: ["./src/less/installer.less"], out: "installer.css" },
nonodes: { files: ["./src/less/pages/nonodes.less"], out: "nonodes.style.min.css"},
preview: { files: ["./src/less/canvas-designer.less"], out: "canvasdesigner.css" },
umbraco: { files: ["./src/less/belle.less"], out: "umbraco.css" }
},
//js files for backoffie
//processed in the js task
js: {
preview: { files: ["./src/preview/**/*.js"], out: "umbraco.preview.js" },
installer: { files: ["./src/installer/**/*.js"], out: "umbraco.installer.js" },
controllers: { files: ["./src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" },
directives: { files: ["./src/common/directives/**/*.js"], out: "umbraco.directives.js" },
filters: { files: ["./src/common/filters/**/*.js"], out: "umbraco.filters.js" },
resources: { files: ["./src/common/resources/**/*.js"], out: "umbraco.resources.js" },
services: { files: ["./src/common/services/**/*.js"], out: "umbraco.services.js" },
security: { files: ["./src/common/interceptors/**/*.js"], out: "umbraco.interceptors.js" }
},
//selectors for copying all views into the build
//processed in the views task
views:{
umbraco: {files: ["./src/views/**/*.html"], folder: ""},
installer: {files: ["./src/installer/steps/*.html"], folder: "install/"}
},
//globs for file-watching
globs:{
views: "./src/views/**/*.html",
less: "./src/less/**/*.less",
js: "./src/*.js",
lib: "./lib/**/*",
assets: "./src/assets/**"
}
},
root: "../Umbraco.Web.UI/Umbraco/",
targets: {
js: "js/",
lib: "lib/",
views: "views/",
css: "assets/css/",
assets: "assets/"
}
};
+10
View File
@@ -0,0 +1,10 @@
'use strict';
var fs = require('fs');
var onlyScripts = require('./util/scriptFilter');
var tasks = fs.readdirSync('./gulp/tasks/').filter(onlyScripts);
tasks.forEach(function(task) {
require('./tasks/' + task);
});
@@ -0,0 +1,10 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var runSequence = require('run-sequence');
// Build - build the files ready for production
gulp.task('build', function(cb) {
runSequence(["dependencies", "js", "less", "views"], cb);
});
@@ -0,0 +1,288 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var MergeStream = require('merge-stream');
var imagemin = require('gulp-imagemin');
/**************************
* Task processes and copies all dependencies, either installed by npm or stored locally in the project
**************************/
gulp.task('dependencies', function () {
//as we do multiple things in this task, we merge the multiple streams
var stream = new MergeStream();
// Pick the dependencies we need from each package
// so we don't just ship with a lot of files that aren't needed
const nodeModules = [
{
"name": "ace-builds",
"src": [
"./node_modules/ace-builds/src-min-noconflict/ace.js",
"./node_modules/ace-builds/src-min-noconflict/ext-language_tools.js",
"./node_modules/ace-builds/src-min-noconflict/ext-searchbox.js",
"./node_modules/ace-builds/src-min-noconflict/ext-settings_menu.js",
"./node_modules/ace-builds/src-min-noconflict/snippets/text.js",
"./node_modules/ace-builds/src-min-noconflict/snippets/javascript.js",
"./node_modules/ace-builds/src-min-noconflict/snippets/css.js",
"./node_modules/ace-builds/src-min-noconflict/theme-chrome.js",
"./node_modules/ace-builds/src-min-noconflict/mode-razor.js",
"./node_modules/ace-builds/src-min-noconflict/mode-javascript.js",
"./node_modules/ace-builds/src-min-noconflict/mode-css.js",
"./node_modules/ace-builds/src-min-noconflict/worker-javascript.js",
"./node_modules/ace-builds/src-min-noconflict/worker-css.js"
],
"base": "./node_modules/ace-builds"
},
{
"name": "angular",
"src": ["./node_modules/angular/angular.js"],
"base": "./node_modules/angular"
},
{
"name": "angular-cookies",
"src": ["./node_modules/angular-cookies/angular-cookies.js"],
"base": "./node_modules/angular-cookies"
},
{
"name": "angular-dynamic-locale",
"src": [
"./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js",
"./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js.map"
],
"base": "./node_modules/angular-dynamic-locale/dist"
},
{
"name": "angular-sanitize",
"src": ["./node_modules/angular-sanitize/angular-sanitize.js"],
"base": "./node_modules/angular-sanitize"
},
{
"name": "angular-touch",
"src": ["./node_modules/angular-touch/angular-touch.js"],
"base": "./node_modules/angular-touch"
},
{
"name": "angular-ui-sortable",
"src": ["./node_modules/angular-ui-sortable/dist/sortable.js"],
"base": "./node_modules/angular-ui-sortable/dist"
},
{
"name": "angular-route",
"src": ["./node_modules/angular-route/angular-route.js"],
"base": "./node_modules/angular-route"
},
{
"name": "angular-animate",
"src": ["./node_modules/angular-animate/angular-animate.js"],
"base": "./node_modules/angular-animate"
},
{
"name": "angular-i18n",
"src": [
"./node_modules/angular-i18n/angular-i18n.js",
"./node_modules/angular-i18n/angular-locale_*.js"
],
"base": "./node_modules/angular-i18n"
},
{
"name": "angular-local-storage",
"src": [
"./node_modules/angular-local-storage/dist/angular-local-storage.min.js",
"./node_modules/angular-local-storage/dist/angular-local-storage.min.js.map"
],
"base": "./node_modules/angular-local-storage/dist"
},
{
"name": "angular-messages",
"src": ["./node_modules/angular-messages/angular-messages.js"],
"base": "./node_modules/angular-messages"
},
{
"name": "angular-mocks",
"src": ["./node_modules/angular-mocks/angular-mocks.js"],
"base": "./node_modules/angular-mocks"
},
{
"name": "animejs",
"src": ["./node_modules/animejs/anime.min.js"],
"base": "./node_modules/animejs"
},
{
"name": "bootstrap-social",
"src": ["./node_modules/bootstrap-social/bootstrap-social.css"],
"base": "./node_modules/bootstrap-social"
},
{
"name": "angular-chart.js",
"src": ["./node_modules/angular-chart.js/dist/angular-chart.min.js"],
"base": "./node_modules/angular-chart.js/dist"
},
{
"name": "chart.js",
"src": ["./node_modules/chart.js/dist/chart.min.js"],
"base": "./node_modules/chart.js/dist"
},
{
"name": "clipboard",
"src": ["./node_modules/clipboard/dist/clipboard.min.js"],
"base": "./node_modules/clipboard/dist"
},
{
"name": "jsdiff",
"src": ["./node_modules/diff/dist/diff.min.js"],
"base": "./node_modules/diff/dist"
},
{
"name": "flatpickr",
"src": [
"./node_modules/flatpickr/dist/flatpickr.js",
"./node_modules/flatpickr/dist/flatpickr.css"
],
"base": "./node_modules/flatpickr/dist"
},
{
"name": "font-awesome",
"src": [
"./node_modules/font-awesome/fonts/*",
"./node_modules/font-awesome/css/font-awesome.min.css"
],
"base": "./node_modules/font-awesome"
},
{
"name": "jquery",
"src": [
"./node_modules/jquery/dist/jquery.min.js",
"./node_modules/jquery/dist/jquery.min.map"
],
"base": "./node_modules/jquery/dist"
},
{
"name": "jquery-ui",
"src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"],
"base": "./node_modules/jquery-ui-dist"
},
{
"name": "jquery-ui-touch-punch",
"src": ["./node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"],
"base": "./node_modules/jquery-ui-touch-punch"
},
{
"name": "lazyload-js",
"src": ["./node_modules/lazyload-js/lazyload.min.js"],
"base": "./node_modules/lazyload-js"
},
{
"name": "moment",
"src": ["./node_modules/moment/min/moment.min.js"],
"base": "./node_modules/moment/min"
},
{
"name": "moment",
"src": ["./node_modules/moment/locale/*.js"],
"base": "./node_modules/moment/locale"
},
{
"name": "ng-file-upload",
"src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"],
"base": "./node_modules/ng-file-upload/dist"
},
{
"name": "nouislider",
"src": [
"./node_modules/nouislider/distribute/nouislider.min.js",
"./node_modules/nouislider/distribute/nouislider.min.css"
],
"base": "./node_modules/nouislider/distribute"
},
{
"name": "signalr",
"src": ["./node_modules/signalr/jquery.signalR.js"],
"base": "./node_modules/signalr"
},
{
"name": "spectrum",
"src": [
"./node_modules/spectrum-colorpicker/spectrum.js",
"./node_modules/spectrum-colorpicker/spectrum.css"
],
"base": "./node_modules/spectrum-colorpicker"
},
{
"name": "tinymce",
"src": [
"./node_modules/tinymce/tinymce.min.js",
"./node_modules/tinymce/plugins/**",
"./node_modules/tinymce/skins/**",
"./node_modules/tinymce/themes/**"
],
"base": "./node_modules/tinymce"
},
{
"name": "typeahead.js",
"src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"],
"base": "./node_modules/typeahead.js/dist"
},
{
"name": "underscore",
"src": ["node_modules/underscore/underscore-min.js"],
"base": "./node_modules/underscore"
}
];
// add streams for node modules
nodeModules.forEach(module => {
stream.add(
gulp.src(module.src,
{ base: module.base })
.pipe(gulp.dest(config.root + config.targets.lib + "/" + module.name))
);
});
//copy over libs which are not on npm (/lib)
stream.add(
gulp.src(config.sources.globs.lib)
.pipe(gulp.dest(config.root + config.targets.lib))
);
//Copies all static assets into /root / assets folder
//css, fonts and image files
stream.add(
gulp.src(config.sources.globs.assets)
.pipe(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.jpegtran({progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({
plugins: [
{removeViewBox: true},
{cleanupIDs: false}
]
})
]))
.pipe(gulp.dest(config.root + config.targets.assets))
);
// Copies all the less files related to the preview into their folder
//these are not pre-processed as preview has its own less combiler client side
stream.add(
gulp.src("src/canvasdesigner/editors/*.less")
.pipe(gulp.dest(config.root + config.targets.assets + "/less"))
);
// Todo: check if we need these fileSize
stream.add(
gulp.src("src/views/propertyeditors/grid/config/*.*")
.pipe(gulp.dest(config.root + config.targets.views + "/propertyeditors/grid/config"))
);
stream.add(
gulp.src("src/views/dashboard/default/*.jpg")
.pipe(gulp.dest(config.root + config.targets.views + "/dashboard/default"))
);
return stream;
});
@@ -0,0 +1,10 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var runSequence = require('run-sequence');
// Dev - build the files ready for development and start watchers
gulp.task('dev', function(cb) {
runSequence(["dependencies", "js", "less", "views"], "watch", cb);
});
@@ -0,0 +1,55 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var connect = require('gulp-connect');
var open = require('gulp-open');
var gulpDocs = require('gulp-ngdocs');
/**************************
* Build Backoffice UI API documentation
**************************/
gulp.task('docs', [], function (cb) {
var options = {
html5Mode: false,
startPage: '/api',
title: "Umbraco Backoffice UI API Documentation",
dest: 'docs/api',
styles: ['docs/umb-docs.css'],
image: "https://our.umbraco.com/assets/images/logo.svg"
}
return gulpDocs.sections({
api: {
glob: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'],
api: true,
title: 'API Documentation'
}
})
.pipe(gulpDocs.process(options))
.pipe(gulp.dest('docs/api'));
cb();
});
gulp.task('connect:docs', function (cb) {
connect.server({
root: 'docs/api',
livereload: true,
fallback: 'docs/api/index.html',
port: 8880
});
cb();
});
gulp.task('open:docs', function (cb) {
var options = {
uri: 'http://localhost:8880/index.html'
};
gulp.src(__filename)
.pipe(open(options));
cb();
});
@@ -0,0 +1,10 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var runSequence = require('run-sequence');
// Docserve - build and open the back office documentation
gulp.task('docserve', function(cb) {
runSequence('docs', 'connect:docs', 'open:docs', cb);
});
@@ -0,0 +1,29 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var _ = require('lodash');
var MergeStream = require('merge-stream');
var processJs = require('../util/processJs');
/**************************
* Copies all angular JS files into their seperate umbraco.*.js file
**************************/
gulp.task('js', function () {
//we run multiple streams, so merge them all together
var stream = new MergeStream();
stream.add(
gulp.src(config.sources.globs.js)
.pipe(gulp.dest(config.root + config.targets.js))
);
_.forEach(config.sources.js, function (group) {
stream.add (processJs(group.files, group.out) );
});
return stream;
});
@@ -0,0 +1,20 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var _ = require('lodash');
var MergeStream = require('merge-stream');
var processLess = require('../util/processLess');
gulp.task('less', function () {
var stream = new MergeStream();
_.forEach(config.sources.less, function (group) {
stream.add( processLess(group.files, group.out) );
});
return stream;
});
@@ -0,0 +1,26 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var karmaServer = require('karma').Server;
/**************************
* Build tests
**************************/
// Karma test
gulp.task('test:unit', function() {
new karmaServer({
configFile: __dirname + "/test/config/karma.conf.js",
keepalive: true
})
.start();
});
gulp.task('test:e2e', function() {
new karmaServer({
configFile: __dirname + "/test/config/e2e.js",
keepalive: true
})
.start();
});
@@ -0,0 +1,25 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var _ = require('lodash');
var MergeStream = require('merge-stream');
gulp.task('views', function () {
var stream = new MergeStream();
_.forEach(config.sources.views, function (group) {
console.log("copying " + group.files + " to " + config.root + config.targets.views + group.folder)
stream.add (
gulp.src(group.files)
.pipe( gulp.dest(config.root + config.targets.views + group.folder) )
);
});
return stream;
});
@@ -0,0 +1,58 @@
'use strict';
var config = require('../config');
var gulp = require('gulp');
var _ = require('lodash');
var MergeStream = require('merge-stream');
var processJs = require('../util/processJs');
var watch = require('gulp-watch');
gulp.task('watch', function () {
var stream = new MergeStream();
var watchInterval = 500;
//Setup a watcher for all groups of javascript files
_.forEach(config.sources.js, function (group) {
if(group.watch !== false){
stream.add(
watch(group.files, { ignoreInitial: true, interval: watchInterval }, function (file) {
console.info(file.path + " has changed, added to: " + group.out);
processJs(group.files, group.out);
})
);
}
});
stream.add(
//watch all less files and trigger the less task
watch(config.sources.globs.less, { ignoreInitial: true, interval: watchInterval }, function () {
gulp.run(['less']);
})
);
//watch all views - copy single file changes
stream.add(
watch(config.sources.globs.views, { interval: watchInterval })
.pipe(gulp.dest(config.root + config.targets.views))
);
//watch all app js files that will not be merged - copy single file changes
stream.add(
watch(config.sources.globs.js, { interval: watchInterval })
.pipe(gulp.dest(config.root + config.targets.js))
);
return stream;
});
+18
View File
@@ -0,0 +1,18 @@
'use strict';
var notify = require('gulp-notify');
module.exports = function(error) {
var args = Array.prototype.slice.call(arguments);
// Send error to notification center with gulp-notify
notify.onError({
title: 'Compile Error',
message: '<%= error.message %>'
}).apply(this, args);
// Keep gulp from hanging on this task
this.emit('end');
};
@@ -0,0 +1,26 @@
var config = require('../config');
var gulp = require('gulp');
var eslint = require('gulp-eslint');
var babel = require("gulp-babel");
var sort = require('gulp-sort');
var concat = require('gulp-concat');
var wrap = require("gulp-wrap-js");
module.exports = function(files, out) {
return gulp.src(files)
// check for js errors
.pipe(eslint())
// outputs the lint results to the console
.pipe(eslint.format())
// sort files in stream by path or any custom sort comparator
.pipe(babel())
.pipe(sort())
.pipe(concat(out))
.pipe(wrap('(function(){\n%= body %\n})();'))
.pipe(gulp.dest(config.root + config.targets.js));
console.log(out + " compiled");
};
@@ -0,0 +1,26 @@
var config = require('../config');
var gulp = require('gulp');
var postcss = require('gulp-postcss');
var less = require('gulp-less');
var autoprefixer = require('autoprefixer');
var cssnano = require('cssnano');
var cleanCss = require("gulp-clean-css");
var rename = require('gulp-rename');
module.exports = function(files, out) {
var processors = [
autoprefixer,
cssnano({zindex: false})
];
return gulp.src(files)
.pipe(less())
.pipe(cleanCss())
.pipe(postcss(processors))
.pipe(rename(out))
.pipe(gulp.dest(config.root + config.targets.css));
console.log(out + " compiled");
}
+9
View File
@@ -0,0 +1,9 @@
'use strict';
var path = require('path');
// Filters out non .js files. Prevents
// accidental inclusion of possible hidden files
module.exports = function(name) {
return /(\.(js)$)/i.test(path.extname(name));
};
+14 -587
View File
@@ -1,587 +1,14 @@
var gulp = require('gulp');
var watch = require('gulp-watch');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var wrap = require("gulp-wrap-js");
var sort = require('gulp-sort');
var connect = require('gulp-connect');
var open = require('gulp-open');
var babel = require("gulp-babel");
var runSequence = require('run-sequence');
var imagemin = require('gulp-imagemin');
var _ = require('lodash');
var MergeStream = require('merge-stream');
// js
var eslint = require('gulp-eslint');
//Less + css
var postcss = require('gulp-postcss');
var less = require('gulp-less');
var autoprefixer = require('autoprefixer');
var cssnano = require('cssnano');
var cleanCss = require("gulp-clean-css");
// Documentation
var gulpDocs = require('gulp-ngdocs');
// Testing
var karmaServer = require('karma').Server;
/***************************************************************
Helper functions
***************************************************************/
function processJs(files, out) {
return gulp.src(files)
// check for js errors
.pipe(eslint())
// outputs the lint results to the console
.pipe(eslint.format())
// sort files in stream by path or any custom sort comparator
.pipe(babel())
.pipe(sort())
.pipe(concat(out))
.pipe(wrap('(function(){\n%= body %\n})();'))
.pipe(gulp.dest(root + targets.js));
console.log(out + " compiled");
}
function processLess(files, out) {
var processors = [
autoprefixer,
cssnano({zindex: false})
];
return gulp.src(files)
.pipe(less())
.pipe(cleanCss())
.pipe(postcss(processors))
.pipe(rename(out))
.pipe(gulp.dest(root + targets.css));
console.log(out + " compiled");
}
/***************************************************************
Paths and destinations
Each group is iterated automatically in the setup tasks below
***************************************************************/
var sources = {
//less files used by backoffice and preview
//processed in the less task
less: {
installer: { files: ["src/less/installer.less"], out: "installer.css" },
nonodes: { files: ["src/less/pages/nonodes.less"], out: "nonodes.style.min.css"},
preview: { files: ["src/less/canvas-designer.less"], out: "canvasdesigner.css" },
umbraco: { files: ["src/less/belle.less"], out: "umbraco.css" }
},
//js files for backoffie
//processed in the js task
js: {
preview: { files: ["src/preview/**/*.js"], out: "umbraco.preview.js" },
installer: { files: ["src/installer/**/*.js"], out: "umbraco.installer.js" },
controllers: { files: ["src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" },
directives: { files: ["src/common/directives/**/*.js"], out: "umbraco.directives.js" },
filters: { files: ["src/common/filters/**/*.js"], out: "umbraco.filters.js" },
resources: { files: ["src/common/resources/**/*.js"], out: "umbraco.resources.js" },
services: { files: ["src/common/services/**/*.js"], out: "umbraco.services.js" },
security: { files: ["src/common/interceptors/**/*.js"], out: "umbraco.interceptors.js" }
},
//selectors for copying all views into the build
//processed in the views task
views:{
umbraco: {files: ["src/views/**/*.html"], folder: ""},
installer: {files: ["src/installer/steps/*.html"], folder: "install"}
},
//globs for file-watching
globs:{
views: "./src/views/**/*.html",
less: "./src/less/**/*.less",
js: "./src/*.js",
lib: "./lib/**/*",
assets: "./src/assets/**"
}
};
var root = "../Umbraco.Web.UI/Umbraco/";
var targets = {
js: "js/",
lib: "lib/",
views: "views/",
css: "assets/css/",
assets: "assets/"
};
/**************************
* Main tasks for the project to prepare backoffice files
**************************/
// Build - build the files ready for production
gulp.task('build', function(cb) {
runSequence(["dependencies", "js", "less", "views"], cb);
});
// Dev - build the files ready for development and start watchers
gulp.task('dev', function(cb) {
runSequence(["dependencies", "js", "less", "views"], "watch", cb);
});
// Docserve - build and open the back office documentation
gulp.task('docserve', function(cb) {
runSequence('docs', 'connect:docs', 'open:docs', cb);
});
/**************************
* Task processes and copies all dependencies, either installed by npm or stored locally in the project
**************************/
gulp.task('dependencies', function () {
//as we do multiple things in this task, we merge the multiple streams
var stream = new MergeStream();
// Pick the dependencies we need from each package
// so we don't just ship with a lot of files that aren't needed
const nodeModules = [
{
"name": "ace-builds",
"src": [
"./node_modules/ace-builds/src-min-noconflict/ace.js",
"./node_modules/ace-builds/src-min-noconflict/ext-language_tools.js",
"./node_modules/ace-builds/src-min-noconflict/ext-searchbox.js",
"./node_modules/ace-builds/src-min-noconflict/ext-settings_menu.js",
"./node_modules/ace-builds/src-min-noconflict/snippets/text.js",
"./node_modules/ace-builds/src-min-noconflict/snippets/javascript.js",
"./node_modules/ace-builds/src-min-noconflict/snippets/css.js",
"./node_modules/ace-builds/src-min-noconflict/theme-chrome.js",
"./node_modules/ace-builds/src-min-noconflict/mode-razor.js",
"./node_modules/ace-builds/src-min-noconflict/mode-javascript.js",
"./node_modules/ace-builds/src-min-noconflict/mode-css.js",
"./node_modules/ace-builds/src-min-noconflict/worker-javascript.js",
"./node_modules/ace-builds/src-min-noconflict/worker-css.js"
],
"base": "./node_modules/ace-builds"
},
{
"name": "angular",
"src": ["./node_modules/angular/angular.js"],
"base": "./node_modules/angular"
},
{
"name": "angular-cookies",
"src": ["./node_modules/angular-cookies/angular-cookies.js"],
"base": "./node_modules/angular-cookies"
},
{
"name": "angular-dynamic-locale",
"src": [
"./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js",
"./node_modules/angular-dynamic-locale/dist/tmhDynamicLocale.min.js.map"
],
"base": "./node_modules/angular-dynamic-locale/dist"
},
{
"name": "angular-sanitize",
"src": ["./node_modules/angular-sanitize/angular-sanitize.js"],
"base": "./node_modules/angular-sanitize"
},
{
"name": "angular-touch",
"src": ["./node_modules/angular-touch/angular-touch.js"],
"base": "./node_modules/angular-touch"
},
{
"name": "angular-ui-sortable",
"src": ["./node_modules/angular-ui-sortable/dist/sortable.js"],
"base": "./node_modules/angular-ui-sortable/dist"
},
{
"name": "angular-route",
"src": ["./node_modules/angular-route/angular-route.js"],
"base": "./node_modules/angular-route"
},
{
"name": "angular-animate",
"src": ["./node_modules/angular-animate/angular-animate.js"],
"base": "./node_modules/angular-animate"
},
{
"name": "angular-i18n",
"src": [
"./node_modules/angular-i18n/angular-i18n.js",
"./node_modules/angular-i18n/angular-locale_*.js"
],
"base": "./node_modules/angular-i18n"
},
{
"name": "angular-local-storage",
"src": [
"./node_modules/angular-local-storage/dist/angular-local-storage.min.js",
"./node_modules/angular-local-storage/dist/angular-local-storage.min.js.map"
],
"base": "./node_modules/angular-local-storage/dist"
},
{
"name": "angular-messages",
"src": ["./node_modules/angular-messages/angular-messages.js"],
"base": "./node_modules/angular-messages"
},
{
"name": "angular-mocks",
"src": ["./node_modules/angular-mocks/angular-mocks.js"],
"base": "./node_modules/angular-mocks"
},
{
"name": "animejs",
"src": ["./node_modules/animejs/anime.min.js"],
"base": "./node_modules/animejs"
},
{
"name": "bootstrap-social",
"src": ["./node_modules/bootstrap-social/bootstrap-social.css"],
"base": "./node_modules/bootstrap-social"
},
{
"name": "angular-chart.js",
"src": ["./node_modules/angular-chart.js/dist/angular-chart.min.js"],
"base": "./node_modules/angular-chart.js/dist"
},
{
"name": "chart.js",
"src": ["./node_modules/chart.js/dist/chart.min.js"],
"base": "./node_modules/chart.js/dist"
},
{
"name": "clipboard",
"src": ["./node_modules/clipboard/dist/clipboard.min.js"],
"base": "./node_modules/clipboard/dist"
},
{
"name": "jsdiff",
"src": ["./node_modules/diff/dist/diff.min.js"],
"base": "./node_modules/diff/dist"
},
{
"name": "flatpickr",
"src": [
"./node_modules/flatpickr/dist/flatpickr.js",
"./node_modules/flatpickr/dist/flatpickr.css"
],
"base": "./node_modules/flatpickr/dist"
},
{
"name": "font-awesome",
"src": [
"./node_modules/font-awesome/fonts/*",
"./node_modules/font-awesome/css/font-awesome.min.css"
],
"base": "./node_modules/font-awesome"
},
{
"name": "jquery",
"src": [
"./node_modules/jquery/dist/jquery.min.js",
"./node_modules/jquery/dist/jquery.min.map"
],
"base": "./node_modules/jquery/dist"
},
{
"name": "jquery-ui",
"src": ["./node_modules/jquery-ui-dist/jquery-ui.min.js"],
"base": "./node_modules/jquery-ui-dist"
},
{
"name": "jquery-ui-touch-punch",
"src": ["./node_modules/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js"],
"base": "./node_modules/jquery-ui-touch-punch"
},
{
"name": "lazyload-js",
"src": ["./node_modules/lazyload-js/lazyload.min.js"],
"base": "./node_modules/lazyload-js"
},
{
"name": "moment",
"src": ["./node_modules/moment/min/moment.min.js"],
"base": "./node_modules/moment/min"
},
{
"name": "moment",
"src": ["./node_modules/moment/locale/*.js"],
"base": "./node_modules/moment/locale"
},
{
"name": "ng-file-upload",
"src": ["./node_modules/ng-file-upload/dist/ng-file-upload.min.js"],
"base": "./node_modules/ng-file-upload/dist"
},
{
"name": "nouislider",
"src": [
"./node_modules/nouislider/distribute/nouislider.min.js",
"./node_modules/nouislider/distribute/nouislider.min.css"
],
"base": "./node_modules/nouislider/distribute"
},
{
"name": "signalr",
"src": ["./node_modules/signalr/jquery.signalR.js"],
"base": "./node_modules/signalr"
},
{
"name": "spectrum",
"src": [
"./node_modules/spectrum-colorpicker/spectrum.js",
"./node_modules/spectrum-colorpicker/spectrum.css"
],
"base": "./node_modules/spectrum-colorpicker"
},
{
"name": "tinymce",
"src": [
"./node_modules/tinymce/tinymce.min.js",
"./node_modules/tinymce/plugins/**",
"./node_modules/tinymce/skins/**",
"./node_modules/tinymce/themes/**"
],
"base": "./node_modules/tinymce"
},
{
"name": "typeahead.js",
"src": ["./node_modules/typeahead.js/dist/typeahead.bundle.min.js"],
"base": "./node_modules/typeahead.js/dist"
},
{
"name": "underscore",
"src": ["node_modules/underscore/underscore-min.js"],
"base": "./node_modules/underscore"
}
];
// add streams for node modules
nodeModules.forEach(module => {
stream.add(
gulp.src(module.src,
{ base: module.base })
.pipe(gulp.dest(root + targets.lib + "/" + module.name))
);
});
//copy over libs which are not on npm (/lib)
stream.add(
gulp.src(sources.globs.lib)
.pipe(gulp.dest(root + targets.lib))
);
//Copies all static assets into /root / assets folder
//css, fonts and image files
stream.add(
gulp.src(sources.globs.assets)
.pipe(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.jpegtran({progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({
plugins: [
{removeViewBox: true},
{cleanupIDs: false}
]
})
]))
.pipe(gulp.dest(root + targets.assets))
);
// Copies all the less files related to the preview into their folder
//these are not pre-processed as preview has its own less combiler client side
stream.add(
gulp.src("src/canvasdesigner/editors/*.less")
.pipe(gulp.dest(root + targets.assets + "/less"))
);
// Todo: check if we need these fileSize
stream.add(
gulp.src("src/views/propertyeditors/grid/config/*.*")
.pipe(gulp.dest(root + targets.views + "/propertyeditors/grid/config"))
);
stream.add(
gulp.src("src/views/dashboard/default/*.jpg")
.pipe(gulp.dest(root + targets.views + "/dashboard/default"))
);
return stream;
});
/**************************
* Copies all angular JS files into their seperate umbraco.*.js file
**************************/
gulp.task('js', function () {
//we run multiple streams, so merge them all together
var stream = new MergeStream();
stream.add(
gulp.src(sources.globs.js)
.pipe(gulp.dest(root + targets.js))
);
_.forEach(sources.js, function (group) {
stream.add (processJs(group.files, group.out) );
});
return stream;
});
gulp.task('less', function () {
var stream = new MergeStream();
_.forEach(sources.less, function (group) {
stream.add( processLess(group.files, group.out) );
});
return stream;
});
gulp.task('views', function () {
var stream = new MergeStream();
_.forEach(sources.views, function (group) {
console.log("copying " + group.files + " to " + root + targets.views + group.folder)
stream.add (
gulp.src(group.files)
.pipe( gulp.dest(root + targets.views + group.folder) )
);
});
return stream;
});
gulp.task('watch', function () {
var stream = new MergeStream();
var watchInterval = 500;
//Setup a watcher for all groups of javascript files
_.forEach(sources.js, function (group) {
if(group.watch !== false){
stream.add(
watch(group.files, { ignoreInitial: true, interval: watchInterval }, function (file) {
console.info(file.path + " has changed, added to: " + group.out);
processJs(group.files, group.out);
})
);
}
});
stream.add(
//watch all less files and trigger the less task
watch(sources.globs.less, { ignoreInitial: true, interval: watchInterval }, function () {
gulp.run(['less']);
})
);
//watch all views - copy single file changes
stream.add(
watch(sources.globs.views, { interval: watchInterval })
.pipe(gulp.dest(root + targets.views))
);
//watch all app js files that will not be merged - copy single file changes
stream.add(
watch(sources.globs.js, { interval: watchInterval })
.pipe(gulp.dest(root + targets.js))
);
return stream;
});
/**************************
* Build Backoffice UI API documentation
**************************/
gulp.task('docs', [], function (cb) {
var options = {
html5Mode: false,
startPage: '/api',
title: "Umbraco Backoffice UI API Documentation",
dest: 'docs/api',
styles: ['docs/umb-docs.css'],
image: "https://our.umbraco.com/assets/images/logo.svg"
}
return gulpDocs.sections({
api: {
glob: ['src/common/**/*.js', 'docs/src/api/**/*.ngdoc'],
api: true,
title: 'API Documentation'
}
})
.pipe(gulpDocs.process(options))
.pipe(gulp.dest('docs/api'));
cb();
});
gulp.task('connect:docs', function (cb) {
connect.server({
root: 'docs/api',
livereload: true,
fallback: 'docs/api/index.html',
port: 8880
});
cb();
});
gulp.task('open:docs', function (cb) {
var options = {
uri: 'http://localhost:8880/index.html'
};
gulp.src(__filename)
.pipe(open(options));
cb();
});
/**************************
* Build tests
**************************/
// Karma test
gulp.task('test:unit', function() {
new karmaServer({
configFile: __dirname + "/test/config/karma.conf.js",
keepalive: true
})
.start();
});
gulp.task('test:e2e', function() {
new karmaServer({
configFile: __dirname + "/test/config/e2e.js",
keepalive: true
})
.start();
});
'use strict';
/*
* gulpfile.js
* ===========
* Rather than manage one giant configuration file responsible
* for creating multiple tasks, each task has been broken out into
* its own file in gulp/tasks. Any file in that folder gets automatically
* required by the loop in ./gulp/index.js (required below).
*
* To add a new task, simply add a new task file to gulp/tasks.
*/
require('./gulp');
+21 -7
View File
@@ -5204,12 +5204,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -5224,17 +5226,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@@ -5351,7 +5356,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@@ -5363,6 +5369,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@@ -5377,6 +5384,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -5384,12 +5392,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@@ -5408,6 +5418,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -5488,7 +5499,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@@ -5500,6 +5512,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -5621,6 +5634,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
+6 -2
View File
@@ -2,7 +2,9 @@
"private": true,
"scripts": {
"test": "karma start test/config/karma.conf.js --singlerun",
"build": "gulp"
"build": "gulp build",
"dev": "gulp dev",
"docs": "gulp docs"
},
"dependencies": {
"ace-builds": "1.4.2",
@@ -62,6 +64,7 @@
"gulp-watch": "5.0.1",
"gulp-wrap": "0.14.0",
"gulp-wrap-js": "0.4.1",
"gulp-notify": "^3.0.0",
"jasmine-core": "3.3.0",
"karma": "3.1.1",
"karma-jasmine": "2.0.1",
@@ -70,6 +73,7 @@
"lodash": "4.17.11",
"marked": "^0.5.2",
"merge-stream": "1.0.1",
"run-sequence": "2.2.1"
"run-sequence": "2.2.1",
"fs": "0.0.2"
}
}
@@ -215,9 +215,15 @@
* @param {any} selectedVariant
*/
function openSplitView(selectedVariant) {
var selectedCulture = selectedVariant.language.culture;
//Find the whole variant model based on the culture that was chosen
var variant = _.find(vm.content.variants, function (v) {
return v.language.culture === selectedCulture;
});
insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length));
//only the content app can be selected since no other apps are shown, and because we copy all of these apps
//to the "editors" we need to update this across all editors
for (var e = 0; e < vm.editors.length; e++) {
@@ -233,13 +239,6 @@
}
}
//Find the whole variant model based on the culture that was chosen
var variant = _.find(vm.content.variants, function (v) {
return v.language.culture === selectedCulture;
});
insertVariantEditor(vm.editors.length, initVariant(variant, vm.editors.length));
//TODO: hacking animation states - these should hopefully be easier to do when we upgrade angular
editor.collapsed = true;
editor.loading = true;
@@ -260,6 +259,8 @@
vm.editors.splice(editorIndex, 1);
//remove variant from open variants
vm.openVariants.splice(editorIndex, 1);
//update the current culture to reflect the last open variant (closing the split view corresponds to selecting the other variant)
$location.search("cculture", vm.openVariants[0]);
splitViewChanged();
}, 400);
}
@@ -270,7 +271,7 @@
* @param {any} editorIndex The index of the editor being changed
*/
function selectVariant(variant, editorIndex) {
// prevent variants already open in a split view to be opened
if(vm.openVariants.indexOf(variant.language.culture) !== -1) {
return;
@@ -44,6 +44,7 @@ the directive will use {@link umbraco.directives.directive:umbLockedField umbLoc
@param {string} alias (<code>binding</code>): The model where the alias is bound.
@param {string} aliasFrom (<code>binding</code>): The model to generate the alias from.
@param {string} validationPosition (<code>binding</code>): The position of the validation. Set to <code>'left'</code> or <code>'right'</code>.
@param {boolean=} enableLock (<code>binding</code>): Set to <code>true</code> to add a lock next to the alias from where it can be unlocked and changed.
**/
@@ -57,6 +58,7 @@ angular.module("umbraco.directives")
alias: '=',
aliasFrom: '=',
enableLock: '=?',
validationPosition: '=?',
serverValidationField: '@'
},
link: function (scope, element, attrs, ctrl) {
@@ -40,6 +40,7 @@ Use this directive to render a value with a lock next to it. When the lock is cl
@param {boolean=} locked (<code>binding</code>): <Code>true</code> by default. Set to <code>false</code> to unlock the text.
@param {string=} placeholderText (<code>binding</code>): If ngModel is empty this text will be shown.
@param {string=} regexValidation (<code>binding</code>): Set a regex expression for validation of the field.
@param {string} validationPosition (<code>binding</code>): The position of the validation. Set to <code>'left'</code> or <code>'right'</code>.
@param {string=} serverValidationField (<code>attribute</code>): Set a server validation field.
**/
@@ -70,7 +71,11 @@ Use this directive to render a value with a lock next to it. When the lock is cl
// if locked state is not defined as an attr set default state
if (scope.placeholderText === undefined || scope.placeholderText === null) {
scope.placeholderText = "Enter value...";
}
}
if (scope.validationPosition === undefined || scope.validationPosition === null) {
scope.validationPosition = "left";
}
}
@@ -93,9 +98,10 @@ Use this directive to render a value with a lock next to it. When the lock is cl
templateUrl: 'views/components/umb-locked-field.html',
scope: {
ngModel: "=",
locked: "=?",
locked: "=?",
placeholderText: "=?",
regexValidation: "=?",
regexValidation: "=?",
validationPosition: "=?",
serverValidationField: "@"
},
link: link
@@ -11,24 +11,24 @@ angular.module('umbraco.mocks').
var menu = [
{ name: "Create", cssclass: "plus", alias: "create", metaData: {} },
{ seperator: true, name: "Delete", cssclass: "remove", alias: "delete", metaData: {} },
{ separator: true, name: "Delete", cssclass: "remove", alias: "delete", metaData: {} },
{ name: "Move", cssclass: "move", alias: "move", metaData: {} },
{ name: "Copy", cssclass: "copy", alias: "copy", metaData: {} },
{ name: "Sort", cssclass: "sort", alias: "sort", metaData: {} },
{ seperator: true, name: "Publish", cssclass: "globe", alias: "publish", metaData: {} },
{ separator: true, name: "Publish", cssclass: "globe", alias: "publish", metaData: {} },
{ name: "Rollback", cssclass: "undo", alias: "rollback", metaData: {} },
{ seperator: true, name: "Permissions", cssclass: "lock", alias: "permissions", metaData: {} },
{ separator: true, name: "Permissions", cssclass: "lock", alias: "permissions", metaData: {} },
{ name: "Audit Trail", cssclass: "time", alias: "audittrail", metaData: {} },
{ name: "Notifications", cssclass: "envelope", alias: "notifications", metaData: {} },
{ seperator: true, name: "Hostnames", cssclass: "home", alias: "hostnames", metaData: {} },
{ separator: true, name: "Hostnames", cssclass: "home", alias: "hostnames", metaData: {} },
{ name: "Public Access", cssclass: "group", alias: "publicaccess", metaData: {} },
{ seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} },
{ separator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} },
{ seperator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyrecyclebin", metaData: {} }
{ separator: true, name: "Empty Recycle Bin", cssclass: "trash", alias: "emptyrecyclebin", metaData: {} }
];
var result = {
@@ -94,7 +94,7 @@ angular.module('umbraco.mocks').
jsAction: "umbracoMenuActions.CreateChildEntity"
}
},
{ seperator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} }
{ separator: true, name: "Reload", cssclass: "refresh", alias: "users", metaData: {} }
];
return [200, menu, null];
@@ -640,7 +640,7 @@ angular.module('umbraco.mocks').
"templateEditor_urlEncodeHelp": "Will format special characters in URLs",
"templateEditor_usedIfAllEmpty": "Will only be used when the field values above are empty",
"templateEditor_usedIfEmpty": "This field will only be used if the primary field is empty",
"templateEditor_withTime": "Yes, with time. Seperator: ",
"templateEditor_withTime": "Yes, with time. Separator: ",
"translation_details": "Translation details",
"translation_DownloadXmlDTD": "Download xml DTD",
"translation_fields": "Fields",
@@ -421,7 +421,7 @@ function contentEditingHelper(fileManager, $q, $location, $routeParams, notifica
*/
reBindChangedProperties: function (origContent, savedContent) {
//TODO: We should probably split out this logic to deal with media/members seperately to content
//TODO: We should probably split out this logic to deal with media/members separately to content
//a method to ignore built-in prop changes
var shouldIgnore = function (propName) {
@@ -125,6 +125,8 @@ body.touch .umb-tree {
position: inherit;
display: inherit;
list-style: none;
h6 {
padding: 10px 0 10px 20px;
font-weight: inherit;
@@ -137,7 +139,15 @@ body.touch .umb-tree {
}
&-item {
padding-left: 20px;
padding: 4px 0;
&:hover {
background-color: @gray-10;
}
&-link {
display: block;
}
}
&-link {
@@ -28,7 +28,7 @@
color: @black;
}
.umb-breadcrumbs__seperator {
.umb-breadcrumbs__separator {
position: relative;
top: 1px;
margin-left: 5px;
@@ -41,4 +41,4 @@ input.umb-breadcrumbs__add-ancestor {
margin-top: -2px;
margin-left: 3px;
width: 100px;
}
}
@@ -38,6 +38,7 @@ input.umb-locked-field__input {
transition: color 0.25s;
padding: 0;
height: auto;
max-width: 300px;
}
input.umb-locked-field__input:focus {
@@ -1,46 +1,67 @@
.umb-validation-label {
position: absolute;
top: 27px;
width: 200px;
padding: 1px 5px;
background: @red;
color: @white;
font-size: 11px;
line-height: 1.5em;
position: absolute;
top: 27px;
min-width: 100px;
max-width: 200px;
padding: 1px 5px;
background: @red;
color: @white;
font-size: 11px;
line-height: 1.5em;
}
.umb-validation-label:after {
bottom: 100%;
left: 10px;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(255, 255, 255, 0);
border-bottom-color: @red;
border-width: 4px;
margin-left: -4px;
bottom: 100%;
left: 10px;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(255, 255, 255, 0);
border-bottom-color: @red;
border-width: 4px;
margin-left: -4px;
}
.umb-validation-label.-left {
left: 0;
right: auto;
&:after {
left: 10px;
right: auto;
}
}
.umb-validation-label.-right {
right: 0;
left: auto;
&:after {
right: 10px;
left: auto;
}
}
.umb-validation-label.-arrow-left {
margin-left: 10px;
margin-left: 10px;
}
.umb-validation-label.-arrow-left:after {
right: 100%;
top: 50%;
left: auto;
bottom: auto;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(255, 255, 255, 0);
border-right-color: @red;
border-width: 4px;
margin-top: -4px;
right: 100%;
top: 50%;
left: auto;
bottom: auto;
border: solid transparent;
content: " ";
height: 0;
width: 0;
position: absolute;
pointer-events: none;
border-color: rgba(255, 255, 255, 0);
border-right-color: @red;
border-width: 4px;
margin-top: -4px;
}
@@ -373,10 +373,6 @@
margin-bottom: 0;
}
.umb-panel-header-alias .umb-validation-label:after {
visibility: hidden;
}
.umb-panel-header-alias .umb-locked-field:after {
display: none;
}
@@ -33,31 +33,51 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
$scope.showTarget = $scope.model.hideTarget !== true;
// this ensures that we only sync the tree once and only when it's ready
var oneTimeTreeSync = {
executed: false,
treeReady: false,
sync: function () {
// don't run this if:
// - it was already run once
// - the tree isn't ready yet
// - the model path hasn't been loaded yet
if (this.executed || !this.treeReady || !($scope.model.target && $scope.model.target.path)) {
return;
}
this.executed = true;
// sync the tree to the model path
$scope.dialogTreeApi.syncTree({
path: $scope.model.target.path,
tree: "content"
});
}
};
if (dialogOptions.currentTarget) {
$scope.model.target = dialogOptions.currentTarget;
// clone the current target so we don't accidentally update the caller's model while manipulating $scope.model.target
$scope.model.target = angular.copy(dialogOptions.currentTarget);
//if we have a node ID, we fetch the current node to build the form data
if ($scope.model.target.id || $scope.model.target.udi) {
//will be either a udi or an int
var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
if (!$scope.model.target.path) {
// is it a content link?
if (!$scope.model.target.isMedia) {
// get the content path
entityResource.getPath(id, "Document").then(function (path) {
$scope.model.target.path = path;
//now sync the tree to this path
$scope.dialogTreeApi.syncTree({
path: $scope.model.target.path,
tree: "content"
});
oneTimeTreeSync.sync();
});
// get the content properties to build the anchor name list
contentResource.getById(id).then(function (resp) {
$scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
$scope.model.target.url = resp.urls[0];
});
}
// if a link exists, get the properties to build the anchor name list
contentResource.getById(id).then(function (resp) {
$scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
$scope.model.target.url = resp.urls[0];
});
} else if ($scope.model.target.url.length) {
// a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
// only do the substring if there's a # or a ?
@@ -72,6 +92,11 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
$scope.anchorValues = dialogOptions.anchors;
}
function treeLoadedHandler(args) {
oneTimeTreeSync.treeReady = true;
oneTimeTreeSync.sync();
}
function nodeSelectHandler(args) {
if (args && args.event) {
args.event.preventDefault();
@@ -127,6 +152,12 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
$scope.model.target.url = mediaHelper.resolveFile(media);
editorService.close();
// make sure the content tree has nothing highlighted
$scope.dialogTreeApi.syncTree({
path: "-1",
tree: "content"
});
},
close: function() {
editorService.close();
@@ -146,7 +177,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, {
nodeSelectHandler({
event: evt,
node: result
});
@@ -159,6 +190,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
};
$scope.onTreeInit = function () {
$scope.dialogTreeApi.callbacks.treeLoaded(treeLoadedHandler);
$scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
$scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
}
@@ -166,7 +198,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
// Mini list view
$scope.selectListViewNode = function (node) {
node.selected = node.selected === true ? false : true;
nodeSelectHandler({}, {
nodeSelectHandler({
node: node
});
};
@@ -55,12 +55,12 @@
<ul class="umb-breadcrumbs">
<li ng-hide="startNodeId != -1" class="umb-breadcrumbs__ancestor">
<a href ng-click="gotoFolder()" prevent-default>Media</a>
<span class="umb-breadcrumbs__seperator">&#47;</span>
<span class="umb-breadcrumbs__separator">&#47;</span>
</li>
<li ng-repeat="item in path" class="umb-breadcrumbs__ancestor">
<a href ng-click="gotoFolder(item)" prevent-default>{{item.name}}</a>
<span class="umb-breadcrumbs__seperator">&#47;</span>
<span class="umb-breadcrumbs__separator">&#47;</span>
</li>
<li class="umb-breadcrumbs__ancestor" ng-show="!lockedFolder">
@@ -54,6 +54,7 @@ angular.module("umbraco").controller("Umbraco.Editors.TreePickerController",
vm.toggleLanguageSelector = toggleLanguageSelector;
vm.selectLanguage = selectLanguage;
vm.onSearchResults = onSearchResults;
vm.selectResult = selectResult;
vm.hideSearch = hideSearch;
vm.closeMiniListView = closeMiniListView;
vm.selectListViewNode = selectListViewNode;
@@ -5,7 +5,7 @@
<div class='umb-modalcolumn-body'>
<ul class="umb-actions">
<li data-element="action-{{action.alias}}" ng-click="executeMenuItem(action)" class="umb-action" ng-class="{sep:action.seperator, '-opens-dialog': action.opensDialog}" ng-repeat="action in menuActions">
<li data-element="action-{{action.alias}}" ng-click="executeMenuItem(action)" class="umb-action" ng-class="{sep:action.separator, '-opens-dialog': action.opensDialog}" ng-repeat="action in menuActions">
<a class="umb-action-link" prevent-default>
<i class="icon icon-{{action.cssclass}}"></i>
<span class="menu-label">{{action.name}}</span>
@@ -7,15 +7,11 @@
<umb-box-content class="block-form">
<ul class="nav nav-stacked" style="margin-bottom: 0;">
<li ng-repeat="url in currentUrls">
<span ng-if="url.isUrl">
<a href="{{url.text}}" target="_blank" ng-if="url.isUrl">
<span ng-if="node.variants.length === 1 && url.culture" style="font-size: 13px; color: #cccccc; width: 50px;display: inline-block">{{url.culture}}</span>
<a href="{{url.text}}" target="_blank">
<i class="icon icon-window-popin"></i>
<span>{{url.text}}</span>
</a>
</span>
<i class="icon icon-out"></i>
<span>{{url.text}}</span>
</a>
<div ng-if="!url.isUrl" style="margin-top: 4px;">
<span ng-if="node.variants.length === 1 && url.culture" style="font-size: 13px; color: #cccccc; width: 50px;display: inline-block">{{url.culture}}</span>
@@ -7,9 +7,9 @@
<!-- use callback to handle click -->
<a ng-if="!$last && allowOnOpen" href="#" ng-click="open(ancestor)" class="umb-breadcrumbs__ancestor-link" title="{{ancestor.name}}" prevent-default>{{ancestor.name}}</a>
<span ng-if="!$last" class="umb-breadcrumbs__seperator">&#47;</span>
<span ng-if="!$last" class="umb-breadcrumbs__separator">&#47;</span>
<span class="umb-breadcrumbs__ancestor-text" ng-if="$last" title="{{ancestor.name}}">{{ancestor.name}}</span>
</li>
</ul>
</ul>
@@ -45,6 +45,7 @@
alias="$parent.alias"
alias-from="$parent.name"
enable-lock="true"
validation-position="'right'"
server-validation-field="Alias">
</umb-generate-alias>
@@ -9,7 +9,7 @@
</umb-button>
<umb-dropdown ng-if="dropdown.isOpen" class="umb-actions" on-close="dropdown.isOpen = false">
<umb-dropdown-item class="umb-action" ng-class="{'sep':action.seperatorm, '-opens-dialog': action.opensDialog}" ng-repeat="action in actions">
<umb-dropdown-item class="umb-action" ng-class="{'sep':action.separatorm, '-opens-dialog': action.opensDialog}" ng-repeat="action in actions">
<a href="" ng-click="executeMenuItem(action)" prevent-default>
<i class="icon icon-{{action.cssclass}}"></i>
<span class="menu-label">{{action.name}}</span>
@@ -5,16 +5,16 @@
<umb-box-content class="block-form">
<umb-empty-state
ng-if="!nodeUrl || !nodeUrl.isUrl"
ng-if="!nodeUrl"
size="small">
<localize key="content_noMediaLink"></localize>
</umb-empty-state>
<ul ng-if="nodeUrl && nodeUrl.isUrl" class="nav nav-stacked" style="margin-bottom: 0;">
<ul ng-if="nodeUrl" class="nav nav-stacked" style="margin-bottom: 0;">
<li>
<a href="{{nodeUrl.text}}" target="_blank">
<i class="icon icon-window-popin"></i>
<span>{{nodeUrl.text}}</span>
<a href="{{nodeUrl}}" target="_blank">
<i class="icon icon-out"></i>
<span>{{nodeUrl}}</span>
</a>
</li>
</ul>
@@ -6,9 +6,9 @@
<ul class="umb-tree">
<li class="root">
<ul class="umb-search-group">
<li ng-repeat="result in results">
<div style="padding-left: 20px" ng-class="{'umb-tree-node-checked' : result.selected}">
<a class="umb-search-group-item-link" ng-class="{first:$first}" ng-click="selectResultCallback($event, result)">
<li class="umb-search-group-item" ng-repeat="result in results">
<div ng-class="{'umb-tree-node-checked' : result.selected}">
<a href class="umb-search-group-item-link" ng-class="{first:$first}" ng-click="selectResultCallback($event, result)">
<div class="umb-search-group-item-name">
<i class="icon umb-tree-icon sprTree {{result.icon}}"></i>
{{result.name}}
@@ -5,8 +5,8 @@
locked="locked"
ng-model="alias"
placeholder-text="placeholderText"
validation-position="validationPosition"
server-validation-field="{{serverValidationField}}">
</umb-locked-field>
</div>
</div>
@@ -29,9 +29,22 @@
</div>
<div ng-messages="lockedFieldForm.lockedField.$error" show-validation-on-submit>
<div class="umb-validation-label" ng-message="required"><localize key="general_required">Required</localize> <localize key="content_alias">alias</localize></div>
<div ng-if="regexValidation.length > 0" class="umb-validation-label" ng-message="valRegex"><localize key="general_invalid">Invalid</localize> <localize key="content_alias">alias</localize></div>
<div ng-if="serverValidationField.length > 0" class="umb-validation-label" ng-message="valServerField">{{lockedFieldForm.lockedField.errorMsg}}</div>
<div class="umb-validation-label"
ng-class="{ '-left': validationPosition === 'left', '-right': validationPosition === 'right' }"
ng-message="required">
<localize key="general_required">Required</localize> <localize key="content_alias">alias</localize>
</div>
<div class="umb-validation-label"
ng-class="{ '-left': validationPosition === 'left', '-right': validationPosition === 'right' }"
ng-if="regexValidation.length > 0"
ng-message="valRegex">
<localize key="general_invalid">Invalid</localize> <localize key="content_alias">alias</localize>
</div>
<div class="umb-validation-label"
ng-class="{ '-left': validationPosition === 'left', '-right': validationPosition === 'right' }"
ng-if="serverValidationField.length > 0"
ng-message="valServerField">{{lockedFieldForm.lockedField.errorMsg}}
</div>
</div>
</ng-form>
@@ -1,26 +1,91 @@
angular.module("umbraco").controller("Umbraco.Editors.Media.RestoreController",
function ($scope, relationResource, mediaResource, navigationService, appState, treeService, localizationService) {
function ($scope, relationResource, mediaResource, entityResource, navigationService, appState, treeService, userService) {
$scope.source = _.clone($scope.currentNode);
$scope.error = null;
$scope.success = false;
$scope.loading = true;
$scope.moving = false;
$scope.success = false;
$scope.dialogTreeApi = {};
$scope.searchInfo = {
showSearch: false,
results: [],
selectedSearchResults: []
}
$scope.treeModel = {
hideHeader: false
}
userService.getCurrentUser().then(function (userData) {
$scope.treeModel.hideHeader = userData.startContentIds.length > 0 && userData.startContentIds.indexOf(-1) == -1;
});
function nodeSelectHandler(args) {
if (args && args.event) {
args.event.preventDefault();
args.event.stopPropagation();
}
if ($scope.target) {
//un-select if there's a current one selected
$scope.target.selected = false;
}
$scope.target = args.node;
$scope.target.selected = true;
}
function nodeExpandedHandler(args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
}
}
$scope.hideSearch = function () {
$scope.searchInfo.showSearch = false;
$scope.searchInfo.results = [];
}
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, { event: evt, node: result });
};
//callback when there are search results
$scope.onSearchResults = function (results) {
$scope.searchInfo.results = results;
$scope.searchInfo.showSearch = true;
};
$scope.onTreeInit = function () {
$scope.dialogTreeApi.callbacks.treeNodeSelect(nodeSelectHandler);
$scope.dialogTreeApi.callbacks.treeNodeExpanded(nodeExpandedHandler);
}
// Mini list view
$scope.selectListViewNode = function (node) {
node.selected = node.selected === true ? false : true;
nodeSelectHandler({}, { node: node });
};
$scope.closeMiniListView = function () {
$scope.miniListView = undefined;
};
function openMiniListView(node) {
$scope.miniListView = node;
}
relationResource.getByChildId($scope.source.id, "relateParentDocumentOnDelete").then(function (data) {
$scope.loading = false;
if (!data.length) {
localizationService.localizeMany(["recycleBin_itemCannotBeRestored", "recycleBin_noRestoreRelation"])
.then(function(values) {
$scope.success = false;
$scope.error = {
errorMsg: values[0],
data: {
Message: values[1]
}
}
});
$scope.moving = true;
return;
}
@@ -30,40 +95,31 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.RestoreController",
$scope.target = { id: -1, name: "Root" };
} else {
$scope.loading = true;
mediaResource.getById($scope.relation.parentId).then(function (data) {
$scope.loading = false;
$scope.target = data;
// make sure the target item isn't in the recycle bin
if ($scope.target.path.indexOf("-21") !== -1) {
localizationService.localizeMany(["recycleBin_itemCannotBeRestored", "recycleBin_restoreUnderRecycled"])
.then(function (values) {
$scope.success = false;
$scope.error = {
errorMsg: values[0],
data: {
Message: values[1].replace('%0%', $scope.target.name)
}
}
});
$scope.success = false;
}
$scope.loading = true;
}, function (err) {
$scope.success = false;
$scope.error = err;
entityResource.getById($scope.relation.parentId, "media").then(function (data) {
$scope.loading = false;
});
$scope.target = data;
// make sure the target item isn't in the recycle bin
if ($scope.target.path.indexOf("-21") !== -1) {
$scope.moving = true;
$scope.target = null;
}
}, function (err) {
$scope.loading = false;
$scope.error = err;
});
}
}, function (err) {
$scope.success = false;
$scope.error = err;
$scope.loading = false;
$scope.error = err;
});
$scope.restore = function () {
$scope.loading = true;
$scope.loading = true;
// this code was copied from `content.move.controller.js`
mediaResource.move({ parentId: $scope.target.id, id: $scope.source.id })
.then(function (path) {
@@ -89,9 +145,8 @@ angular.module("umbraco").controller("Umbraco.Editors.Media.RestoreController",
});
}, function (err) {
$scope.success = false;
$scope.error = err;
$scope.loading = false;
$scope.error = err;
});
};
@@ -1,34 +1,87 @@
<div ng-controller="Umbraco.Editors.Media.RestoreController">
<div class="umb-dialog-body">
<umb-pane>
<div class="umb-dialog-body">
<umb-pane>
<umb-load-indicator ng-show="loading">
</umb-load-indicator>
<umb-load-indicator
ng-show="loading">
</umb-load-indicator>
<div ng-show="error">
<div class="alert alert-error">
<div><strong>{{error.errorMsg}}</strong></div>
<div>{{error.data.Message}}</div>
</div>
</div>
<p class="abstract" ng-hide="loading || error != null || success">
<localize key="actions_restore">Restore</localize> <strong>{{source.name}}</strong> <localize key="general_under">under</localize> <strong>{{target.name}}</strong>?
</p>
<div ng-show="success">
<div class="alert alert-success">
<strong>{{source.name}}</strong>
<span ng-hide="moving"><localize key="recycleBin_wasRestored">was restored under</localize></span>
<span ng-show="moving"><localize key="editdatatype_wasMoved">was moved underneath</localize></span>
<strong>{{target.name}}</strong>
</div>
<button class="btn btn-primary" ng-click="close()">Ok</button>
</div>
<div ng-show="error">
<div class="alert alert-error">
<div><strong>{{error.errorMsg}}</strong></div>
<div>{{error.data.Message}}</div>
</div>
</div>
<div ng-hide="moving || loading || success">
<div ng-show="success">
<div class="alert alert-success">
<strong>{{source.name}}</strong> <localize key="editdatatype_wasMoved">was moved underneath</localize> <strong>{{target.name}}</strong>
</div>
<button class="btn btn-primary" ng-click="close()"><localize key="general_ok">Ok</localize></button>
</div>
<p class="abstract" ng-hide="error || success">
<localize key="actions_restore">Restore</localize> <strong>{{source.name}}</strong> <localize key="general_under">under</localize> <strong>{{target.name}}</strong>?
</p>
</umb-pane>
</div>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="loading || success">
<a class="btn btn-link" ng-click="close()"><localize key="general_cancel">Cancel</localize></a>
<button class="btn btn-primary" ng-click="restore()" ng-show="error == null"><localize key="actions_restore">Restore</localize></button>
</div>
<div ng-hide="!moving || loading || success">
<div>
<div class="alert alert-info">
<div><strong><localize key="recycleBin_itemCannotBeRestored">Cannot automatically restore this item</localize></strong></div>
<div><localize key="recycleBin_itemCannotBeRestoredHelpText">There is no location where this item can be automatically restored. You can move the item manually using the tree below.</localize></div>
</div>
</div>
<div ng-hide="miniListView">
<umb-tree-search-box hide-search-callback="hideSearch"
search-callback="onSearchResults"
show-search="{{searchInfo.showSearch}}"
section="media">
</umb-tree-search-box>
<br />
<umb-tree-search-results ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
<div ng-hide="searchInfo.showSearch">
<umb-tree section="media"
hideheader="{{treeModel.hideHeader}}"
hideoptions="true"
isdialog="true"
api="dialogTreeApi"
on-init="onTreeInit()"
enablelistviewexpand="true"
enablecheckboxes="true">
</umb-tree>
</div>
</div>
<umb-mini-list-view ng-if="miniListView"
node="miniListView"
entity-type="Document"
on-select="selectListViewNode(node)"
on-close="closeMiniListView()">
</umb-mini-list-view>
</div>
</umb-pane>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="loading || moving || success">
<a class="btn btn-link" ng-click="close()"><localize key="general_cancel">Cancel</localize></a>
<button class="btn btn-primary" ng-click="restore()" ng-show="error == null"><localize key="actions_restore">Restore</localize></button>
</div>
<div class="umb-dialog-footer btn-toolbar umb-btn-toolbar" ng-hide="loading || !moving || success">
<a class="btn btn-link" ng-click="close()"><localize key="general_cancel">Cancel</localize></a>
<button class="btn btn-primary" ng-click="restore()" ng-show="error == null" ng-disabled="!target"><localize key="actions_move">Move</localize></button>
</div>
</div>
@@ -437,7 +437,7 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
view: "views/propertyeditors/listview/overlays/listviewpublish.html",
submitButtonLabelKey: "actions_publish",
submit: function (model) {
// create a comma seperated array of selected cultures
// create a comma separated array of selected cultures
let selectedCultures = [];
if (model.languages && model.languages.length > 0) {
model.languages.forEach(language => {
@@ -492,7 +492,7 @@ function listViewController($scope, $routeParams, $injector, $timeout, currentUs
submitButtonLabelKey: "actions_unpublish",
submit: function (model) {
// create a comma seperated array of selected cultures
// create a comma separated array of selected cultures
let selectedCultures = [];
if (model.languages && model.languages.length > 0) {
model.languages.forEach(language => {
@@ -142,7 +142,7 @@ namespace Umbraco.Web.Editors
continue;
}
//Find position in current comma seperate string path (so we get the correct children path)
//Find position in current comma separate string path (so we get the correct children path)
var positionInPath = childTemplate.Path.IndexOf(templateIdInPath) + templateIdInPath.Length;
//Get the substring of the child & any children (descendants it may have too)
@@ -27,7 +27,7 @@ namespace Umbraco.Web.Models.ContentEditing
/// <summary>
/// Path represents the path used by the backoffice tree
/// For files stored on disk, this is a urlencoded, comma seperated
/// For files stored on disk, this is a urlencoded, comma separated
/// path to the file, always starting with -1.
///
/// -1,Partials,Parials%2FFolder,Partials%2FFolder%2FFile.cshtml
@@ -10,18 +10,18 @@ namespace Umbraco.Web.Models.Trees
{
public override string AngularServiceName => "umbracoMenuActions";
public CreateChildEntity(string name, bool seperatorBefore = false)
public CreateChildEntity(string name, bool separatorBefore = false)
: base(ActionNew.ActionAlias, name)
{
Icon = "add"; Name = name;
SeperatorBefore = seperatorBefore;
SeparatorBefore = separatorBefore;
}
public CreateChildEntity(ILocalizedTextService textService, bool seperatorBefore = false)
public CreateChildEntity(ILocalizedTextService textService, bool separatorBefore = false)
: base(ActionNew.ActionAlias, textService)
{
Icon = "add";
SeperatorBefore = seperatorBefore;
SeparatorBefore = separatorBefore;
}
}
}
+3 -3
View File
@@ -49,7 +49,7 @@ namespace Umbraco.Web.Models.Trees
{
Name = name.IsNullOrWhiteSpace() ? action.Alias : name;
Alias = action.Alias;
SeperatorBefore = false;
SeparatorBefore = false;
Icon = action.Icon;
Action = action;
}
@@ -80,8 +80,8 @@ namespace Umbraco.Web.Models.Trees
/// <summary>
/// Ensures a menu separator will exist before this menu item
/// </summary>
[DataMember(Name = "seperator")]
public bool SeperatorBefore { get; set; }
[DataMember(Name = "separator")]
public bool SeparatorBefore { get; set; }
[DataMember(Name = "cssclass")]
public string Icon { get; set; }
+2 -2
View File
@@ -83,7 +83,7 @@ namespace Umbraco.Web.Models.Trees
if (item == null) return null;
var menuItem = new MenuItem(item, name)
{
SeperatorBefore = hasSeparator,
SeparatorBefore = hasSeparator,
OpensDialog = opensDialog
};
@@ -98,7 +98,7 @@ namespace Umbraco.Web.Models.Trees
var menuItem = new MenuItem(item, textService.Localize($"actions/{item.Alias}"))
{
SeperatorBefore = hasSeparator,
SeparatorBefore = hasSeparator,
OpensDialog = opensDialog
};
+4 -4
View File
@@ -10,18 +10,18 @@ namespace Umbraco.Web.Models.Trees
{
public override string AngularServiceName => "umbracoMenuActions";
public RefreshNode(string name, bool seperatorBefore = false)
public RefreshNode(string name, bool separatorBefore = false)
: base("refreshNode", name)
{
Icon = "refresh";
SeperatorBefore = seperatorBefore;
SeparatorBefore = separatorBefore;
}
public RefreshNode(ILocalizedTextService textService, bool seperatorBefore = false)
public RefreshNode(ILocalizedTextService textService, bool separatorBefore = false)
: base("refreshNode", textService)
{
Icon = "refresh";
SeperatorBefore = seperatorBefore;
SeparatorBefore = separatorBefore;
}
}
}
@@ -1,4 +1,5 @@
using Umbraco.Core.PropertyEditors;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
@@ -8,6 +9,6 @@ namespace Umbraco.Web.PropertyEditors
public bool ShowOpenButton { get; set; }
[ConfigurationField("startNodeId", "Start node", "treepicker")] // + config in configuration editor ctor
public int StartNodeId { get; set; } = -1; // default value is -1
public Udi StartNodeId { get; set; }
}
}
}
@@ -1,4 +1,5 @@
using Umbraco.Core.PropertyEditors;
using Umbraco.Core;
using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
@@ -17,6 +18,6 @@ namespace Umbraco.Web.PropertyEditors
public bool DisableFolderSelect { get; set; }
[ConfigurationField("startNodeId", "Start node", "mediapicker")]
public int StartNodeId { get; set; }
public Udi StartNodeId { get; set; }
}
}
}
@@ -4,7 +4,7 @@ using Umbraco.Core.PropertyEditors;
namespace Umbraco.Web.PropertyEditors
{
[DataEditor(Constants.PropertyEditors.Aliases.MemberGroupPicker, "Member Group Picker", "membergrouppicker", Group="People", Icon="icon-users")]
[DataEditor(Constants.PropertyEditors.Aliases.MemberGroupPicker, "Member Group Picker", "membergrouppicker", ValueType = ValueTypes.Text, Group = "People", Icon = "icon-users")]
public class MemberGroupPickerPropertyEditor : DataEditor
{
public MemberGroupPickerPropertyEditor(ILogger logger)
@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using Umbraco.Core;
namespace Umbraco.Web.PropertyEditors
{
@@ -15,6 +16,6 @@ namespace Umbraco.Web.PropertyEditors
public string StartNodeQuery {get;set;}
[JsonProperty("id")]
public int? StartNodeId {get;set;}
public Udi StartNodeId {get;set;}
}
}
@@ -154,9 +154,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
public override string Name => _name;
public override PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException();
public override PublishedCultureInfo GetCulture(string culture = null) => null;
public override IReadOnlyDictionary<string, PublishedCultureInfo> Cultures => throw new NotSupportedException();
private static readonly Lazy<Dictionary<string, PublishedCultureInfo>> NoCultures = new Lazy<Dictionary<string, PublishedCultureInfo>>(() => new Dictionary<string, PublishedCultureInfo>());
public override IReadOnlyDictionary<string, PublishedCultureInfo> Cultures => NoCultures.Value;
public override string UrlSegment => _urlName;
@@ -136,9 +136,10 @@ namespace Umbraco.Web.PublishedCache.XmlPublishedCache
}
}
public override PublishedCultureInfo GetCulture(string culture = null) => throw new NotSupportedException();
public override PublishedCultureInfo GetCulture(string culture = null) => null;
public override IReadOnlyDictionary<string, PublishedCultureInfo> Cultures => throw new NotSupportedException();
private static readonly Lazy<Dictionary<string, PublishedCultureInfo>> NoCultures = new Lazy<Dictionary<string, PublishedCultureInfo>>(() => new Dictionary<string, PublishedCultureInfo>());
public override IReadOnlyDictionary<string, PublishedCultureInfo> Cultures => NoCultures.Value;
public override string WriterName
{
+105 -75
View File
@@ -4,9 +4,7 @@ using System.Data;
using System.Linq;
using System.Web;
using Examine;
using Examine.Search;
using Umbraco.Core;
using Umbraco.Core.Configuration;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Core.Models;
using Umbraco.Core.Services;
@@ -268,6 +266,21 @@ namespace Umbraco.Web
public static bool HasCulture(this IPublishedContent content, string culture)
=> content.Cultures.ContainsKey(culture);
/// <summary>
/// Filters a sequence of <see cref="IPublishedContent"/> to return invariant items, and items that are published for the specified culture.
/// </summary>
/// <param name="contents">The content items.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null).</param>
internal static IEnumerable<IPublishedContent> WhereIsInvariantOrHasCulture(this IEnumerable<IPublishedContent> contents, string culture = null)
{
if (contents == null) throw new ArgumentNullException(nameof(contents));
culture = culture ?? Current.VariationContextAccessor.VariationContext?.Culture ?? "";
// either does not vary by culture, or has the specified culture
return contents.Where(x => !x.ContentType.VariesByCulture() || x.HasCulture(culture));
}
#endregion
#region Search
@@ -767,27 +780,29 @@ namespace Umbraco.Web
/// </summary>
/// <param name="parentNodes"></param>
/// <param name="docTypeAlias"></param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns></returns>
/// <remarks>
/// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot
/// </remarks>
public static IEnumerable<IPublishedContent> DescendantsOrSelf(this IEnumerable<IPublishedContent> parentNodes, string docTypeAlias)
public static IEnumerable<IPublishedContent> DescendantsOrSelf(this IEnumerable<IPublishedContent> parentNodes, string docTypeAlias, string culture = null)
{
return parentNodes.SelectMany(x => x.DescendantsOrSelf(docTypeAlias));
return parentNodes.SelectMany(x => x.DescendantsOrSelf(docTypeAlias, culture));
}
/// <summary>
/// Returns all DescendantsOrSelf of all content referenced
/// </summary>
/// <param name="parentNodes"></param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns></returns>
/// <remarks>
/// This can be useful in order to return all nodes in an entire site by a type when combined with TypedContentAtRoot
/// </remarks>
public static IEnumerable<T> DescendantsOrSelf<T>(this IEnumerable<IPublishedContent> parentNodes)
public static IEnumerable<T> DescendantsOrSelf<T>(this IEnumerable<IPublishedContent> parentNodes, string culture = null)
where T : class, IPublishedContent
{
return parentNodes.SelectMany(x => x.DescendantsOrSelf<T>());
return parentNodes.SelectMany(x => x.DescendantsOrSelf<T>(culture));
}
@@ -810,133 +825,133 @@ namespace Umbraco.Web
// - the relative order of siblings is the order in which they occur in the children property of their parent node.
// - children and descendants occur before following siblings.
public static IEnumerable<IPublishedContent> Descendants(this IPublishedContent content)
public static IEnumerable<IPublishedContent> Descendants(this IPublishedContent content, string culture = null)
{
return content.DescendantsOrSelf(false, null);
return content.DescendantsOrSelf(false, null, culture);
}
public static IEnumerable<IPublishedContent> Descendants(this IPublishedContent content, int level)
public static IEnumerable<IPublishedContent> Descendants(this IPublishedContent content, int level, string culture = null)
{
return content.DescendantsOrSelf(false, p => p.Level >= level);
return content.DescendantsOrSelf(false, p => p.Level >= level, culture);
}
public static IEnumerable<IPublishedContent> Descendants(this IPublishedContent content, string contentTypeAlias)
public static IEnumerable<IPublishedContent> Descendants(this IPublishedContent content, string contentTypeAlias, string culture = null)
{
return content.DescendantsOrSelf(false, p => p.ContentType.Alias == contentTypeAlias);
return content.DescendantsOrSelf(false, p => p.ContentType.Alias == contentTypeAlias, culture);
}
public static IEnumerable<T> Descendants<T>(this IPublishedContent content)
public static IEnumerable<T> Descendants<T>(this IPublishedContent content, string culture = null)
where T : class, IPublishedContent
{
return content.Descendants().OfType<T>();
return content.Descendants(culture).OfType<T>();
}
public static IEnumerable<T> Descendants<T>(this IPublishedContent content, int level)
public static IEnumerable<T> Descendants<T>(this IPublishedContent content, int level, string culture = null)
where T : class, IPublishedContent
{
return content.Descendants(level).OfType<T>();
return content.Descendants(level, culture).OfType<T>();
}
public static IEnumerable<IPublishedContent> DescendantsOrSelf(this IPublishedContent content)
public static IEnumerable<IPublishedContent> DescendantsOrSelf(this IPublishedContent content, string culture = null)
{
return content.DescendantsOrSelf(true, null);
return content.DescendantsOrSelf(true, null, culture);
}
public static IEnumerable<IPublishedContent> DescendantsOrSelf(this IPublishedContent content, int level)
public static IEnumerable<IPublishedContent> DescendantsOrSelf(this IPublishedContent content, int level, string culture = null)
{
return content.DescendantsOrSelf(true, p => p.Level >= level);
return content.DescendantsOrSelf(true, p => p.Level >= level, culture);
}
public static IEnumerable<IPublishedContent> DescendantsOrSelf(this IPublishedContent content, string contentTypeAlias)
public static IEnumerable<IPublishedContent> DescendantsOrSelf(this IPublishedContent content, string contentTypeAlias, string culture = null)
{
return content.DescendantsOrSelf(true, p => p.ContentType.Alias == contentTypeAlias);
return content.DescendantsOrSelf(true, p => p.ContentType.Alias == contentTypeAlias, culture);
}
public static IEnumerable<T> DescendantsOrSelf<T>(this IPublishedContent content)
public static IEnumerable<T> DescendantsOrSelf<T>(this IPublishedContent content, string culture = null)
where T : class, IPublishedContent
{
return content.DescendantsOrSelf().OfType<T>();
return content.DescendantsOrSelf(culture).OfType<T>();
}
public static IEnumerable<T> DescendantsOrSelf<T>(this IPublishedContent content, int level)
public static IEnumerable<T> DescendantsOrSelf<T>(this IPublishedContent content, int level, string culture = null)
where T : class, IPublishedContent
{
return content.DescendantsOrSelf(level).OfType<T>();
return content.DescendantsOrSelf(level, culture).OfType<T>();
}
public static IPublishedContent Descendant(this IPublishedContent content)
public static IPublishedContent Descendant(this IPublishedContent content, string culture = null)
{
return content.Children.FirstOrDefault();
return content.Children(culture).FirstOrDefault();
}
public static IPublishedContent Descendant(this IPublishedContent content, int level)
public static IPublishedContent Descendant(this IPublishedContent content, int level, string culture = null)
{
return content.EnumerateDescendants(false).FirstOrDefault(x => x.Level == level);
return content.EnumerateDescendants(false, culture).FirstOrDefault(x => x.Level == level);
}
public static IPublishedContent Descendant(this IPublishedContent content, string contentTypeAlias)
public static IPublishedContent Descendant(this IPublishedContent content, string contentTypeAlias, string culture = null)
{
return content.EnumerateDescendants(false).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias);
return content.EnumerateDescendants(false, culture).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias);
}
public static T Descendant<T>(this IPublishedContent content)
public static T Descendant<T>(this IPublishedContent content, string culture = null)
where T : class, IPublishedContent
{
return content.EnumerateDescendants(false).FirstOrDefault(x => x is T) as T;
return content.EnumerateDescendants(false, culture).FirstOrDefault(x => x is T) as T;
}
public static T Descendant<T>(this IPublishedContent content, int level)
public static T Descendant<T>(this IPublishedContent content, int level, string culture = null)
where T : class, IPublishedContent
{
return content.Descendant(level) as T;
return content.Descendant(level, culture) as T;
}
public static IPublishedContent DescendantOrSelf(this IPublishedContent content)
public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string culture = null)
{
return content;
}
public static IPublishedContent DescendantOrSelf(this IPublishedContent content, int level)
public static IPublishedContent DescendantOrSelf(this IPublishedContent content, int level, string culture = null)
{
return content.EnumerateDescendants(true).FirstOrDefault(x => x.Level == level);
return content.EnumerateDescendants(true, culture).FirstOrDefault(x => x.Level == level);
}
public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string contentTypeAlias)
public static IPublishedContent DescendantOrSelf(this IPublishedContent content, string contentTypeAlias, string culture = null)
{
return content.EnumerateDescendants(true).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias);
return content.EnumerateDescendants(true, culture).FirstOrDefault(x => x.ContentType.Alias == contentTypeAlias);
}
public static T DescendantOrSelf<T>(this IPublishedContent content)
public static T DescendantOrSelf<T>(this IPublishedContent content, string culture = null)
where T : class, IPublishedContent
{
return content.EnumerateDescendants(true).FirstOrDefault(x => x is T) as T;
return content.EnumerateDescendants(true, culture).FirstOrDefault(x => x is T) as T;
}
public static T DescendantOrSelf<T>(this IPublishedContent content, int level)
public static T DescendantOrSelf<T>(this IPublishedContent content, int level, string culture = null)
where T : class, IPublishedContent
{
return content.DescendantOrSelf(level) as T;
return content.DescendantOrSelf(level, culture) as T;
}
internal static IEnumerable<IPublishedContent> DescendantsOrSelf(this IPublishedContent content, bool orSelf, Func<IPublishedContent, bool> func)
internal static IEnumerable<IPublishedContent> DescendantsOrSelf(this IPublishedContent content, bool orSelf, Func<IPublishedContent, bool> func, string culture = null)
{
return content.EnumerateDescendants(orSelf).Where(x => func == null || func(x));
return content.EnumerateDescendants(orSelf, culture).Where(x => func == null || func(x));
}
internal static IEnumerable<IPublishedContent> EnumerateDescendants(this IPublishedContent content, bool orSelf)
internal static IEnumerable<IPublishedContent> EnumerateDescendants(this IPublishedContent content, bool orSelf, string culture = null)
{
if (content == null) throw new ArgumentNullException(nameof(content));
if (orSelf) yield return content;
foreach (var desc in content.Children.SelectMany(x => x.EnumerateDescendants()))
foreach (var desc in content.Children(culture).SelectMany(x => x.EnumerateDescendants()))
yield return desc;
}
internal static IEnumerable<IPublishedContent> EnumerateDescendants(this IPublishedContent content)
internal static IEnumerable<IPublishedContent> EnumerateDescendants(this IPublishedContent content, string culture = null)
{
yield return content;
foreach (var desc in content.Children.SelectMany(x => x.EnumerateDescendants()))
foreach (var desc in content.Children(culture).SelectMany(x => x.EnumerateDescendants()))
yield return desc;
}
@@ -1026,15 +1041,25 @@ namespace Umbraco.Web
/// Gets the children of the content.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns>The children of the content.</returns>
/// <remarks>
/// <para>Children are sorted by their sortOrder.</para>
/// <para>This method exists for consistency, it is the same as calling content.Children as a property.</para>
/// </remarks>
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content)
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, string culture = null)
{
if (content == null) throw new ArgumentNullException(nameof(content));
return content.Children;
//
return content.Children.Where(x =>
{
if (!x.ContentType.VariesByCulture()) return true; // invariant = always ok
return x.HasCulture(culture);
return false;
});
return content.Children.WhereIsInvariantOrHasCulture(culture);
}
/// <summary>
@@ -1042,24 +1067,26 @@ namespace Umbraco.Web
/// </summary>
/// <param name="content">The content.</param>
/// <param name="predicate">The predicate.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns>The children of the content, filtered by the predicate.</returns>
/// <remarks>
/// <para>Children are sorted by their sortOrder.</para>
/// </remarks>
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, Func<IPublishedContent, bool> predicate)
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, Func<IPublishedContent, bool> predicate, string culture = null)
{
return content.Children().Where(predicate);
return content.Children(culture).Where(predicate);
}
/// <summary>
/// Gets the children of the content, of any of the specified types.
/// </summary>
/// <param name="content">The content.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <param name="alias">One or more content type alias.</param>
/// <returns>The children of the content, of any of the specified types.</returns>
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, params string[] alias)
public static IEnumerable<IPublishedContent> Children(this IPublishedContent content, string culture = null, params string[] alias)
{
return content.Children(x => alias.InvariantContains(x.ContentType.Alias));
return content.Children(x => alias.InvariantContains(x.ContentType.Alias), culture);
}
/// <summary>
@@ -1067,19 +1094,20 @@ namespace Umbraco.Web
/// </summary>
/// <typeparam name="T">The content type.</typeparam>
/// <param name="content">The content.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns>The children of content, of the given content type.</returns>
/// <remarks>
/// <para>Children are sorted by their sortOrder.</para>
/// </remarks>
public static IEnumerable<T> Children<T>(this IPublishedContent content)
public static IEnumerable<T> Children<T>(this IPublishedContent content, string culture = null)
where T : class, IPublishedContent
{
return content.Children().OfType<T>();
return content.Children(culture).OfType<T>();
}
public static IPublishedContent FirstChild(this IPublishedContent content)
public static IPublishedContent FirstChild(this IPublishedContent content, string culture = null)
{
return content.Children().FirstOrDefault();
return content.Children(culture).FirstOrDefault();
}
/// <summary>
@@ -1088,26 +1116,26 @@ namespace Umbraco.Web
/// <param name="content">The content.</param>
/// <param name="alias">The content type alias.</param>
/// <returns>The first child of content, of the given content type.</returns>
public static IPublishedContent FirstChild(this IPublishedContent content, string alias)
public static IPublishedContent FirstChild(this IPublishedContent content, string alias, string culture = null)
{
return content.Children(alias).FirstOrDefault();
return content.Children(culture,alias).FirstOrDefault();
}
public static IPublishedContent FirstChild(this IPublishedContent content, Func<IPublishedContent, bool> predicate)
public static IPublishedContent FirstChild(this IPublishedContent content, Func<IPublishedContent, bool> predicate, string culture = null)
{
return content.Children(predicate).FirstOrDefault();
return content.Children(predicate, culture).FirstOrDefault();
}
public static T FirstChild<T>(this IPublishedContent content)
public static T FirstChild<T>(this IPublishedContent content, string culture = null)
where T : class, IPublishedContent
{
return content.Children<T>().FirstOrDefault();
return content.Children<T>(culture).FirstOrDefault();
}
public static T FirstChild<T>(this IPublishedContent content, Func<T, bool> predicate)
public static T FirstChild<T>(this IPublishedContent content, Func<T, bool> predicate, string culture = null)
where T : class, IPublishedContent
{
return content.Children<T>().FirstOrDefault(predicate);
return content.Children<T>(culture).FirstOrDefault(predicate);
}
/// <summary>
@@ -1116,10 +1144,11 @@ namespace Umbraco.Web
/// <param name="content">The content.</param>
/// <param name="services">A service context.</param>
/// <param name="contentTypeAliasFilter">An optional content type alias.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns>The children of the content.</returns>
public static DataTable ChildrenAsTable(this IPublishedContent content, ServiceContext services, string contentTypeAliasFilter = "")
public static DataTable ChildrenAsTable(this IPublishedContent content, ServiceContext services, string contentTypeAliasFilter = "", string culture = null)
{
return GenerateDataTable(content, services, contentTypeAliasFilter);
return GenerateDataTable(content, services, contentTypeAliasFilter, culture);
}
/// <summary>
@@ -1128,14 +1157,15 @@ namespace Umbraco.Web
/// <param name="content">The content.</param>
/// <param name="services">A service context.</param>
/// <param name="contentTypeAliasFilter">An optional content type alias.</param>
/// <param name="culture">The specific culture to filter for. If null is used the current culture is used. (Default is null)</param>
/// <returns>The children of the content.</returns>
private static DataTable GenerateDataTable(IPublishedContent content, ServiceContext services, string contentTypeAliasFilter = "")
private static DataTable GenerateDataTable(IPublishedContent content, ServiceContext services, string contentTypeAliasFilter = "", string culture = null)
{
var firstNode = contentTypeAliasFilter.IsNullOrWhiteSpace()
? content.Children.Any()
? content.Children.ElementAt(0)
? content.Children(culture).Any()
? content.Children(culture).ElementAt(0)
: null
: content.Children.FirstOrDefault(x => x.ContentType.Alias == contentTypeAliasFilter);
: content.Children(culture).FirstOrDefault(x => x.ContentType.Alias == contentTypeAliasFilter);
if (firstNode == null)
return new DataTable(); //no children found
+24 -1
View File
@@ -646,7 +646,30 @@ namespace Umbraco.Web.Routing
}
else
{
_logger.Debug<PublishedRouter>("EnsurePublishedContentAccess: Current member has access");
// grab the current member
var member = membershipHelper.GetCurrentMember();
// if the member has the "approved" and/or "locked out" properties, make sure they're correctly set before allowing access
var memberIsActive = true;
if (member != null)
{
if (member.HasProperty(Constants.Conventions.Member.IsApproved) == false)
memberIsActive = member.Value<bool>(Constants.Conventions.Member.IsApproved);
if (member.HasProperty(Constants.Conventions.Member.IsLockedOut) == false)
memberIsActive = member.Value<bool>(Constants.Conventions.Member.IsLockedOut) == false;
}
if (memberIsActive == false)
{
_logger.Debug<PublishedRouter>("Current member is either unapproved or locked out, redirect to error page");
var errorPageId = publicAccessAttempt.Result.NoAccessNodeId;
if (errorPageId != request.PublishedContent.Id)
request.PublishedContent = request.UmbracoContext.PublishedSnapshot.Content.GetById(errorPageId);
}
else
{
_logger.Debug<PublishedRouter>("Current member has access");
}
}
}
else
+10 -8
View File
@@ -137,7 +137,7 @@ namespace Umbraco.Web.Trees
if (menu.Items.Any())
{
menu.Items.Last().SeperatorBefore = true;
menu.Items.Last().SeparatorBefore = true;
}
// add default actions for *all* users
@@ -237,14 +237,16 @@ namespace Umbraco.Web.Trees
AddActionNode<ActionRights>(item, menu, opensDialog: true);
//fixme - conver this editor to angular
AddActionNode<ActionProtect>(item, menu, true, convert: true, opensDialog: true);
menu.Items.Add(new MenuItem("notify", Services.TextService)
if (EmailSender.CanSendRequiredEmail)
{
Icon = "megaphone",
SeperatorBefore = true,
OpensDialog = true
});
menu.Items.Add(new MenuItem("notify", Services.TextService)
{
Icon = "megaphone",
SeparatorBefore = true,
OpensDialog = true
});
}
menu.Items.Add(new RefreshNode(Services.TextService, true));
return menu;
@@ -84,7 +84,7 @@ namespace Umbraco.Web.Trees
menu.Items.Add(new MenuItem("importDocumentType", Services.TextService)
{
Icon = "page-up",
SeperatorBefore = true,
SeparatorBefore = true,
OpensDialog = true
});
menu.Items.Add(new RefreshNode(Services.TextService, true));
@@ -130,7 +130,7 @@ namespace Umbraco.Web.Trees
menu.Items.Add(new MenuItem("export", Services.TextService)
{
Icon = "download-alt",
SeperatorBefore = true,
SeparatorBefore = true,
OpensDialog = true
});
menu.Items.Add<ActionDelete>(Services.TextService, true, opensDialog: true);
+1 -1
View File
@@ -832,7 +832,7 @@ namespace Umbraco.Web
}
/// <summary>
/// Joins any number of int/string/objects into one string and seperates them with the string seperator parameter.
/// Joins any number of int/string/objects into one string and separates them with the string separator parameter.
/// </summary>
public string Join(string separator, params object[] args)
{