Merge branch 'v8/dev' into pr/7004

This commit is contained in:
emma burstow
2019-12-03 11:21:21 +00:00
231 changed files with 8647 additions and 6479 deletions

View File

@@ -259,19 +259,18 @@ function dependencies() {
//css, fonts and image files
var assetsTask = gulp.src(config.sources.globs.assets, { allowEmpty: true });
if (global.isProd === true) {
assetsTask = assetsTask.pipe(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.jpegtran({progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({
plugins: [
{removeViewBox: true},
{cleanupIDs: false}
]
})
]));
}
assetsTask = assetsTask.pipe(imagemin([
imagemin.gifsicle({interlaced: true}),
imagemin.jpegtran({progressive: true}),
imagemin.optipng({optimizationLevel: 5}),
imagemin.svgo({
plugins: [
{removeViewBox: true},
{cleanupIDs: false}
]
})
]));
assetsTask = assetsTask.pipe(gulp.dest(config.root + config.targets.assets));
stream.add(assetsTask);

View File

@@ -1,22 +0,0 @@
'use strict';
var gulp = require('gulp');
var through2 = require('through2');
function createEmptyStream() {
var pass = through2.obj();
process.nextTick(pass.end.bind(pass));
return pass;
}
/**************************
* Copies all angular JS files into their separate umbraco.*.js file
**************************/
function removeProductionMode() {
global.isProd = false;
return createEmptyStream();
};
module.exports = { removeProductionMode: removeProductionMode };

View File

@@ -20,7 +20,7 @@ function watchTask(cb) {
//Setup a watcher for all groups of JS files
_.forEach(config.sources.js, function (group) {
if(group.watch !== false) {
watch(group.files, { ignoreInitial: true, interval: watchInterval }, function JS_Group_Compile() { return processJs(group.files, group.out); });
watch(group.files, { ignoreInitial: true, interval: watchInterval }, function JS_Group_Compile() { return processJs(group.files, group.out);});
}
});

View File

@@ -23,11 +23,10 @@ module.exports = function (files, out) {
// sort files in stream by path or any custom sort comparator
task = task.pipe(babel())
.pipe(sort());
if (global.isProd === true) {
//in production, embed the templates
task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } }))
}
//in production, embed the templates
task = task.pipe(embedTemplates({ basePath: "./src/", minimize: { loose: true } }))
task = task.pipe(concat(out))
.pipe(wrap('(function(){\n%= body %\n})();'))
.pipe(gulp.dest(config.root + config.targets.js));

View File

@@ -10,8 +10,6 @@
* and then add the exports command to add the new item into the task menu.
*/
global.isProd = true;
const { src, dest, series, parallel, lastRun } = require('gulp');
const { dependencies } = require('./gulp/tasks/dependencies');
@@ -20,7 +18,6 @@ const { less } = require('./gulp/tasks/less');
const { testE2e, testUnit } = require('./gulp/tasks/test');
const { views } = require('./gulp/tasks/views');
const { watchTask } = require('./gulp/tasks/watchTask');
const { removeProductionMode } = require('./gulp/tasks/removeProductionMode');
// Load local overwrites, can be used to overwrite paths in your local setup.
var fs = require('fs');
@@ -41,7 +38,6 @@ try {
// ***********************************************************
exports.build = series(parallel(dependencies, js, less, views), testUnit);
exports.dev = series(parallel(dependencies, js, less, views), watchTask);
exports.fastdev = series(removeProductionMode, parallel(dependencies, js, less, views), watchTask);
exports.watch = series(watchTask);
//
exports.runTests = series(js, testUnit);

View File

@@ -195,6 +195,7 @@ Use this directive to construct a header inside the main editor window.
@param {string=} icon Show and edit the content icon. Opens an overlay to change the icon.
@param {boolean=} hideIcon Set to <code>true</code> to hide icon.
@param {string=} alias show and edit the content alias.
@param {boolean=} aliasLocked Set to <code>true</code> to lock the alias.
@param {boolean=} hideAlias Set to <code>true</code> to hide alias.
@param {string=} description Add a description to the content.
@param {boolean=} hideDescription Set to <code>true</code> to hide description.
@@ -239,12 +240,13 @@ Use this directive to construct a header inside the main editor window.
if (scope.editorfor) {
localizeVars.push(scope.editorfor);
}
localizationService.localizeMany(localizeVars).then(function (data) {
localizationService.localizeMany(localizeVars).then(function(data) {
setAccessibilityForEditor(data);
scope.loading = false;
});
} else {
scope.loading = false;
}
scope.goBack = function () {
if (scope.onBack) {
scope.onBack();
@@ -346,6 +348,7 @@ Use this directive to construct a header inside the main editor window.
icon: "=",
hideIcon: "@",
alias: "=",
aliasLocked: "<",
hideAlias: "=",
description: "=",
hideDescription: "@",

View File

@@ -4,7 +4,7 @@
* @restrict E
**/
angular.module("umbraco.directives")
.directive('umbProperty', function (umbPropEditorHelper, userService) {
.directive('umbProperty', function (userService) {
return {
scope: {
property: "=",
@@ -16,22 +16,30 @@ angular.module("umbraco.directives")
replace: true,
templateUrl: 'views/components/property/umb-property.html',
link: function (scope) {
scope.propertyActions = [];
userService.getCurrentUser().then(function (u) {
var isAdmin = u.userGroups.indexOf('admin') !== -1;
scope.propertyAlias = (Umbraco.Sys.ServerVariables.isDebuggingEnabled === true || isAdmin) ? scope.property.alias : null;
});
},
//Define a controller for this directive to expose APIs to other directives
controller: function ($scope, $timeout) {
controller: function ($scope) {
var self = this;
//set the API properties/methods
self.property = $scope.property;
self.setPropertyError = function (errorMsg) {
$scope.property.propertyErrorMessage = errorMsg;
};
self.setPropertyActions = function(actions) {
$scope.propertyActions = actions;
};
}
};
});

View File

@@ -65,6 +65,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
vm.reloadNode = reloadNode;
vm.syncTree = syncTree;
vm.loadChildren = loadChildren;
vm.hasTree = hasTree;
//wire up the exposed api object for hosting controllers
if ($scope.api) {
@@ -72,6 +73,7 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
$scope.api.load = vm.load;
$scope.api.reloadNode = vm.reloadNode;
$scope.api.syncTree = vm.syncTree;
$scope.api.hasTree = vm.hasTree;
}
//flag to track the last loaded section when the tree 'un-loads'. We use this to determine if we should
@@ -203,6 +205,25 @@ function umbTreeDirective($q, $rootScope, treeService, notificationsService, use
});
}
//given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node
function hasTree(treeAlias) {
if (!$scope.tree) {
throw "Err in umbtree.directive.loadActiveTree, $scope.tree is null";
}
if (!treeAlias) {
return false;
}
var treeRoots = getTreeRootNodes();
var foundTree = _.find(treeRoots, function (node) {
return node.metaData.treeAlias.toUpperCase() === treeAlias.toUpperCase();
});
return foundTree !== undefined;
}
//given a tree alias, this will search the current section tree for the specified tree alias and set the current active tree to it's root node
function loadActiveTree(treeAlias) {

View File

@@ -56,6 +56,7 @@ function confirmDirective() {
onCancel: '=',
caption: '@',
confirmButtonStyle: '@',
confirmDisabled: '<?',
confirmLabelKey: '@'
},
link: function (scope, element, attr, ctrl) {

View File

@@ -549,7 +549,9 @@
property.dataTypeIcon = propertyModel.dataTypeIcon;
property.dataTypeName = propertyModel.dataTypeName;
property.validation.mandatory = propertyModel.validation.mandatory;
property.validation.mandatoryMessage = propertyModel.validation.mandatoryMessage;
property.validation.pattern = propertyModel.validation.pattern;
property.validation.patternMessage = propertyModel.validation.patternMessage;
property.showOnMemberProfile = propertyModel.showOnMemberProfile;
property.memberCanEdit = propertyModel.memberCanEdit;
property.isSensitiveValue = propertyModel.isSensitiveValue;
@@ -632,7 +634,9 @@
propertyState: "init",
validation: {
mandatory: false,
pattern: null
mandatoryMessage: null,
pattern: null,
patternMessage: null
}
};

View File

@@ -389,7 +389,7 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) {
(umbRequestHelper.getApiUrl(
"dataTypeApiBaseUrl",
"PostRenameContainer",
{ id: id, name: name })),
{ id: id, name: encodeURIComponent(name) })),
"Failed to rename the folder with id " + id);
}
};

View File

@@ -1,203 +1,264 @@
/**
* @ngdoc service
* @name umbraco.services.clipboardService
*
* @requires notificationsService
* @requires eventsService
*
* @description
* Service to handle clipboard in general across the application. Responsible for handling the data both storing and retrive.
* The service has a set way for defining a data-set by a entryType and alias, which later will be used to retrive the posible entries for a paste scenario.
*
*/
function clipboardService(notificationsService, eventsService, localStorageService) {
var STORAGE_KEY = "umbClipboardService";
var retriveStorage = function() {
if (localStorageService.isSupported === false) {
return null;
}
var dataJSON;
var dataString = localStorageService.get(STORAGE_KEY);
if (dataString != null) {
dataJSON = JSON.parse(dataString);
}
if(dataJSON == null) {
dataJSON = new Object();
}
if(dataJSON.entries === undefined) {
dataJSON.entries = [];
}
return dataJSON;
}
var saveStorage = function(storage) {
var storageString = JSON.stringify(storage);
try {
var storageJSON = JSON.parse(storageString);
localStorageService.set(STORAGE_KEY, storageString);
eventsService.emit("clipboardService.storageUpdate");
return true;
} catch(e) {
return false;
}
return false;
}
var service = {};
/**
* @ngdoc method
* @name umbraco.services.clipboardService#copy
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to storing, example: 'elementType', 'contentNode'
* @param {string} alias A string defining the alias of the data to store, example: 'product'
* @param {object} data A object containing the properties to be saved.
*
* @description
* Saves a single JS-object with a type and alias to the clipboard.
*/
service.copy = function(type, alias, data) {
var storage = retriveStorage();
var shallowCloneData = Object.assign({}, data);// Notice only a shallow copy, since we dont need to deep copy. (that will happen when storing the data)
delete shallowCloneData.key;
delete shallowCloneData.$$hashKey;
var key = data.key || data.$$hashKey || console.error("missing unique key for this content");
// remove previous copies of this entry:
storage.entries = storage.entries.filter(
(entry) => {
return entry.unique !== key;
}
);
var entry = {unique:key, type:type, alias:alias, data:shallowCloneData};
storage.entries.push(entry);
if (saveStorage(storage) === true) {
notificationsService.success("Clipboard", "Copied to clipboard.");
} else {
notificationsService.success("Clipboard", "Couldnt copy this data to clipboard.");
}
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#supported
* @methodOf umbraco.services.clipboardService
*
* @description
* Determins wether the current browser is able to performe its actions.
*/
service.isSupported = function() {
return localStorageService.isSupported;
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#hasEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data test for.
* @param {string} aliases A array of strings providing the alias of the data you want to test for.
*
* @description
* Determines whether the current clipboard has entries that match a given type and one of the aliases.
*/
service.hasEntriesOfType = function(type, aliases) {
if(service.retriveEntriesOfType(type, aliases).length > 0) {
return true;
}
return false;
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#retriveEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to recive.
* @param {string} aliases A array of strings providing the alias of the data you want to recive.
*
* @description
* Returns an array of entries matching the given type and one of the provided aliases.
*/
service.retriveEntriesOfType = function(type, aliases) {
var storage = retriveStorage();
// Find entries that are fulfilling the criteria for this nodeType and nodeTypesAliases.
var filteretEntries = storage.entries.filter(
(entry) => {
return (entry.type === type && aliases.filter(alias => alias === entry.alias).length > 0);
}
);
return filteretEntries;
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#retriveEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to recive.
* @param {string} aliases A array of strings providing the alias of the data you want to recive.
*
* @description
* Returns an array of data of entries matching the given type and one of the provided aliases.
*/
service.retriveDataOfType = function(type, aliases) {
return service.retriveEntriesOfType(type, aliases).map((x) => x.data);
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#retriveEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to remove.
* @param {string} aliases A array of strings providing the alias of the data you want to remove.
*
* @description
* Removes entries matching the given type and one of the provided aliases.
*/
service.clearEntriesOfType = function(type, aliases) {
var storage = retriveStorage();
// Find entries that are NOT fulfilling the criteria for this nodeType and nodeTypesAliases.
var filteretEntries = storage.entries.filter(
(entry) => {
return !(entry.type === type && aliases.filter(alias => alias === entry.alias).length > 0);
}
);
storage.entries = filteretEntries;
saveStorage(storage);
};
return service;
}
/**
* @ngdoc service
* @name umbraco.services.clipboardService
*
* @requires notificationsService
* @requires eventsService
*
* @description
* Service to handle clipboard in general across the application. Responsible for handling the data both storing and retrive.
* The service has a set way for defining a data-set by a entryType and alias, which later will be used to retrive the posible entries for a paste scenario.
*
*/
function clipboardService(notificationsService, eventsService, localStorageService, iconHelper) {
var STORAGE_KEY = "umbClipboardService";
var retriveStorage = function() {
if (localStorageService.isSupported === false) {
return null;
}
var dataJSON;
var dataString = localStorageService.get(STORAGE_KEY);
if (dataString != null) {
dataJSON = JSON.parse(dataString);
}
if(dataJSON == null) {
dataJSON = new Object();
}
if(dataJSON.entries === undefined) {
dataJSON.entries = [];
}
return dataJSON;
}
var saveStorage = function(storage) {
var storageString = JSON.stringify(storage);
try {
var storageJSON = JSON.parse(storageString);
localStorageService.set(STORAGE_KEY, storageString);
eventsService.emit("clipboardService.storageUpdate");
return true;
} catch(e) {
return false;
}
return false;
}
var prepareEntryForStorage = function(entryData) {
var shallowCloneData = Object.assign({}, entryData);// Notice only a shallow copy, since we dont need to deep copy. (that will happen when storing the data)
delete shallowCloneData.key;
delete shallowCloneData.$$hashKey;
return shallowCloneData;
}
var isEntryCompatible = function(entry, type, allowedAliases) {
return entry.type === type
&&
(
(entry.alias && allowedAliases.filter(allowedAlias => allowedAlias === entry.alias).length > 0)
||
(entry.aliases && entry.aliases.filter(entryAlias => allowedAliases.filter(allowedAlias => allowedAlias === entryAlias).length > 0).length === entry.aliases.length)
);
}
var service = {};
/**
* @ngdoc method
* @name umbraco.services.clipboardService#copy
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to storing, example: 'elementType', 'contentNode'
* @param {string} alias A string defining the alias of the data to store, example: 'product'
* @param {object} entry A object containing the properties to be saved, this could be the object of a ElementType, ContentNode, ...
* @param {string} displayLabel (optional) A string swetting the label to display when showing paste entries.
*
* @description
* Saves a single JS-object with a type and alias to the clipboard.
*/
service.copy = function(type, alias, data, displayLabel) {
var storage = retriveStorage();
var uniqueKey = data.key || data.$$hashKey || console.error("missing unique key for this content");
// remove previous copies of this entry:
storage.entries = storage.entries.filter(
(entry) => {
return entry.unique !== uniqueKey;
}
);
var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data), label:displayLabel || data.name, icon:iconHelper.convertFromLegacyIcon(data.icon)};
storage.entries.push(entry);
if (saveStorage(storage) === true) {
notificationsService.success("Clipboard", "Copied to clipboard.");
} else {
notificationsService.error("Clipboard", "Couldnt copy this data to clipboard.");
}
};
/**
* @ngdoc method
* @name umbraco.services.clipboardService#copyArray
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to storing, example: 'elementTypeArray', 'contentNodeArray'
* @param {string} aliases An array of strings defining the alias of the data to store, example: ['banana', 'apple']
* @param {object} datas An array of objects containing the properties to be saved, example: [ElementType, ElementType, ...]
* @param {string} displayLabel A string setting the label to display when showing paste entries.
* @param {string} displayIcon A string setting the icon to display when showing paste entries.
* @param {string} uniqueKey A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data.
*
* @description
* Saves a single JS-object with a type and alias to the clipboard.
*/
service.copyArray = function(type, aliases, datas, displayLabel, displayIcon, uniqueKey) {
var storage = retriveStorage();
// Clean up each entry
var copiedDatas = datas.map(data => prepareEntryForStorage(data));
// remove previous copies of this entry:
storage.entries = storage.entries.filter(
(entry) => {
return entry.unique !== uniqueKey;
}
);
var entry = {unique:uniqueKey, type:type, aliases:aliases, data:copiedDatas, label:displayLabel, icon:displayIcon};
storage.entries.push(entry);
if (saveStorage(storage) === true) {
notificationsService.success("Clipboard", "Copied to clipboard.");
} else {
notificationsService.error("Clipboard", "Couldnt copy this data to clipboard.");
}
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#supported
* @methodOf umbraco.services.clipboardService
*
* @description
* Determins wether the current browser is able to performe its actions.
*/
service.isSupported = function() {
return localStorageService.isSupported;
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#hasEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data test for.
* @param {string} aliases A array of strings providing the alias of the data you want to test for.
*
* @description
* Determines whether the current clipboard has entries that match a given type and one of the aliases.
*/
service.hasEntriesOfType = function(type, aliases) {
if(service.retriveEntriesOfType(type, aliases).length > 0) {
return true;
}
return false;
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#retriveEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to recive.
* @param {string} aliases A array of strings providing the alias of the data you want to recive.
*
* @description
* Returns an array of entries matching the given type and one of the provided aliases.
*/
service.retriveEntriesOfType = function(type, allowedAliases) {
var storage = retriveStorage();
// Find entries that are fulfilling the criteria for this nodeType and nodeTypesAliases.
var filteretEntries = storage.entries.filter(
(entry) => {
return isEntryCompatible(entry, type, allowedAliases);
}
);
return filteretEntries;
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#retriveEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to recive.
* @param {string} aliases A array of strings providing the alias of the data you want to recive.
*
* @description
* Returns an array of data of entries matching the given type and one of the provided aliases.
*/
service.retriveDataOfType = function(type, aliases) {
return service.retriveEntriesOfType(type, aliases).map((x) => x.data);
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#retriveEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to remove.
* @param {string} aliases A array of strings providing the alias of the data you want to remove.
*
* @description
* Removes entries matching the given type and one of the provided aliases.
*/
service.clearEntriesOfType = function(type, allowedAliases) {
var storage = retriveStorage();
// Find entries that are NOT fulfilling the criteria for this nodeType and nodeTypesAliases.
var filteretEntries = storage.entries.filter(
(entry) => {
return !isEntryCompatible(entry, type, allowedAliases);
}
);
storage.entries = filteretEntries;
saveStorage(storage);
};
return service;
}
angular.module("umbraco.services").factory("clipboardService", clipboardService);

View File

@@ -338,6 +338,22 @@ function navigationService($routeParams, $location, $q, $injector, eventsService
});
},
/**
* @ngdoc method
* @name umbraco.services.navigationService#hasTree
* @methodOf umbraco.services.navigationService
*
* @description
* Checks if a tree with the given alias exists.
*
* @param {String} treeAlias the tree alias to check
*/
hasTree: function (treeAlias) {
return navReadyPromise.promise.then(function () {
return mainTreeApi.hasTree(treeAlias);
});
},
/**
Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
have to set an active tree and then sync, the new API does this in one method by using syncTree

View File

@@ -89,6 +89,7 @@
}
function confirmDelete(overlay) {
overlay.confirmType = "delete";
confirm(overlay);
}

View File

@@ -184,7 +184,13 @@
});
if (cached) return cached[1];
nodeComputedStyle = nodeComputedStyle || this.doc.defaultView.getComputedStyle(node);
if (!nodeComputedStyle) {
if (node instanceof DocumentFragment) {
return true;// though DocumentFragment doesn't directly have display 'none', we know that it will never be visible, and therefore we return true. (and do not cache this, cause it will change if appended to the DOM)
} else {
nodeComputedStyle = this.doc.defaultView.getComputedStyle(node);
}
}
var result = false;

View File

@@ -543,11 +543,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s
'contenteditable': false
},
embed.preview);
if (activeElement) {
// Only replace if activeElement is an Embed element.
if (activeElement && activeElement.nodeName.toUpperCase() === "DIV" && activeElement.classList.contains("embeditem")){
activeElement.replaceWith(wrapper); // directly replaces the html node
}
else {
} else {
editor.selection.setNode(wrapper);
}
},

View File

@@ -0,0 +1,34 @@
(function () {
'use strict';
function validationMessageService($q, localizationService) {
// Gets the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type's validation object
// or a localised default.
function getMandatoryMessage(validation) {
if (!validation) {
return $q.when("");
}
if (validation.mandatoryMessage) {
return $q.when(validation.mandatoryMessage);
} else {
return localizationService.localize("general_required").then(function (value) {
return $q.when(value);
});
}
}
var service = {
getMandatoryMessage: getMandatoryMessage
};
return service;
}
angular.module('umbraco.services').factory('validationMessageService', validationMessageService);
})();

View File

@@ -130,6 +130,7 @@
@import "components/umb-media-grid.less";
@import "components/umb-folder-grid.less";
@import "components/umb-content-grid.less";
@import "components/umb-contextmenu.less";
@import "components/umb-layout-selector.less";
@import "components/tooltip/umb-tooltip.less";
@import "components/tooltip/umb-tooltip-list.less";
@@ -138,6 +139,7 @@
@import "components/umb-grid.less";
@import "components/umb-empty-state.less";
@import "components/umb-property-editor.less";
@import "components/umb-property-actions.less";
@import "components/umb-color-swatches.less";
@import "components/check-circle.less";
@import "components/umb-file-icon.less";
@@ -188,6 +190,8 @@
@import "components/users/umb-user-preview.less";
@import "components/users/umb-user-picker-list.less";
@import "components/contextdialogs/umb-dialog-datatype-delete.less";
// Utilities
@import "utilities/layout/_display.less";

View File

@@ -0,0 +1,33 @@
.umb-dialog-datatype-delete {
.umb-dialog-datatype-delete__table-head-column-name {
width: 140px;
}
.umb-table-body__icon {
margin-right: 5px;
vertical-align: top;
display: inline-block;
}
.table tbody td {
vertical-align: top;
}
.table tbody td > span {
margin: 5px 0;
vertical-align: middle;
}
.table tbody p {
line-height: 12px;
margin: 5px 0;
vertical-align: middle;
}
.table tbody .icon {
vertical-align: top;
margin-right: 5px;
display: inline-block;
}
}

View File

@@ -0,0 +1,75 @@
.umb-contextmenu {
margin: 0;
list-style: none;
user-select: none;
overflow: hidden;
border-radius: 3px;
border: 1px solid @dropdownBorder;
.box-shadow(0 5px 20px rgba(0,0,0,.3));
border-bottom: 1px solid rgba(0,0,0,.2);
.sep {
display: block;
border-top: 1px solid @gray-9;
&:first-child {
border-top: none;
}
}
}
.umb-contextmenu-item {
.icon {
font-size: 18px;
vertical-align: middle;
}
.menu-label {
display: inline-block;
vertical-align: middle;
margin-left: 5px;
}
button {
position: relative;
display: block;
font-weight: normal;
line-height: @baseLineHeight;
white-space: nowrap;
background-color: @ui-option;
border: 0;
padding: 7px 12px;
color: @ui-option-type;
width: 100%;
font-size: 14px;
text-align: left;
&:hover {
text-decoration: none;
color: @ui-option-type-hover;
background-color: @ui-option-hover;
}
}
&.-opens-dialog {
.menu-label:after {
// adds an ellipsis (...) after the menu label for actions that open a dialog
content: '\2026';
}
}
button:disabled {
cursor: not-allowed;
color: @ui-option-disabled-type;
&:hover {
color: @ui-option-disabled-type-hover;
background-color: @ui-option;
}
}
}

View File

@@ -493,11 +493,11 @@ input.umb-group-builder__group-sort-value {
font-weight: bold;
font-size: 14px;
color: @ui-action-type;
&:hover{
&:hover {
text-decoration: none;
color:@ui-action-type-hover;
border-color:@ui-action-border-hover;
color: @ui-action-type-hover;
border-color: @ui-action-border-hover;
}
}
@@ -554,7 +554,13 @@ input.umb-group-builder__group-sort-value {
overflow: hidden;
}
.editor-validation-pattern{
.editor-validation-message {
min-width: 100%;
min-height: 25px;
margin-top: 4px;
}
.editor-validation-pattern {
border: 1px solid @gray-7;
margin: 10px 0 0;
padding: 6px;

View File

@@ -1,5 +1,4 @@
.umb-nested-content {
text-align: center;
position: relative;
}
@@ -15,6 +14,17 @@
pointer-events: none;
}
.umb-nested-content--mandatory {
/*
yeah so this is a pain, but we must be super specific in targeting the mandatory property labels,
otherwise all properties within a reqired, nested, nested content property will all appear mandatory
*/
> ng-form > .control-group > .umb-el-wrap > .control-header label:after {
content: '*';
color: @red;
}
}
.umb-nested-content-overlay {
position: absolute;
top: 0;
@@ -135,6 +145,8 @@
.umb-nested-content__icon {
background: transparent;
border: 0 none;
display: inline-block;
padding: 4px;
margin: 2px;
@@ -170,6 +182,7 @@
.umb-nested-content__add-content {
display: flex;
width: 100%;
align-items: center;
justify-content: center;
border: 1px dashed @ui-action-discreet-border;
@@ -235,7 +248,6 @@
}
.umb-nested-content__placeholder {
height: 22px;
padding: 4px 6px;
border: 1px dashed #d8d7d9;
background: 0 0;
@@ -246,33 +258,18 @@
text-align: center;
&--selected {
border: 1px solid #d8d7d9;
border: none;
text-align: left;
padding: 0;
}
}
.umb-nested-content__placeholder-name{
font-size: 15px;
}
.umb-nested-content__placeholder:hover {
color: #2152a3;
border-color: #2152a3;
text-decoration: none;
}
.umb-nested-content__placeholder-icon-holder {
width: 20px;
text-align: center;
display: inline-block;
}
.umb-nested-content__placeholder-icon {
font-size: 18px;
vertical-align: middle;
}
.form-horizontal .umb-nested-content--narrow .controls-row {
margin-left: 40% !important;
}

View File

@@ -107,5 +107,5 @@
.umb-panel-group__details-status-action-description {
margin-top: 5px;
font-size: 12px;
padding-left: 165px;
padding-left:165px;
}

View File

@@ -0,0 +1,96 @@
.umb-property-actions {
display: inline;
}
.umb-property-actions__toggle,
.umb-property-actions__menu-open-toggle {
position: relative;
display: flex;
flex: 0 0 auto;
padding: 6px 6px;
text-align: center;
cursor: pointer;
border-radius: 3px;
background-color: @ui-action-hover;
i {
height: 3px !important;
width: 3px !important;
border-radius: 3px;
background: @ui-action-type;
display: inline-block;
margin: 0 2px 0 0;
&:last-child {
margin: 0;
}
}
&:hover {
i {
background: @ui-action-type-hover;
}
}
}
.umb-property-actions__menu-open-toggle {
position: absolute;
z-index:1;
outline: none;// this is not acceccible by keyboard, since we use the .umb-property-actions__toggle for that.
top: -15px;
border-radius: 3px 3px 0 0;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
border: 1px solid @dropdownBorder;
border-bottom: 1px solid @gray-9;
.box-shadow(0 5px 20px rgba(0,0,0,.3));
background-color: white;
}
.umb-property .umb-property-actions {
float: left;
}
.umb-property .umb-property-actions__toggle {
margin-top: 2px;
opacity: 0;
transition: opacity 120ms;
}
.umb-property:hover .umb-property-actions__toggle,
.umb-property .umb-property-actions__toggle:focus {
opacity: 1;
}
// Revert-style-hack that ensures that we only show property-actions on properties that are directly begin hovered.
.umb-property:hover .umb-property:not(:hover) .umb-property-actions__toggle {
opacity: 0;
}
.umb-property-actions__menu {
position: absolute;
z-index: 1000;
display: block;
float: left;
min-width: 160px;
list-style: none;
.umb-contextmenu {
border-top-left-radius: 0;
margin-top:1px;
}
.umb-contextmenu-item > button {
z-index:2;// need to stay on top of menu-toggle-open shadow.
}
}

View File

@@ -161,6 +161,7 @@ input.umb-table__input {
line-height: 20px;
color: @ui-option-type;
vertical-align: bottom;
text-decoration: none;
}
.umb-table-body__checkicon,

View File

@@ -63,6 +63,10 @@ a.umb-user-details-details__back-link {
.umb-user-details-details__sidebar {
flex: 0 0 @sidebarwidth;
.umb-button{
margin-left:0px;
}
}
@media (max-width: 768px) {
@@ -101,6 +105,7 @@ a.umb-user-details-details__back-link {
.umb-user-details-details__information-item {
margin-bottom: 10px;
font-size: 13px;
margin-top:10px;
}
.umb-user-details-details__information-item-label {

View File

@@ -8,7 +8,6 @@
.umb-healthcheck-help-text {
line-height: 1.6em;
max-width: 750px;
}
.umb-healthcheck-action-bar {

View File

@@ -528,7 +528,8 @@ input[type="checkbox"][readonly] {
.help-inline {
display: inline-block;
vertical-align: middle;
padding-left: 5px;
padding-top: 4px;
padding-left: 2px;
}
div.help {

View File

@@ -116,24 +116,42 @@ h5.-black {
margin: 20px;
}
.umb-control-group {
border-bottom: 1px solid @gray-11;
padding-bottom: 20px;
position: relative;
&::after {
content: '';
display:block;
margin-top: 20px;
width: 100%;
height: 1px;
background-color: @gray-11;
}
}
.umb-control-group.-no-border {
border: none;
&::after {
margin-top: 0px;
height: 0;
background-color: transparent;
}
}
.umb-property:last-of-type .umb-control-group {
border: none;
margin-bottom: 0 !important;
padding-bottom: 0;
&::after {
margin-top: 0px;
height: 0;
background-color: transparent;
}
margin-bottom: 0 !important;
}
/* BLOCK MODE */
.block-form .umb-control-group {
border-bottom: none;
padding-bottom: 0;
margin-top: 0px;
&::after {
margin-top: 0px;
height: 0;
background-color: transparent;
}
}
.block-form .umb-control-group label .help-block,
@@ -163,7 +181,36 @@ h5.-black {
}
.umb-control-group .umb-el-wrap {
padding: 0
padding: 0;
}
.form-horizontal .umb-control-group .control-header {
float: left;
width: 160px;
padding-top: 5px;
text-align: left;
.control-label {
float: left;
width: auto;
padding-top: 0;
text-align: left;
}
.control-description {
display: block;
clear: both;
max-width:480px;// avoiding description becoming too wide when its placed on top of property.
margin-bottom: 10px;
}
}
@media (max-width: 767px) {
.form-horizontal .umb-control-group .control-header {
float: none;
width: 100%;
}
}
/* LABELS*/

View File

@@ -97,7 +97,8 @@
.history-line {
width: 2px;
height: 100%;
top: 10px;
bottom: 10px;
margin: 0 0 0 14px;
background-color: @gray-8;
position: absolute;

View File

@@ -415,8 +415,6 @@
text-decoration: none;
display: flex;
flex-direction: row;
opacity: 0;
visibility: hidden;
}
.umb-sortable-thumbnails.ui-sortable:not(.ui-sortable-disabled) {
@@ -425,9 +423,8 @@
}
}
.umb-sortable-thumbnails li:hover .umb-sortable-thumbnails__actions {
.umb-sortable-thumbnails li:hover .umb-sortable-thumbnails__action {
opacity: 1;
visibility: visible;
}
.umb-sortable-thumbnails .umb-sortable-thumbnails__action {
@@ -443,6 +440,12 @@
margin-left: 5px;
text-decoration: none;
.box-shadow(0 1px 2px rgba(0,0,0,0.25));
opacity: 0;
transition: opacity .1s ease-in-out;
.tabbing-active &:focus {
opacity: 1;
}
}
.umb-sortable-thumbnails .umb-sortable-thumbnails__action.-red {

View File

@@ -132,6 +132,7 @@
@ui-option-type: @blueExtraDark;
@ui-option-type-hover: @blueMid;
@ui-option: white;
@ui-option-hover: @sand-7;
@ui-option-disabled-type: @gray-6;

View File

@@ -60,7 +60,7 @@
</ul>
</div>
<div ng-if="vm.availableGroups.length > 0">
<div class="umb-control-group -no-border" ng-if="vm.availableGroups.length > 0">
<ul class="umb-checkbox-list" ng-repeat="group in vm.availableGroups | filter:searchTerm">
<li ng-show="vm.availableGroups.length > 1">
<i class="icon-folder umb-checkbox-list__item-icon"></i>

View File

@@ -15,42 +15,40 @@
<div on-drag-leave="vm.dragLeave()" on-drag-end="vm.dragLeave()" on-drag-enter="vm.dragEnter()">
<div class="umb-control-group umb-mediapicker-upload">
<div class="umb-control-group">
<umb-load-indicator
ng-if="vm.loading">
</umb-load-indicator>
<div class="umb-mediapicker-upload">
<div class="form-search">
<i class="icon-search" aria-hidden="true"></i>
<input class="umb-search-field search-query -full-width-input"
ng-model="vm.searchOptions.filter"
localize="placeholder"
placeholder="@placeholders_search"
ng-change="vm.changeSearch()"
type="text"
umb-auto-focus
no-dirty-check
/>
<div class="form-search">
<i class="icon-search" aria-hidden="true"></i>
<input class="umb-search-field search-query -full-width-input"
ng-model="vm.searchOptions.filter"
localize="placeholder"
placeholder="@placeholders_search"
ng-change="vm.changeSearch()"
type="text"
umb-auto-focus
no-dirty-check
/>
<div class="form-search__toggle">
<umb-checkbox
model="showChilds"
on-change="vm.toggle()"
text="Include subfolders in search"
label-key="general_includeFromsubFolders">
</umb-checkbox>
<div class="form-search__toggle">
<umb-checkbox
model="showChilds"
on-change="vm.toggle()"
text="Include subfolders in search"
label-key="general_includeFromsubFolders">
</umb-checkbox>
</div>
</div>
</div>
<div class="upload-button">
<umb-button
type="button"
label-key="general_upload"
action="vm.upload()"
disabled="lockedFolder"
button-style="action">
</umb-button>
<div class="upload-button">
<umb-button
type="button"
label-key="general_upload"
action="vm.upload()"
disabled="lockedFolder"
button-style="action">
</umb-button>
</div>
</div>
</div>
@@ -120,6 +118,11 @@
current-folder-id="{{currentFolder.id}}">
</umb-media-grid>
<umb-load-indicator
ng-if="vm.loading">
</umb-load-indicator>
<div class="flex justify-center">
<umb-pagination
ng-if="vm.searchOptions.totalPages > 0 && !vm.loading"
@@ -136,7 +139,7 @@
</div>
<umb-overlay ng-if="vm.mediaPickerDetailsOverlay.show" model="vm.mediaPickerDetailsOverlay" position="right">
<div class="umb-control-group" ng-if="!target.id">
<h5>
<localize key="@general_url"></localize>

View File

@@ -81,39 +81,55 @@
</div>
<div class="umb-control-group clearfix" ng-if="!model.property.locked">
<h5><localize key="validation_validation"></localize></h5>
<label>
<label>
<localize key="validation_fieldIsMandatory"></localize>
</label>
<umb-toggle data-element="validation_mandatory"
checked="model.property.validation.mandatory"
on-click="vm.toggleValidation()"
on-click="vm.toggleValidation()">
focus-when="{{vm.focusOnMandatoryField}}"
>
</umb-toggle>
<label class="mt3">
<input type="text"
class="editor-validation-message"
localize="placeholder"
placeholder="@validation_mandatoryMessage"
ng-model="model.property.validation.mandatoryMessage"
ng-if="model.property.validation.mandatory"
ng-keypress="vm.submitOnEnter($event)">
</input>
<label class="mt3">
<localize key="validation_customValidation"></localize>
</label>
<select class="umb-dropdown" ng-options="validationType.name for validationType in vm.validationTypes" ng-model="vm.selectedValidationType" ng-change="vm.changeValidationType(vm.selectedValidationType)">
<option value=""><localize key="validation_noValidation">No validation</localize></option>
</select>
<textarea class="editor-validation-pattern"
localize="placeholder"
placeholder="@validation_validationRegExp"
ng-model="model.property.validation.pattern"
ng-change="vm.changeValidationPattern()"
ng-if="vm.showValidationPattern"
umb-auto-resize
focus-when="{{vm.focusOnPatternField}}"
ng-keypress="vm.submitOnEnter($event)">
</textarea>
localize="placeholder"
placeholder="@validation_validationRegExp"
ng-model="model.property.validation.pattern"
ng-change="vm.changeValidationPattern()"
ng-if="vm.showValidationPattern"
umb-auto-resize
focus-when="{{vm.focusOnPatternField}}"
ng-keypress="vm.submitOnEnter($event)">
</textarea>
<input type="text"
class="editor-validation-message"
localize="placeholder"
placeholder="@validation_validationRegExpMessage"
ng-model="model.property.validation.patternMessage"
ng-if="vm.showValidationPattern"
ng-keypress="vm.submitOnEnter($event)" />
</div>
<div class="umb-control-group clearfix" ng-if="model.contentType === 'documentType' && model.contentTypeAllowCultureVariant">

View File

@@ -46,6 +46,7 @@
title="{{historyLabel}}">
<umb-button
ng-hide="node.trashed"
type="button"
button-style="outline"
action="openRollback()"
@@ -168,9 +169,9 @@
ng-change="updateTemplate(node.template)">
<option value="">{{chooseLabel}}...</option>
</select>
<a href="" ng-show="allowChangeTemplate && node.template !== null" class="umb-node-preview__action" style="margin-left:15px;" ng-click="openTemplate()">
<button ng-show="allowChangeTemplate && node.template !== null" class="umb-node-preview__action" style="margin-left:15px;" ng-click="openTemplate()">
<localize key="general_open">Open</localize>
</a>
</button>
</div>
</umb-control-group>

View File

@@ -0,0 +1,15 @@
<div class="umb-property-actions" ng-if="vm.actions.length > 0">
<button type="button" class="btn-reset umb-outline umb-property-actions__toggle" ng-click="vm.toggle()" localize="title" title="propertyActions_tooltipForPropertyActionsMenu"><i></i><i></i><i></i></button>
<div class="umb-property-actions__menu" role="menu" ng-if="vm.isOpen" on-outside-click="vm.close()" on-close="vm.close()" deep-blur="vm.close()">
<button class="umb-property-actions__menu-open-toggle" ng-click="vm.close()" tabindex="-1"><i></i><i></i><i></i></button>
<ul class="umb-contextmenu">
<li ng-repeat="action in vm.actions" role="menuitem" class="umb-contextmenu-item" ng-class="{'-opens-dialog': action.opensDialog}">
<button type="button" class="btn-reset umb-outline" ng-click="vm.executeAction(action)" ng-disabled="action.isDisabled === true">
<i class="icon icon-{{action.icon}}" aria-hidden="true"></i>
<span class="menu-label"><localize key="{{::action.labelKey}}" tokens="action.labelTokens"></localize></span>
</button>
</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,61 @@
(function () {
'use strict';
/**
* A component to render the property action toggle
*/
function umbPropertyActionsController(keyboardService) {
var vm = this;
vm.isOpen = false;
function initDropDown() {
keyboardService.bind("esc", vm.close);
}
function destroyDropDown() {
keyboardService.unbind("esc");
}
vm.toggle = function() {
if (vm.isOpen === true) {
vm.close();
} else {
vm.open();
}
}
vm.open = function() {
vm.isOpen = true;
initDropDown();
}
vm.close = function() {
vm.isOpen = false;
destroyDropDown();
}
vm.executeAction = function(action) {
action.method();
vm.close();
}
vm.$onDestroy = function () {
if (vm.isOpen === true) {
destroyDropDown();
}
}
}
var umbPropertyActionsComponent = {
templateUrl: 'views/components/property/property-actions/umb-property-actions.html',
bindings: {
actions: "<"
},
controllerAs: 'vm',
controller: umbPropertyActionsController
};
angular.module('umbraco.directives').component('umbPropertyActions', umbPropertyActionsComponent);
})();

View File

@@ -6,19 +6,25 @@
<div class="umb-el-wrap">
<label class="control-label" ng-hide="property.hideLabel" for="{{property.alias}}" ng-attr-title="{{propertyAlias}}">
<div class="control-header">
<small ng-if="showInherit" class="db" style="padding-top: 0; margin-bottom: 5px;">
<localize key="contentTypeEditor_inheritedFrom"></localize> {{inheritsFrom}}
</small>
{{property.label}}
<label class="control-label" ng-hide="property.hideLabel" for="{{property.alias}}" ng-attr-title="{{propertyAlias}}">
<span ng-if="property.validation.mandatory">
<strong class="umb-control-required">*</strong>
</span>
<small ng-bind-html="property.description | preserveNewLineInHtml"></small>
</label>
{{property.label}}
<span ng-if="property.validation.mandatory">
<strong class="umb-control-required">*</strong>
</span>
</label>
<umb-property-actions actions="propertyActions"></umb-property-actions>
<small class="control-description" ng-bind-html="property.description | preserveNewLineInHtml"></small>
</div>
<div class="controls" ng-transclude>
</div>

View File

@@ -14,6 +14,7 @@
action="confirm()"
button-style="{{confirmButtonStyle || 'primary'}}"
state="confirmButtonState"
disabled="confirmDisabled === true"
label-key="{{confirmLabelKey || 'general_ok'}}">
</umb-button>
</div>

View File

@@ -4,13 +4,15 @@
<umb-box>
<umb-box-content>
<h3>Hours of Umbraco training videos are only a click away</h3>
<p>Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit <a href="https://umbraco.tv" target="_blank">umbraco.tv</a> for even more Umbraco videos</p>
<h3><localize key="settingsDashboardVideos_trainingHeadline">Hours of Umbraco training videos are only a click away</localize></h3>
<localize key="settingsDashboardVideos_trainingDescription">
<p>Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit <a href="http://umbraco.tv" target="_blank">umbraco.tv</a> for even more Umbraco videos</p>
</localize>
</umb-box-content>
</umb-box>
<div ng-show="videos.length">
<h4>To get you started:</h4>
<h4><localize key="settingsDashboardVideos_getStarted">To get you started</localize>:</h4>
<ul class="thumbnails" >
<li class="span2" ng-repeat="video in videos">
<div class="thumbnail" style="margin-right: 20px">

View File

@@ -100,8 +100,12 @@
<div class="umb-panel-group__details-check">
<div class="umb-panel-group__details-check-title">
<div class="umb-panel-group__details-check-name">Search</div>
<div class="umb-panel-group__details-check-description">Search the index and view the results</div>
<div class="umb-panel-group__details-check-name">
<localize key="general_search">Search</localize>
</div>
<div class="umb-panel-group__details-check-description">
<localize key="examineManagement_searchDescription">Search the index and view the results</localize>
</div>
</div>
<div class="umb-panel-group__details-status">
@@ -225,7 +229,7 @@
<div class="umb-panel-group__details-status-text">
<div>{{vm.selectedIndex.healthStatus}}</div>
<div ng-show="!vm.selectedIndex" class="color-red">
The index cannot be read and will need to be rebuilt
<localize key="examineManagement_indexCannotRead">The index cannot be read and will need to be rebuilt</localize>
</div>
<!--<div ng-if="status.description" ng-bind-html="status.description"></div>-->
</div>
@@ -377,13 +381,14 @@
</div>
<div ng-show="vm.selectedIndex.processingAttempts >= 100">
The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation
<localize key="examineManagement_processIsTakingLonger">The process is taking longer than expected, check the umbraco log to see if there have been any errors during this operation</localize>
</div>
</ng-form>
<div class="umb-panel-group__details-status-action-description" ng-show="!vm.selectedIndex.canRebuild">
This index cannot be rebuilt because it has no assigned <code>IIndexPopulator</code>
<localize key="examineManagement_indexCannotRebuild">This index cannot be rebuilt because it has no assigned </localize>
<code><localize key="examineManagement_iIndexPopulator">IIndexPopulator</localize></code>
</div>
</div>
</div>

View File

@@ -4,78 +4,70 @@
<umb-box>
<umb-box-content>
<div class="flex justify-between items-center">
<h3 class="bold">Health Check</h3>
<umb-button
type="button"
button-style="success"
label="Check All Groups"
action="vm.checkAllGroups(vm.groups)">
</umb-button>
</div>
<div class="umb-healthcheck-help-text">
<p>The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button.
You can add your own health checks, have a look at <a href="https://our.umbraco.com/documentation/Extending/Healthcheck/" target="_blank" class="btn-link -underline">the documentation for more information</a> about custom health checks.</p>
<p>
The health checker evaluates various areas of your site for best practice settings, configuration, potential problems, etc. You can easily fix problems by pressing a button.
You can add your own health checks, have a look at <a href="https://our.umbraco.com/documentation/Extending/Healthcheck/" target="_blank" class="btn-link -underline">the documentation for more information</a> about custom health checks.
</p>
</div>
<div class="umb-panel-group__details-status-actions">
<umb-button type="button"
button-style="success"
label="Check All Groups"
action="vm.checkAllGroups(vm.groups)">
</umb-button>
</div>
</umb-box-content>
</umb-box>
<div class="umb-healthcheck">
<div class="umb-air" ng-repeat="group in vm.groups">
<button type="button" class="umb-healthcheck-group" ng-click="vm.openGroup(group);">
<div class="umb-healthcheck-title">{{group.name}}</div>
<div class="umb-healthcheck-title">{{group.name}}</div>
<div class="umb-healthcheck-group__load-container" ng-if="group.loading">
<umb-load-indicator></umb-load-indicator>
<div class="umb-healthcheck-group__load-container" ng-if="group.loading">
<umb-load-indicator></umb-load-indicator>
</div>
<div class="umb-healthcheck-messages"
ng-hide="group.loading || !group.totalSuccess && !group.totalWarning && !group.totalError && !group.totalInfo">
<div class="umb-healthcheck-message" ng-if="group.totalSuccess > 0">
<i class="icon-check color-green" aria-hidden="true"></i>
{{ group.totalSuccess }}
<span class="sr-only">
<localize key="visuallyHiddenTexts_passed">passed</localize>
</span>
</div>
<div
class="umb-healthcheck-messages"
ng-hide="group.loading || !group.totalSuccess && !group.totalWarning && !group.totalError && !group.totalInfo"
>
<div class="umb-healthcheck-message" ng-if="group.totalSuccess > 0">
<i class="icon-check color-green" aria-hidden="true"></i>
{{ group.totalSuccess }}
<span class="sr-only">
<localize key="visuallyHiddenTexts_passed">passed</localize>
</span>
</div>
<div class="umb-healthcheck-message" ng-if="group.totalWarning > 0">
<i class="icon-alert color-orange" aria-hidden="true"></i>
{{ group.totalWarning }}
<span class="sr-only">
<localize key="visuallyHiddenTexts_warning">warning</localize>
</span>
</div>
<div class="umb-healthcheck-message" ng-if="group.totalError > 0">
<i class="icon-delete color-red" aria-hidden="true"></i>
{{ group.totalError }}
<span class="sr-only">
<localize key="visuallyHiddenTexts_failed">failed</localize>
</span>
</div>
<div class="umb-healthcheck-message" ng-if="group.totalInfo > 0">
<i class="umb-healthcheck-status-icon icon-info" aria-hidden="true"></i>
{{ group.totalInfo }}
<span class="sr-only">
<localize key="visuallyHiddenTexts_suggestion">suggestion</localize>
</span>
</div>
<div class="umb-healthcheck-message" ng-if="group.totalWarning > 0">
<i class="icon-alert color-orange" aria-hidden="true"></i>
{{ group.totalWarning }}
<span class="sr-only">
<localize key="visuallyHiddenTexts_warning">warning</localize>
</span>
</div>
<div class="umb-healthcheck-message" ng-if="group.totalError > 0">
<i class="icon-delete color-red" aria-hidden="true"></i>
{{ group.totalError }}
<span class="sr-only">
<localize key="visuallyHiddenTexts_failed">failed</localize>
</span>
</div>
<div class="umb-healthcheck-message" ng-if="group.totalInfo > 0">
<i class="umb-healthcheck-status-icon icon-info" aria-hidden="true"></i>
{{ group.totalInfo }}
<span class="sr-only">
<localize key="visuallyHiddenTexts_suggestion">suggestion</localize>
</span>
</div>
</div>
</button>
</div>
</div>
</div>
<div ng-if="vm.viewState === 'details'">
@@ -88,31 +80,25 @@
</umb-editor-sub-header-content-left>
</umb-editor-sub-header>
<div class="umb-panel-group__details">
<div class="umb-panel-group__details-group">
<div class="umb-panel-group__details-group-title">
<div class="umb-panel-group__details-group-name">{{ vm.selectedGroup.name }}</div>
<umb-button
type="button"
action="vm.checkAllInGroup(vm.selectedGroup, vm.selectedGroup.checks)"
label="Check group">
<umb-button type="button" button-style="success"
action="vm.checkAllInGroup(vm.selectedGroup, vm.selectedGroup.checks)"
label="Check group">
</umb-button>
</div>
<div class="umb-panel-group__details-checks">
<div class="umb-panel-group__details-check" ng-repeat="check in vm.selectedGroup.checks">
<div class="umb-panel-group__details-check-title">
<div class="umb-panel-group__details-check-name">{{ check.name }}</div>
<div class="umb-panel-group__details-check-description">{{ check.description }}</div>
</div>
<div class="umb-panel-group__details-status" ng-repeat="status in check.status">
<div class="umb-panel-group__details-status-icon-container" aria-hidden="true">
<i class="umb-healthcheck-status-icon icon-check color-green" ng-if="status.resultType === 0"></i>
<i class="umb-healthcheck-status-icon icon-alert icon-alert color-yellow" ng-if="status.resultType === 1"></i>
@@ -121,7 +107,6 @@
</div>
<div class="umb-panel-group__details-status-content">
<div class="umb-panel-group__details-status-text">
<span class="sr-only" ng-if="status.resultType === 0">
<localize key="visuallyHiddenTexts_checkPassed">Check passed</localize>:
@@ -145,16 +130,15 @@
<div ng-if="action.valueRequired">
<div><label class="bold">Set new value:</label></div>
<input name="providedValue" type="text" ng-model="action.providedValue" required val-email/>
<input name="providedValue" type="text" ng-model="action.providedValue" required val-email />
</div>
<umb-button
type="button"
button-style="success"
size="s"
disabled="healthCheckAction.providedValue.$invalid"
action="vm.executeAction(check, $parent.$index, action)"
label="{{action.name}}">
<umb-button type="button"
button-style="success"
size="s"
disabled="healthCheckAction.providedValue.$invalid"
action="vm.executeAction(check, $parent.$index, action)"
label="{{action.name}}">
</umb-button>
</ng-form>
@@ -162,9 +146,7 @@
<div class="umb-panel-group__details-status-action-description" ng-if="action.description" ng-bind-html="action.description"></div>
</div>
</div>
</div>
</div>
<div ng-show="check.loading">
@@ -173,13 +155,8 @@
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -4,12 +4,14 @@
<umb-load-indicator></umb-load-indicator>
</div>
<p>
<span ng-show="vm.working">(wait)</span>
<span ng-show="vm.working">(<localize key="nuCache_wait">wait</localize>)</span>
</p>
<div class="umb-panel-group__details-group">
<div class="umb-panel-group__details-group-title">
<div class="umb-panel-group__details-group-name">Published Cache Status</div>
<div class="umb-panel-group__details-group-name">
<localize key="nuCache_publishedCacheStatus">Published Cache Status</localize>
</div>
</div>
<div class="umb-panel-group__details-checks">
<div class="umb-panel-group__details-check">
@@ -20,7 +22,9 @@
</div>
<div class="umb-panel-group__details-status-actions">
<div class="umb-panel-group__details-status-action no-background no-left-padding">
<button type="button" ng-click="vm.verify($event)" class="btn btn-danger">Refresh status</button>
<button type="button" ng-click="vm.verify($event)" class="btn btn-danger">
<localize key="nuCache_refreshStatus">Refresh status</localize>
</button>
</div>
</div>
</div>
@@ -31,53 +35,75 @@
<br />
<div class="umb-panel-group__details-group">
<div class="umb-panel-group__details-group-title">
<div class="umb-panel-group__details-group-name">Caches</div>
<div class="umb-panel-group__details-group-name">
<localize key="nuCache_caches">Caches</localize>
</div>
</div>
<div class="umb-panel-group__details-checks">
<div class="umb-panel-group__details-check">
<div class="umb-panel-group__details-check-title">
<div class="umb-panel-group__details-check-name">Memory Cache</div>
<div class="umb-panel-group__details-check-name">
<localize key="nuCache_memoryCache">Memory Cache</localize>
</div>
<div class="umb-panel-group__details-check-description">
This button lets you reload the in-memory cache, by entirely reloading it from the database
cache (but it does not rebuild that database cache). This is relatively fast.
Use it when you think that the memory cache has not been properly refreshed, after some events
triggered&mdash;which would indicate a minor Umbraco issue.
(note: triggers the reload on all servers in an LB environment).
<localize key="nuCache_memoryCacheDescription">
This button lets you reload the in-memory cache, by entirely reloading it from the database
cache (but it does not rebuild that database cache). This is relatively fast.
Use it when you think that the memory cache has not been properly refreshed, after some events
triggered&mdash;which would indicate a minor Umbraco issue.
(note: triggers the reload on all servers in an LB environment).
</localize>
</div>
<div class="umb-panel-group__details-status-actions">
<div class="umb-panel-group__details-status-action no-background no-left-padding">
<button type="button" ng-click="vm.reload($event)" class="btn btn-danger">Reload</button>
<button type="button" ng-click="vm.reload($event)" class="btn btn-danger">
<localize key="nuCache_reload">Reload</localize>
</button>
</div>
</div>
</div>
<div class="umb-panel-group__details-check-title top-border">
<div class="umb-panel-group__details-check-name">Database Cache</div>
<div class="umb-panel-group__details-check-name">
<localize key="nuCache_databaseCache">Database Cache</localize>
</div>
<div class="umb-panel-group__details-check-description">
This button lets you rebuild the database cache, ie the content of the cmsContentNu table.
<strong>Rebuilding can be expensive.</strong>
Use it when reloading is not enough, and you think that the database cache has not been
properly generated&mdash;which would indicate some critical Umbraco issue.
<localize key="nuCache_databaseCacheDescription">
This button lets you rebuild the database cache, ie the content of the cmsContentNu table.
<strong>Rebuilding can be expensive.</strong>
Use it when reloading is not enough, and you think that the database cache has not been
properly generated&mdash;which would indicate some critical Umbraco issue.
</localize>
</div>
<div class="umb-panel-group__details-status-actions">
<div class="umb-panel-group__details-status-action no-background no-left-padding">
<button type="button" ng-click="vm.rebuild($event)" class="btn btn-danger">Rebuild</button>
<button type="button" ng-click="vm.rebuild($event)" class="btn btn-danger">
<localize key="nuCache_rebuild">Rebuild</localize>
</button>
</div>
</div>
</div>
<div class="umb-panel-group__details-check-title top-border">
<div class="umb-panel-group__details-check-name">Internals</div>
<div class="umb-panel-group__details-check-name">
<localize key="nuCache_internals">Internals</localize>
</div>
<div class="umb-panel-group__details-check-description">
This button lets you trigger a NuCache snapshots collection (after running a fullCLR GC).
Unless you know what that means, you probably do <em>not</em> need to use it.
<localize key="nuCache_internalsDescription">
This button lets you trigger a NuCache snapshots collection (after running a fullCLR GC).
Unless you know what that means, you probably do <em>not</em> need to use it.
</localize>
</div>
<div class="umb-panel-group__details-status-actions">
<div class="umb-panel-group__details-status-action no-background no-left-padding">
<button type="button" ng-click="vm.collect($event)" class="btn btn-danger">Collect</button>
<button type="button" ng-click="vm.collect($event)" class="btn btn-danger">
<localize key="nuCache_collect">Collect</localize>
</button>
</div>
</div>
</div>

View File

@@ -6,39 +6,51 @@
<umb-box ng-hide="vm.loading">
<umb-box-content>
<h3 class="bold">Performance profiling</h3>
<h3 class="bold">
<localize key="profiling_performanceProfiling">Performance profiling</localize>
</h3>
<div ng-show="vm.profilerEnabled">
<div class="mb4">
<p>
Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages.
</p>
<p>
If you want to activate the profiler for a specific page rendering, simply add <b>umbDebug=true</b> to the querystring when requesting the page.
</p>
<p>
If you want the profiler to be activated by default for all page renderings, you can use the toggle below.
It will set a cookie in your browser, which then activates the profiler automatically.
In other words, the profiler will only be active by default in <i>your</i> browser - not everyone else's.
</p>
<localize key="profiling_performanceProfilingDescription">
<p>
Umbraco currently runs in debug mode. This means you can use the built-in performance profiler to assess the performance when rendering pages.
</p>
<p>
If you want to activate the profiler for a specific page rendering, simply add <b>umbDebug=true</b> to the querystring when requesting the page.
</p>
<p>
If you want the profiler to be activated by default for all page renderings, you can use the toggle below.
It will set a cookie in your browser, which then activates the profiler automatically.
In other words, the profiler will only be active by default in <i>your</i> browser - not everyone else's.
</p>
</localize>
</div>
<div class="mb4">
<div class="flex items-center">
<umb-toggle checked="vm.alwaysOn" id="profilerAlwaysOn" on-click="vm.toggle()"></umb-toggle>
<label for="profilerAlwaysOn" class="mb0 ml2">Activate the profiler by default</label>
<label for="profilerAlwaysOn" class="mb0 ml2">
<localize key="profiling_activateByDefault">Activate the profiler by default</localize>
</label>
</div>
</div>
<h4>Friendly reminder</h4>
<p>
You should never let a production site run in debug mode. Debug mode is turned off by setting <b>debug="false"</b> on the <b>&lt;compilation /&gt;</b> element in web.config.
</p>
<h4>
<localize key="profiling_reminder">Friendly reminder</localize>
</h4>
<localize key="profiling_reminderDescription">
<p>
You should never let a production site run in debug mode. Debug mode is turned off by setting <b>debug="false"</b> on the <b>&lt;compilation /&gt;</b> element in web.config.
</p>
</localize>
</div>
<div ng-hide="vm.profilerEnabled">
<p>
Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site.
</p>
<p>
Debug mode is turned on by setting <b>debug="true"</b> on the <b>&lt;compilation /&gt;</b> element in web.config.
</p>
<localize key="profiling_profilerEnabledDescription">
<p>
Umbraco currently does not run in debug mode, so you can't use the built-in profiler. This is how it should be for a production site.
</p>
<p>
Debug mode is turned on by setting <b>debug="true"</b> on the <b>&lt;compilation /&gt;</b> element in web.config.
</p>
</localize>
</div>
</umb-box-content>
</umb-box>

View File

@@ -1,14 +1,30 @@
<umb-box>
<umb-box-content>
<h3 class="bold">Start here</h3>
<p>This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section:</p>
<h5>Find out more:</h5>
<h3 class="bold">
<localize key="settingsDashboard_start">Start here</localize>
</h3>
<localize key="settingsDashboard_startDescription">
<p>This section contains the building blocks for your Umbraco site. Follow the below links to find out more about working with the items in the Settings section:</p>
</localize>:
<h5>
<localize key="settingsDashboard_more">Find out more</localize>:
</h5>
<ul>
<li>Read more about working with the items in Settings <a class="btn-link -underline" href="https://our.umbraco.com/documentation/Getting-Started/Backoffice/Sections/" target="_blank">in the Documentation section</a> of Our Umbraco</li>
<li>Ask a question in the <a class="btn-link -underline" href="https://our.umbraco.com/forum" target="_blank">Community Forum</a></li>
<li>Watch our <a class="btn-link -underline" href="https://umbraco.tv" target="_blank">tutorial videos</a> (some are free, some require a subscription)</li>
<li>Find out about our <a class="btn-link -underline" href="https://umbraco.com/products/" target="_blank">productivity boosting tools and commercial support</a></li>
<li>Find out about real-life <a class="btn-link -underline" href="https://umbraco.com/training/" target="_blank">training and certification</a> opportunities</li>
<li>
<localize key="settingsDashboard_bulletPointOne">Read more about working with the items in Settings <a class="btn-link -underline" href="https://our.umbraco.com/documentation/Getting-Started/Backoffice/Sections/" target="_blank">in the Documentation section</a> of Our Umbraco</localize>
</li>
<li>
<localize key="settingsDashboard_bulletPointTwo">Ask a question in the <a class="btn-link -underline" href="https://our.umbraco.com/forum" target="_blank">Community Forum</a></localize>
</li>
<li>
<localize key="settingsDashboard_bulletPointThree">Watch our <a class="btn-link -underline" href="https://umbraco.tv" target="_blank">tutorial videos</a> (some are free, some require a subscription)</localize>
</li>
<li>
<localize key="settingsDashboard_bulletPointFour">Find out about our <a class="btn-link -underline" href="https://umbraco.com/products/" target="_blank">productivity boosting tools and commercial support</a></localize>
</li>
<li>
<localize key="settingsDashboard_bulletPointFive">Find out about real-life <a class="btn-link -underline" href="https://umbraco.com/training/" target="_blank">training and certification</a> opportunities</localize>
</li>
</ul>
</umb-box-content>
</umb-box>

View File

@@ -1,12 +1,18 @@
<h3>Hours of Umbraco training videos are only a click away</h3>
<p>Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit <a href="http://umbraco.tv" target="_blank">umbraco.tv</a> for even more Umbraco videos</p>
<h3>
<localize key="settingsDashboardVideos_trainingHeadline">Hours of Umbraco training videos are only a click away</localize>
</h3>
<localize key="settingsDashboardVideos_trainingDescription">
<p>Want to master Umbraco? Spend a couple of minutes learning some best practices by watching one of these videos about using Umbraco. And visit <a href="http://umbraco.tv" target="_blank">umbraco.tv</a> for even more Umbraco videos</p>
</localize>
<div class="row-fluid"
ng-init="init('https://umbraco.tv/videos/implementor/chapterrss?sort=no')"
ng-controller="Umbraco.Dashboard.StartupVideosController">
<div ng-show="videos.length">
<h4>To get you started:</h4>
<h4>
<localize key="settingsDashboardVideos_getStarted">To get you started</localize>:
</h4>
<ul class="thumbnails" >
<li class="span2" ng-repeat="video in videos">
<div class="thumbnail" style="margin-right: 20px">

View File

@@ -51,6 +51,12 @@ function DataTypeDeleteController($scope, dataTypeResource, treeService, navigat
navigationService.hideDialog();
};
vm.onReferenceClicked = function(event) {
if (event.metaKey !== true) {
navigationService.hideDialog();
}
};
vm.labels = {};
localizationService
.localize("editdatatype_acceptDeleteConsequence", [$scope.currentNode.name])

View File

@@ -24,10 +24,6 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
alias: "selectedEditor",
description: "Select a property editor",
label: "Property editor"
},
selectedEditorId: {
alias: "selectedEditorId",
label: "Property editor alias"
}
};
@@ -205,7 +201,7 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
var labelKeys = [
"general_settings",
"references_tabName"
"general_info"
];
localizationService.localizeMany(labelKeys).then(function (values) {
@@ -220,9 +216,9 @@ function DataTypeEditController($scope, $routeParams, appState, navigationServic
},
{
"name": values[1],
"alias": "references",
"icon": "icon-molecular-network",
"view": "views/datatypes/views/datatype.references.html"
"alias": "info",
"icon": "icon-info",
"view": "views/datatypes/views/datatype.info.html"
}
];
});

View File

@@ -1,4 +1,4 @@
<div class="umb-dialog umb-pane" ng-controller="Umbraco.Editors.DataType.DeleteController as vm">
<div class="umb-dialog umb-pane umb-dialog-datatype-delete" ng-controller="Umbraco.Editors.DataType.DeleteController as vm">
<div class="umb-dialog-body">
<ng-switch on="currentNode.nodeType">
@@ -44,14 +44,14 @@
<table class="table table-condensed table-bordered">
<thead>
<tr>
<th><localize key="general_name">Name</localize></th>
<th class="umb-dialog-datatype-delete__table-head-column-name"><localize key="general_name">Name</localize></th>
<th><localize key="general_properties">Properties</localize></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="relation in vm.references.documentTypes">
<td><span title="{{::relation.name}}({{::relation.alias}})"><i class="umb-table-body__icon {{relation.icon}}"></i>{{::relation.name}}</span></td>
<td><span><span class="red" ng-repeat="property in relation.properties"><i class="icon icon-alert red"></i>{{::property.name}}{{$last ? '' : ', '}}</span></span></td>
<td><a title="{{::relation.name}}({{::relation.alias}})" href="#/settings/documentTypes/edit/{{::relation.id}}" ng-click="vm.onReferenceClicked($event)"><i class="umb-table-body__icon {{relation.icon}}"></i>{{::relation.name}}</a></td>
<td><div><p class="red" ng-repeat="property in relation.properties"><i class="icon icon-alert red"></i>{{::property.name}}</p></div></td>
</tr>
</tbody>
</table>
@@ -68,14 +68,14 @@
<table class="table table-condensed table-bordered">
<thead>
<tr>
<th><localize key="general_name">Name</localize></th>
<th class="umb-dialog-datatype-delete__table-head-column-name"><localize key="general_name">Name</localize></th>
<th><localize key="general_properties">Properties</localize></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="relation in vm.references.mediaTypes">
<td><span title="{{::relation.name}}({{::relation.alias}})"><i class="umb-table-body__icon {{relation.icon}}"></i>{{::relation.name}}</span></td>
<td><span><span class="red" ng-repeat="property in relation.properties"><i class="icon icon-alert red"></i>{{::property.name}}{{$last ? '' : ', '}}</span></span></td>
<td><a title="{{::relation.name}}({{::relation.alias}})" href="#/settings/documentTypes/edit/{{::relation.id}}" ng-click="vm.onReferenceClicked($event)"><i class="umb-table-body__icon {{relation.icon}}"></i>{{::relation.name}}</a></td>
<td><div><p class="red" ng-repeat="property in relation.properties"><i class="icon icon-alert red"></i>{{::property.name}}</p></div></td>
</tr>
</tbody>
</table>
@@ -92,14 +92,14 @@
<table class="table table-condensed table-bordered">
<thead>
<tr>
<th><localize key="general_name">Name</localize></th>
<th class="umb-dialog-datatype-delete__table-head-column-name"><localize key="general_name">Name</localize></th>
<th><localize key="general_properties">Properties</localize></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="relation in vm.references.memberTypes">
<td><span title="{{::relation.name}}({{::relation.alias}})"><i class="umb-table-body__icon {{relation.icon}}"></i>{{::relation.name}}</span></td>
<td><span><span class="red" ng-repeat="property in relation.properties"><i class="icon icon-alert red"></i>{{::property.name}}{{$last ? '' : ', '}}</span></span></td>
<td><a title="{{::relation.name}}({{::relation.alias}})" href="#/settings/documentTypes/edit/{{::relation.id}}" ng-click="vm.onReferenceClicked($event)"><i class="umb-table-body__icon {{relation.icon}}"></i>{{::relation.name}}</a></td>
<td><div><p class="red" ng-repeat="property in relation.properties"><i class="icon icon-alert red"></i>{{::property.name}}</p></div></td>
</tr>
</tbody>
</table>

View File

@@ -1,12 +1,12 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.DataType.ReferencesController
* @name Umbraco.Editors.DataType.InfoController
* @function
*
* @description
* The controller for the references view of the datatype editor
* The controller for the info view of the datatype editor
*/
function DataTypeReferencesController($scope, $routeParams, dataTypeResource, eventsService, $timeout) {
function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsService, $timeout) {
var vm = this;
var evts = [];
@@ -34,7 +34,7 @@ function DataTypeReferencesController($scope, $routeParams, dataTypeResource, ev
// load data type references when the references tab is activated
evts.push(eventsService.on("app.tabChange", function (event, args) {
$timeout(function () {
if (args.alias === "references") {
if (args.alias === "info") {
loadRelations();
}
});
@@ -52,4 +52,4 @@ function DataTypeReferencesController($scope, $routeParams, dataTypeResource, ev
}
angular.module("umbraco").controller("Umbraco.Editors.DataType.ReferencesController", DataTypeReferencesController);
angular.module("umbraco").controller("Umbraco.Editors.DataType.InfoController", DataTypeInfoController);

View File

@@ -0,0 +1,140 @@
<div ng-controller="Umbraco.Editors.DataType.InfoController as vm">
<div class="umb-package-details">
<div class="umb-package-details__main-content">
<umb-load-indicator ng-if="vm.view.loading === true"></umb-load-indicator>
<umb-box ng-if="vm.view.loading === false && vm.hasReferences === false">
<umb-box-header title-key="references_tabName"></umb-box-header>
<umb-box-content>
<umb-empty-state size="small">
<localize key="references_DataTypeNoReferences">This Data Type has no references.</localize>
</umb-empty-state>
</umb-box-content>
</umb-box>
<div ng-if="vm.view.loading === false && vm.hasReferences === true">
<!-- Document Types -->
<div ng-if="vm.references.documentTypes.length > 0" class="mb4">
<h5 class="mt0" style="margin-bottom: 20px;">
<localize key="references_labelUsedByDocumentTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.documentTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/documentTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
<!-- Media Types -->
<div ng-if="vm.references.mediaTypes.length > 0" class="mb4">
<h5 class="mt0" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMediaTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.mediaTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/mediaTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
<!-- Member Types -->
<div ng-if="vm.references.memberTypes.length > 0" class="mb4">
<h5 class="mt0" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMemberTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.memberTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/memberTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="umb-package-details__sidebar">
<umb-box data-element="node-info-general">
<umb-box-header title-key="general_general"></umb-box-header>
<umb-box-content class="block-form">
<umb-control-group label="Id" ng-if="model.showIdentifier">
<div>{{model.content.id}}</div>
<small>{{model.content.key}}</small>
</umb-control-group>
<umb-control-group label="Property Editor Alias" ng-if="model.content.selectedEditor">
<div>{{model.content.selectedEditor}}</div>
</umb-control-group>
</umb-box-content>
</umb-box>
</div>
</div>
</div>

View File

@@ -1,112 +0,0 @@
<div ng-controller="Umbraco.Editors.DataType.ReferencesController as vm">
<umb-load-indicator ng-if="vm.view.loading === true"></umb-load-indicator>
<umb-box ng-if="vm.view.loading === false && vm.hasReferences === false">
<umb-box-header title-key="references_tabName"></umb-box-header>
<umb-box-content>
<umb-empty-state size="small">
<localize key="references_DataTypeNoReferences">This Data Type has no references.</localize>
</umb-empty-state>
</umb-box-content>
</umb-box>
<div ng-if="vm.view.loading === false && vm.hasReferences === true">
<!-- Document Types -->
<div ng-if="vm.references.documentTypes.length > 0">
<h5 class="mt4" style="margin-bottom: 20px;">
<localize key="references_labelUsedByDocumentTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.documentTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/documentTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
<!-- Media Types -->
<div ng-if="vm.references.mediaTypes.length > 0">
<h5 class="mt4" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMediaTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.mediaTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/documentTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
<!-- Member Types -->
<div ng-if="vm.references.memberTypes.length > 0">
<h5 class="mt4" style="margin-bottom: 20px;">
<localize key="references_labelUsedByMemberTypes"></localize>
</h5>
<div class="umb-table">
<div class="umb-table-head">
<div class="umb-table-row">
<div class="umb-table-cell"></div>
<div class="umb-table-cell umb-table__name not-fixed"><localize key="general_name">Name</localize></div>
<div class="umb-table-cell"><localize key="content_alias">Alias</localize></div>
<div class="umb-table-cell"><localize key="references_usedByProperties">Used in</localize></div>
<div class="umb-table-cell umb-table-cell--nano"><localize key="general_open" style="visibility:hidden;">Open</localize></div>
</div>
</div>
<div class="umb-table-body">
<div class="umb-table-row" ng-repeat="reference in vm.references.memberTypes">
<div class="umb-table-cell"><i class="umb-table-body__icon {{reference.icon}}"></i></div>
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/documentTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -2,11 +2,6 @@
<umb-box-content>
<umb-control-group label="Id" ng-if="model.showIdentifier">
<div>{{model.content.id}}</div>
<small>{{model.content.key}}</small>
</umb-control-group>
<umb-property property="model.properties.selectedEditor">
<div>
<select name="selectedEditor"
@@ -20,10 +15,6 @@
</umb-property>
<umb-property property="model.properties.selectedEditorId">
<div>{{model.content.selectedEditor}}</div>
</umb-property>
<umb-property property="preValue"
ng-repeat="preValue in model.preValues">
<umb-property-editor model="preValue" is-pre-value="true"></umb-property-editor>
@@ -31,4 +22,4 @@
</umb-box-content>
</umb-box>
</umb-box>

View File

@@ -54,7 +54,7 @@
</div>
<umb-control-group label="Enter a folder name" hide-label="false">
<input type="text" name="folderName" ng-model="model.folderName" class="umb-textstring textstring input-block-level" umb-auto-focus required />
<input type="text" name="folderName" maxlength="255" ng-model="model.folderName" class="umb-textstring textstring input-block-level" umb-auto-focus required />
</umb-control-group>
<button type="submit" class="btn btn-primary"><localize key="general_create">Create</localize></button>
@@ -81,7 +81,7 @@
<umb-control-group>
<label for="collectionName" class="control-label">Name of the Parent Document Type</label>
<input type="text" name="collectionName" id="collectionName" ng-model="model.collectionName" class="umb-textstring textstring input-block-level" umb-auto-focus required />
<input type="text" name="collectionName" id="collectionName" ng-model="model.collectionName" maxlength="255" class="umb-textstring textstring input-block-level" umb-auto-focus required />
<umb-checkbox ng-if="model.disableTemplates === false"
name="collectionCreateTemplate"
@@ -91,7 +91,7 @@
<umb-control-group>
<label for="collectionItemName" class="control-label">Name of the Item Document Type</label>
<input type="text" name="collectionItemName" id="collectionItemName" ng-model="model.collectionItemName" class="umb-textstring textstring input-block-level" required />
<input type="text" name="collectionItemName" id="collectionItemName" ng-model="model.collectionItemName" maxlength="255" class="umb-textstring textstring input-block-level" required />
<umb-checkbox ng-if="model.disableTemplates === false"
name="collectionItemCreateTemplate"

View File

@@ -18,6 +18,8 @@
var documentTypeId = $routeParams.id;
var create = $routeParams.create;
var noTemplate = $routeParams.notemplate;
var isElement = $routeParams.iselement;
var allowVaryByCulture = $routeParams.culturevary;
var infiniteMode = $scope.model && $scope.model.infiniteMode;
vm.save = save;
@@ -63,6 +65,8 @@
documentTypeId = $scope.model.id;
create = $scope.model.create;
noTemplate = $scope.model.notemplate;
isElement = $scope.model.isElement;
allowVaryByCulture = $scope.model.allowVaryByCulture;
vm.submitButtonKey = "buttons_saveAndClose";
vm.generateModelsKey = "buttons_generateModelsAndClose";
}
@@ -430,7 +434,14 @@
contentType.defaultTemplate = contentTypeHelper.insertDefaultTemplatePlaceholder(contentType.defaultTemplate);
contentType.allowedTemplates = contentTypeHelper.insertTemplatePlaceholder(contentType.allowedTemplates);
}
// set isElement checkbox by default
if (isElement) {
contentType.isElement = true;
}
// set vary by culture checkbox by default
if (allowVaryByCulture) {
contentType.allowCultureVariant = true;
}
// convert icons for content type
convertLegacyIcons(contentType);
@@ -508,11 +519,16 @@
}));
evts.push(eventsService.on("editors.documentType.saved", function(name, args) {
if(args.documentType.allowedTemplates.length > 0){
navigationService.syncTree({ tree: "templates", path: [], forceReload: true })
.then(function (syncArgs) {
navigationService.reloadNode(syncArgs.node)
});
if(args.documentType.allowedTemplates.length > 0) {
navigationService.hasTree("templates").then(function (treeExists) {
if (treeExists) {
navigationService.syncTree({ tree: "templates", path: [], forceReload: true })
.then(function (syncArgs) {
navigationService.reloadNode(syncArgs.node)
}
);
}
});
}
}));

View File

@@ -58,7 +58,9 @@
</td>
<td>
<span ng-if="language.fallbackLanguageId">{{vm.getLanguageById(language.fallbackLanguageId).name}}</span>
<span ng-if="!language.fallbackLanguageId">(none)</span>
<span ng-if="!language.fallbackLanguageId">
(<localize key="languages_none">none</localize>)
</span>
</td>
<td style="text-align: right;">
<umb-button ng-if="!language.isDefault"

View File

@@ -20,16 +20,16 @@
<div class="umb-sortable-thumbnails__actions" data-element="sortable-thumbnail-actions">
<a role="button" aria-label="Remove" class="umb-sortable-thumbnails__action -red" data-element="action-remove" ng-click="remove()">
<i class="icon icon-delete"></i>
</a>
<button aria-label="Remove" class="umb-sortable-thumbnails__action -red btn-reset" data-element="action-remove" ng-click="remove()">
<i class="icon icon-delete" aria-hidden="true"></i>
</button>
</div>
</li>
<li style="border: none;" class="add-wrapper unsortable" ng-hide="model.value">
<a role="button" aria-label="Open media picker" data-element="sortable-thumbnails-add" class="add-link add-link-square" ng-click="add()" prevent-default>
<i class="icon icon-add large"></i>
</a>
<button aria-label="Open media picker" data-element="sortable-thumbnails-add" class="add-link add-link-square btn-reset" ng-click="add()" prevent-default>
<i class="icon icon-add large" aria-hidden="true"></i>
</button>
</li>
</ul>
</div>

View File

@@ -71,7 +71,7 @@ function ColorPickerController($scope, $timeout) {
);
return {
isValid: isValid,
errorMsg: "Value cannot be empty",
errorMsg: $scope.model.validation.mandatoryMessage || "Value cannot be empty",
errorKey: "required"
};
}

View File

@@ -1,4 +1,4 @@
function dateTimePickerController($scope, notificationsService, assetsService, angularHelper, userService, $element, dateHelper) {
function dateTimePickerController($scope, angularHelper, dateHelper, validationMessageService) {
let flatPickr = null;
@@ -62,6 +62,11 @@ function dateTimePickerController($scope, notificationsService, assetsService, a
setDatePickerVal();
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
}
$scope.clearDate = function() {

View File

@@ -32,11 +32,11 @@
</div>
<span ng-messages="datePickerForm.datepicker.$error" show-validation-on-submit >
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" ng-message="valServer">{{datePickerForm.datepicker.errorMsg}}</span>
<span class="help-inline" ng-message="pickerError"><localize key="validation_invalidDate">Invalid date</localize></span>
</span>
<div ng-messages="datePickerForm.datepicker.$error" show-validation-on-submit >
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
<p class="help-inline" ng-message="valServer">{{datePickerForm.datepicker.errorMsg}}</p>
<p class="help-inline" ng-message="pickerError"><localize key="validation_invalidDate">Invalid date</localize></p>
</div>
<p ng-if="model.config.offsetTime === '1' && serverTimeNeedsOffsetting && model.value" class="muted">
<small><localize key="content_scheduledPublishServerTime">This translates to the following time on the server:</localize> {{serverTime}}</small><br />

View File

@@ -1,5 +1,5 @@
angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleController",
function($scope) {
function ($scope, validationMessageService) {
//setup the default config
var config = {
@@ -89,4 +89,10 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.DropdownFlexibleCo
$scope.model.value = null;
}
}
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
});

View File

@@ -1,21 +1,29 @@
<div ng-controller="Umbraco.PropertyEditors.DropdownFlexibleController" ng-switch="model.config.multiple">
<select name="dropDownList"
class="umb-property-editor umb-dropdown"
ng-switch-default
ng-change="updateSingleDropdownValue()"
ng-model="model.singleDropdownValue"
ng-options="item.value as item.value for item in model.config.items"
ng-required="model.validation.mandatory">
<option></option>
</select>
<ng-form name="dropDownListForm">
<select name="dropDownList"
class="umb-property-editor umb-dropdown"
ng-switch-default
ng-change="updateSingleDropdownValue()"
ng-model="model.singleDropdownValue"
ng-options="item.value as item.value for item in model.config.items"
ng-required="model.validation.mandatory">
<option></option>
</select>
<!--NOTE: This ng-switch is required because ng-multiple doesn't actually support dynamic bindings with multi-select lists -->
<select name="dropDownList"
class="umb-property-editor umb-dropdown"
ng-switch-when="true"
multiple
ng-model="model.value"
ng-options="item.value as item.value for item in model.config.items"
ng-required="model.validation.mandatory"></select>
<!--NOTE: This ng-switch is required because ng-multiple doesn't actually support dynamic bindings with multi-select lists -->
<select name="dropDownList"
class="umb-property-editor umb-dropdown"
ng-switch-when="true"
multiple
ng-model="model.value"
ng-options="item.value as item.value for item in model.config.items"
ng-required="model.validation.mandatory"></select>
<div ng-messages="dropDownListForm.dropDownList.$error" show-validation-on-submit>
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
</div>
</ng-form>
</div>

View File

@@ -0,0 +1,10 @@
function emailController($scope, validationMessageService) {
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function(value) {
$scope.mandatoryMessage = value;
});
}
angular.module('umbraco').controller("Umbraco.PropertyEditors.EmailController", emailController);

View File

@@ -1,4 +1,4 @@
<div>
<div ng-controller="Umbraco.PropertyEditors.EmailController">
<ng-form name="emailFieldForm">
<input type="email" name="textbox"
ng-model="model.value"
@@ -8,10 +8,10 @@
ng-required="model.config.IsRequired || model.validation.mandatory"
val-server="value" />
<span ng-messages="emailFieldForm.textbox.$error" show-validation-on-submit >
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" ng-message="valEmail"><localize key="validation_invalidEmail">Invalid email</localize></span>
<span class="help-inline" ng-message="valServer">{{emailFieldForm.textbox.errorMsg}}</span>
</span>
<div ng-messages="emailFieldForm.textbox.$error" show-validation-on-submit >
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
<p class="help-inline" ng-message="valEmail"><localize key="validation_invalidEmail">Invalid email</localize></p>
<p class="help-inline" ng-message="valServer">{{emailFieldForm.textbox.errorMsg}}</p>
</div>
</ng-form>
</div>

View File

@@ -73,7 +73,10 @@ angular.module("umbraco")
ui.item.find(".umb-rte").each(function (key, value) {
// remove all RTEs in the dragged row and save their settings
var rteId = value.id;
draggedRteSettings[rteId] = _.findWhere(tinyMCE.editors, { id: rteId }).settings;
var editor = _.findWhere(tinyMCE.editors, { id: rteId });
if (editor) {
draggedRteSettings[rteId] = editor.settings;
}
});
},
@@ -85,9 +88,17 @@ angular.module("umbraco")
// reset all RTEs affected by the dragging
ui.item.parents(".umb-column").find(".umb-rte").each(function (key, value) {
var rteId = value.id;
draggedRteSettings[rteId] = draggedRteSettings[rteId] || _.findWhere(tinyMCE.editors, { id: rteId }).settings;
tinyMCE.execCommand("mceRemoveEditor", false, rteId);
tinyMCE.init(draggedRteSettings[rteId]);
var settings = draggedRteSettings[rteId];
if (!settings) {
var editor = _.findWhere(tinyMCE.editors, { id: rteId });
if (editor) {
settings = editor.settings;
}
}
if (settings) {
tinyMCE.execCommand("mceRemoveEditor", false, rteId);
tinyMCE.init(settings);
}
});
currentForm.$setDirty();
}
@@ -662,6 +673,7 @@ angular.module("umbraco")
return ((spans / $scope.model.config.items.columns) * 100).toFixed(8);
};
$scope.clearPrompt = function (scopedObject, e) {
scopedObject.deletePrompt = false;
e.preventDefault();
@@ -681,8 +693,15 @@ angular.module("umbraco")
};
$scope.getTemplateName = function (control) {
if (control.editor.nameExp) return control.editor.nameExp(control)
return control.editor.name;
var templateName = control.editor.name;
if (control.editor.nameExp) {
var valueOfTemplate = control.editor.nameExp(control);
if (valueOfTemplate != "") {
templateName += ": ";
templateName += valueOfTemplate;
}
}
return templateName;
}
// *********************************************

View File

@@ -95,7 +95,7 @@
</div>
<div class="cell-tools-remove row-tool">
<i class="icon-trash" ng-click="togglePrompt(row)"></i>
<i class="icon-trash" ng-click="togglePrompt(row)" localize="title" title="@delete"></i>
<umb-confirm-action
ng-if="row.deletePrompt"
direction="left"
@@ -200,7 +200,7 @@
</div>
<div class="umb-control-tool">
<i class="umb-control-tool-icon icon-trash" ng-click="togglePrompt(control)"></i>
<i class="umb-control-tool-icon icon-trash" ng-click="togglePrompt(control)" localize="title" title="@delete"></i>
<umb-confirm-action ng-if="control.deletePrompt"
direction="left"
on-confirm="removeControl(area, $index)"

View File

@@ -36,18 +36,18 @@
</umb-file-icon>
<div class="umb-sortable-thumbnails__actions" data-element="sortable-thumbnail-actions">
<a role="button" aria-label="Edit media" ng-if="allowEditMedia" class="umb-sortable-thumbnails__action" data-element="action-edit" ng-click="vm.editItem(media)">
<i class="icon icon-edit"></i>
</a>
<a role="button" aria-label="Remove" class="umb-sortable-thumbnails__action -red" data-element="action-remove" ng-click="vm.remove($index)">
<i class="icon icon-delete"></i>
</a>
<button aria-label="Edit media" ng-if="allowEditMedia" class="umb-sortable-thumbnails__action btn-reset" data-element="action-edit" ng-click="vm.editItem(media)">
<i class="icon icon-edit" aria-hidden="true"></i>
</button>
<button aria-label="Remove" class="umb-sortable-thumbnails__action -red btn-reset" data-element="action-remove" ng-click="vm.remove($index)">
<i class="icon icon-delete" aria-hidden="true"></i>
</button>
</div>
</li>
<li style="border: none;" class="add-wrapper unsortable" ng-if="vm.showAdd() && allowAddMedia">
<a role="button" aria-label="Open media picker" data-element="sortable-thumbnails-add" class="add-link" ng-click="vm.add()" ng-class="{'add-link-square': (mediaItems.length === 0 || isMultiPicker)}" prevent-default>
<i class="icon icon-add large"></i>
</a>
<button aria-label="Open media picker" data-element="sortable-thumbnails-add" class="add-link btn-reset" ng-click="vm.add()" ng-class="{'add-link-square': (mediaItems.length === 0 || isMultiPicker)}" prevent-default>
<i class="icon icon-add large" aria-hidden="true"></i>
</button>
</li>
</ul>
</div>

View File

@@ -1,323 +1,246 @@
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [
(function () {
'use strict';
"$scope",
"Umbraco.PropertyEditors.NestedContent.Resources",
"overlayService",
"localizationService",
"iconHelper",
function ($scope, ncResources, overlayService, localizationService, iconHelper) {
var selectElementTypeModalTitle = "";
$scope.elemTypeTabs = [];
init();
function init() {
localizationService.localize("content_nestedContentSelectElementTypeModalTitle").then(function (value) {
selectElementTypeModalTitle = value;
});
ncResources.getContentTypes().then(function (elemTypes) {
$scope.model.elemTypes = elemTypes;
// convert legacy icons
iconHelper.formatContentTypeIcons($scope.model.elemTypes);
// Count doctype name occurrences
var elTypeNameOccurrences= _.countBy(elemTypes, 'name');
// Populate document type tab dictionary
// And append alias to name if multiple doctypes have the same name
elemTypes.forEach(function (value) {
$scope.elemTypeTabs[value.alias] = value.tabs;
if (elTypeNameOccurrences[value.name] > 1) {
value.name += " (" + value.alias + ")";
}
});
});
}
$scope.add = function () {
$scope.model.value.push({
// As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
// For good measure we'll also prefix the tab alias "nc"
ncAlias: "",
ncTabAlias: "",
nameTemplate: ""
});
}
$scope.canAdd = function () {
return !$scope.model.docTypes || !$scope.model.value || $scope.model.value.length < $scope.model.docTypes.length;
}
$scope.remove = function (index) {
$scope.model.value.splice(index, 1);
}
$scope.sortableOptions = {
axis: "y",
cursor: "move",
handle: ".handle",
placeholder: 'sortable-placeholder',
forcePlaceholderSize: true,
helper: function (e, ui) {
// When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
ui.children().each(function () {
$(this).width($(this).width());
});
return ui;
},
start: function (e, ui) {
var cellHeight = ui.item.height();
// Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size
var cellCount = 0;
$('td, th', ui.helper).each(function () {
// For each td or th try and get it's colspan attribute, and add that or 1 to the total
var colspan = 1;
var colspanAttr = $(this).attr('colspan');
if (colspanAttr > 1) {
colspan = colspanAttr;
}
cellCount += colspan;
});
// Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr
ui.placeholder.html('<td colspan="' + cellCount + '"></td>').height(cellHeight);
angular
.module('umbraco')
.component('nestedContentPropertyEditor', {
templateUrl: 'views/propertyeditors/nestedcontent/nestedcontent.propertyeditor.html',
controller: NestedContentController,
controllerAs: 'vm',
require: {
umbProperty: '?^umbProperty',
umbVariantContent: '?^^umbVariantContent'
}
};
});
$scope.placeholder = function (config) {
return _.find($scope.model.elemTypes, function (elType) {
return elType.alias === config.ncAlias;
});
}
$scope.selectableElemTypesFor = function (config) {
// return all elemTypes that are:
// 1. either already selected for this config, or
// 2. not selected in any other config
return _.filter($scope.model.elemTypes, function (elType) {
return elType.alias === config.ncAlias || !_.find($scope.model.value, function (c) {
return elType.alias === c.ncAlias;
});
});
}
$scope.canAdd = function () {
return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) {
return !_.find($scope.model.value, function (c) {
return elType.alias === c.ncAlias;
});
});
}
$scope.openElemTypeModal = function ($event, config) {
//we have to add the alias to the objects (they are stored as ncAlias)
var selectedItems = _.each($scope.model.value, function (obj) {
obj.alias = obj.ncAlias;
return obj;
})
var elemTypeSelectorOverlay = {
view: "itempicker",
title: selectElementTypeModalTitle,
availableItems: $scope.selectableElemTypesFor(config),
selectedItems: selectedItems,
position: "target",
event: $event,
submit: function (model) {
config.ncAlias = model.selectedItem.alias;
overlayService.close();
},
close: function () {
overlayService.close();
}
};
overlayService.open(elemTypeSelectorOverlay);
}
if (!$scope.model.value) {
$scope.model.value = [];
$scope.add();
}
}
]);
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.PropertyEditorController", [
"$scope",
"$interpolate",
"$filter",
"$timeout",
"contentResource",
"localizationService",
"iconHelper",
"clipboardService",
"eventsService",
"overlayService",
function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState) {
function NestedContentController($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService, overlayService, $routeParams, editorState) {
var vm = this;
var model = $scope.$parent.$parent.model;
var contentTypeAliases = [];
_.each($scope.model.config.contentTypes, function (contentType) {
_.each(model.config.contentTypes, function (contentType) {
contentTypeAliases.push(contentType.ncAlias);
});
_.each($scope.model.config.contentTypes, function (contentType) {
_.each(model.config.contentTypes, function (contentType) {
contentType.nameExp = !!contentType.nameTemplate
? $interpolate(contentType.nameTemplate)
: undefined;
});
$scope.nodes = [];
$scope.currentNode = undefined;
$scope.realCurrentNode = undefined;
$scope.scaffolds = undefined;
$scope.sorting = false;
$scope.inited = false;
vm.nodes = [];
vm.currentNode = null;
vm.scaffolds = null;
vm.sorting = false;
vm.inited = false;
$scope.minItems = $scope.model.config.minItems || 0;
$scope.maxItems = $scope.model.config.maxItems || 0;
vm.minItems = model.config.minItems || 0;
vm.maxItems = model.config.maxItems || 0;
if ($scope.maxItems === 0)
$scope.maxItems = 1000;
if (vm.maxItems === 0)
vm.maxItems = 1000;
$scope.singleMode = $scope.minItems === 1 && $scope.maxItems === 1;
$scope.showIcons = Object.toBoolean($scope.model.config.showIcons);
$scope.wideMode = Object.toBoolean($scope.model.config.hideLabel);
$scope.hasContentTypes = $scope.model.config.contentTypes.length > 0;
vm.singleMode = vm.minItems === 1 && vm.maxItems === 1;
vm.showIcons = Object.toBoolean(model.config.showIcons);
vm.wideMode = Object.toBoolean(model.config.hideLabel);
vm.hasContentTypes = model.config.contentTypes.length > 0;
$scope.labels = {};
localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function (data) {
$scope.labels.grid_addElement = data[0];
$scope.labels.content_createEmpty = data[1];
var labels = {};
vm.labels = labels;
localizationService.localizeMany(["grid_addElement", "content_createEmpty", "actions_copy"]).then(function (data) {
labels.grid_addElement = data[0];
labels.content_createEmpty = data[1];
labels.copy_icon_title = data[2]
});
function setCurrentNode(node) {
vm.currentNode = node;
updateModel();
}
var copyAllEntries = function() {
syncCurrentNode();
// list aliases
var aliases = vm.nodes.map((node) => node.contentTypeAlias);
// remove dublicates
aliases = aliases.filter((item, index) => aliases.indexOf(item) === index);
var nodeName = "";
if(vm.umbVariantContent) {
nodeName = vm.umbVariantContent.editor.content.name;
}
localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, nodeName]).then(function(data) {
clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id);
});
}
var copyAllEntriesAction = {
labelKey: 'clipboard_labelForCopyAllEntries',
labelTokens: [model.label],
icon: 'documents',
method: copyAllEntries,
isDisabled: true
}
var removeAllEntries = function () {
localizationService.localizeMany(["content_nestedContentDeleteAllItems", "general_delete"]).then(function (data) {
overlayService.confirmDelete({
title: data[1],
content: data[0],
close: function () {
overlayService.close();
},
submit: function () {
vm.nodes = [];
setDirty();
updateModel();
overlayService.close();
}
});
});
}
var removeAllEntriesAction = {
labelKey: 'clipboard_labelForRemoveAllEntries',
labelTokens: [],
icon: 'trash',
method: removeAllEntries,
isDisabled: true
}
// helper to force the current form into the dirty state
$scope.setDirty = function () {
if ($scope.propertyForm) {
$scope.propertyForm.$setDirty();
function setDirty() {
if ($scope.$parent.$parent.propertyForm) {
$scope.$parent.$parent.propertyForm.$setDirty();
}
};
$scope.addNode = function (alias) {
var scaffold = $scope.getScaffold(alias);
function addNode(alias) {
var scaffold = getScaffold(alias);
var newNode = createNode(scaffold, null);
$scope.currentNode = newNode;
$scope.setDirty();
setCurrentNode(newNode);
setDirty();
};
$scope.openNodeTypePicker = function ($event) {
if ($scope.nodes.length >= $scope.maxItems) {
vm.openNodeTypePicker = function ($event) {
if (vm.nodes.length >= vm.maxItems) {
return;
}
$scope.overlayMenu = {
vm.overlayMenu = {
show: false,
style: {},
filter: $scope.scaffolds.length > 12 ? true : false,
filter: vm.scaffolds.length > 12 ? true : false,
orderBy: "$index",
view: "itempicker",
event: $event,
clickPasteItem: function(item) {
$scope.pasteFromClipboard(item.data);
$scope.overlayMenu.show = false;
$scope.overlayMenu = null;
if (item.type === "elementTypeArray") {
_.each(item.data, function (entry) {
pasteFromClipboard(entry);
});
} else {
pasteFromClipboard(item.data);
}
vm.overlayMenu.show = false;
vm.overlayMenu = null;
},
submit: function (model) {
if (model && model.selectedItem) {
$scope.addNode(model.selectedItem.alias);
addNode(model.selectedItem.alias);
}
$scope.overlayMenu.show = false;
$scope.overlayMenu = null;
vm.overlayMenu.show = false;
vm.overlayMenu = null;
},
close: function () {
$scope.overlayMenu.show = false;
$scope.overlayMenu = null;
vm.overlayMenu.show = false;
vm.overlayMenu = null;
}
};
// this could be used for future limiting on node types
$scope.overlayMenu.availableItems = [];
_.each($scope.scaffolds, function (scaffold) {
$scope.overlayMenu.availableItems.push({
vm.overlayMenu.availableItems = [];
_.each(vm.scaffolds, function (scaffold) {
vm.overlayMenu.availableItems.push({
alias: scaffold.contentTypeAlias,
name: scaffold.contentTypeName,
icon: iconHelper.convertFromLegacyIcon(scaffold.icon)
});
});
if ($scope.overlayMenu.availableItems.length === 0) {
if (vm.overlayMenu.availableItems.length === 0) {
return;
}
$scope.overlayMenu.size = $scope.overlayMenu.availableItems.length > 6 ? "medium" : "small";
vm.overlayMenu.size = vm.overlayMenu.availableItems.length > 6 ? "medium" : "small";
$scope.overlayMenu.pasteItems = [];
var availableNodesForPaste = clipboardService.retriveDataOfType("elementType", contentTypeAliases);
_.each(availableNodesForPaste, function (node) {
$scope.overlayMenu.pasteItems.push({
alias: node.contentTypeAlias,
name: node.name, //contentTypeName
data: node,
icon: iconHelper.convertFromLegacyIcon(node.icon)
vm.overlayMenu.pasteItems = [];
var singleEntriesForPaste = clipboardService.retriveEntriesOfType("elementType", contentTypeAliases);
_.each(singleEntriesForPaste, function (entry) {
vm.overlayMenu.pasteItems.push({
type: "elementType",
name: entry.label,
data: entry.data,
icon: entry.icon
});
});
var arrayEntriesForPaste = clipboardService.retriveEntriesOfType("elementTypeArray", contentTypeAliases);
_.each(arrayEntriesForPaste, function (entry) {
vm.overlayMenu.pasteItems.push({
type: "elementTypeArray",
name: entry.label,
data: entry.data,
icon: entry.icon
});
});
$scope.overlayMenu.title = $scope.overlayMenu.pasteItems.length > 0 ? $scope.labels.grid_addElement : $scope.labels.content_createEmpty;
vm.overlayMenu.title = vm.overlayMenu.pasteItems.length > 0 ? labels.grid_addElement : labels.content_createEmpty;
$scope.overlayMenu.clickClearPaste = function ($event) {
vm.overlayMenu.clickClearPaste = function ($event) {
$event.stopPropagation();
$event.preventDefault();
clipboardService.clearEntriesOfType("elementType", contentTypeAliases);
$scope.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually.
clipboardService.clearEntriesOfType("elementTypeArray", contentTypeAliases);
vm.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually.
};
if ($scope.overlayMenu.availableItems.length === 1 && $scope.overlayMenu.pasteItems.length === 0) {
if (vm.overlayMenu.availableItems.length === 1 && vm.overlayMenu.pasteItems.length === 0) {
// only one scaffold type - no need to display the picker
$scope.addNode($scope.scaffolds[0].contentTypeAlias);
addNode(vm.scaffolds[0].contentTypeAlias);
return;
}
$scope.overlayMenu.show = true;
vm.overlayMenu.show = true;
};
$scope.editNode = function (idx) {
if ($scope.currentNode && $scope.currentNode.key === $scope.nodes[idx].key) {
$scope.currentNode = undefined;
vm.editNode = function (idx) {
if (vm.currentNode && vm.currentNode.key === vm.nodes[idx].key) {
setCurrentNode(null);
} else {
$scope.currentNode = $scope.nodes[idx];
setCurrentNode(vm.nodes[idx]);
}
};
$scope.deleteNode = function (idx) {
$scope.nodes.splice(idx, 1);
$scope.setDirty();
function deleteNode(idx) {
vm.nodes.splice(idx, 1);
setDirty();
updateModel();
};
$scope.requestDeleteNode = function (idx) {
if ($scope.nodes.length <= $scope.model.config.minItems) {
return;
}
if ($scope.model.config.confirmDeletes === true) {
vm.requestDeleteNode = function (idx) {
if (model.config.confirmDeletes === true) {
localizationService.localizeMany(["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]).then(function (data) {
const overlay = {
title: data[1],
@@ -329,7 +252,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
overlayService.close();
},
submit: function () {
$scope.deleteNode(idx);
deleteNode(idx);
overlayService.close();
}
};
@@ -337,23 +260,23 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
overlayService.open(overlay);
});
} else {
$scope.deleteNode(idx);
deleteNode(idx);
}
};
$scope.getName = function (idx) {
vm.getName = function (idx) {
var name = "";
if ($scope.model.value[idx]) {
if (model.value[idx]) {
var contentType = $scope.getContentTypeConfig($scope.model.value[idx].ncContentTypeAlias);
var contentType = getContentTypeConfig(model.value[idx].ncContentTypeAlias);
if (contentType != null) {
// first try getting a name using the configured label template
if (contentType.nameExp) {
// Run the expression against the stored dictionary value, NOT the node object
var item = $scope.model.value[idx];
var item = model.value[idx];
// Add a temporary index property
item["$index"] = (idx + 1);
@@ -368,8 +291,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
// if we still do not have a name and we have multiple content types to choose from, use the content type name (same as is shown in the content type picker)
if (!name && $scope.scaffolds.length > 1) {
var scaffold = $scope.getScaffold(contentType.ncAlias);
if (!name && vm.scaffolds.length > 1) {
var scaffold = getScaffold(contentType.ncAlias);
if (scaffold) {
name = scaffold.contentTypeName;
}
@@ -383,18 +306,19 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
// Update the nodes actual name value
if ($scope.nodes[idx].name !== name) {
$scope.nodes[idx].name = name;
if (vm.nodes[idx].name !== name) {
vm.nodes[idx].name = name;
}
return name;
};
$scope.getIcon = function (idx) {
var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
vm.getIcon = function (idx) {
var scaffold = getScaffold(model.value[idx].ncContentTypeAlias);
return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
}
$scope.sortableOptions = {
vm.sortableOptions = {
axis: "y",
cursor: "move",
handle: '.umb-nested-content__header-bar',
@@ -405,46 +329,45 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
start: function (ev, ui) {
updateModel();
// Yea, yea, we shouldn't modify the dom, sue me
$("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
$("#umb-nested-content--" + model.id + " .umb-rte textarea").each(function () {
tinymce.execCommand("mceRemoveEditor", false, $(this).attr("id"));
$(this).css("visibility", "hidden");
});
$scope.$apply(function () {
$scope.sorting = true;
vm.sorting = true;
});
},
update: function (ev, ui) {
$scope.setDirty();
setDirty();
},
stop: function (ev, ui) {
$("#umb-nested-content--" + $scope.model.id + " .umb-rte textarea").each(function () {
$("#umb-nested-content--" + model.id + " .umb-rte textarea").each(function () {
tinymce.execCommand("mceAddEditor", true, $(this).attr("id"));
$(this).css("visibility", "visible");
});
$scope.$apply(function () {
$scope.sorting = false;
vm.sorting = false;
updateModel();
});
}
};
$scope.getScaffold = function (alias) {
return _.find($scope.scaffolds, function (scaffold) {
function getScaffold(alias) {
return _.find(vm.scaffolds, function (scaffold) {
return scaffold.contentTypeAlias === alias;
});
}
$scope.getContentTypeConfig = function (alias) {
return _.find($scope.model.config.contentTypes, function (contentType) {
function getContentTypeConfig(alias) {
return _.find(model.config.contentTypes, function (contentType) {
return contentType.ncAlias === alias;
});
}
$scope.showCopy = clipboardService.isSupported();
vm.showCopy = clipboardService.isSupported();
vm.showPaste = false;
$scope.showPaste = false;
$scope.clickCopy = function ($event, node) {
vm.clickCopy = function ($event, node) {
syncCurrentNode();
@@ -452,7 +375,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
$event.stopPropagation();
}
$scope.pasteFromClipboard = function(newNode) {
function pasteFromClipboard(newNode) {
if (newNode === undefined) {
return;
@@ -461,15 +385,15 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
// generate a new key.
newNode.key = String.CreateGuid();
$scope.nodes.push(newNode);
$scope.setDirty();
vm.nodes.push(newNode);
setDirty();
//updateModel();// done by setting current node...
$scope.currentNode = newNode;
setCurrentNode(newNode);
}
function checkAbilityToPasteContent() {
$scope.showPaste = clipboardService.hasEntriesOfType("elementType", contentTypeAliases);
vm.showPaste = clipboardService.hasEntriesOfType("elementType", contentTypeAliases) || clipboardService.hasEntriesOfType("elementTypeArray", contentTypeAliases);
}
eventsService.on("clipboardService.storageUpdate", checkAbilityToPasteContent);
@@ -482,8 +406,8 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
// Initialize
var scaffoldsLoaded = 0;
$scope.scaffolds = [];
_.each($scope.model.config.contentTypes, function (contentType) {
vm.scaffolds = [];
_.each(model.config.contentTypes, function (contentType) {
contentResource.getScaffold(-20, contentType.ncAlias).then(function (scaffold) {
// make sure it's an element type before allowing the user to create new ones
if (scaffold.isElement) {
@@ -507,7 +431,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
// Store the scaffold object
$scope.scaffolds.push(scaffold);
vm.scaffolds.push(scaffold);
}
scaffoldsLoaded++;
@@ -520,22 +444,22 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
var initIfAllScaffoldsHaveLoaded = function () {
// Initialize when all scaffolds have loaded
if ($scope.model.config.contentTypes.length === scaffoldsLoaded) {
if (model.config.contentTypes.length === scaffoldsLoaded) {
// Because we're loading the scaffolds async one at a time, we need to
// sort them explicitly according to the sort order defined by the data type.
contentTypeAliases = [];
_.each($scope.model.config.contentTypes, function (contentType) {
_.each(model.config.contentTypes, function (contentType) {
contentTypeAliases.push(contentType.ncAlias);
});
$scope.scaffolds = $filter("orderBy")($scope.scaffolds, function (s) {
vm.scaffolds = $filter("orderBy")(vm.scaffolds, function (s) {
return contentTypeAliases.indexOf(s.contentTypeAlias);
});
// Convert stored nodes
if ($scope.model.value) {
for (var i = 0; i < $scope.model.value.length; i++) {
var item = $scope.model.value[i];
var scaffold = $scope.getScaffold(item.ncContentTypeAlias);
if (model.value) {
for (var i = 0; i < model.value.length; i++) {
var item = model.value[i];
var scaffold = getScaffold(item.ncContentTypeAlias);
if (scaffold == null) {
// No such scaffold - the content type might have been deleted. We need to skip it.
continue;
@@ -544,20 +468,21 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
}
// Enforce min items
if ($scope.nodes.length < $scope.model.config.minItems) {
for (var i = $scope.nodes.length; i < $scope.model.config.minItems; i++) {
$scope.addNode($scope.scaffolds[0].contentTypeAlias);
// Auto-fill with elementTypes, but only if we have one type to choose from, and if this property is empty.
if (vm.singleMode === true && vm.nodes.length === 0 && model.config.minItems > 0) {
for (var i = vm.nodes.length; i < model.config.minItems; i++) {
addNode(vm.scaffolds[0].contentTypeAlias);
}
}
// If there is only one item, set it as current node
if ($scope.singleMode || ($scope.nodes.length === 1 && $scope.maxItems === 1)) {
$scope.currentNode = $scope.nodes[0];
if (vm.singleMode || (vm.nodes.length === 1 && vm.maxItems === 1)) {
setCurrentNode(vm.nodes[0]);
}
$scope.inited = true;
vm.inited = true;
updatePropertyActionStates();
checkAbilityToPasteContent();
}
}
@@ -576,10 +501,11 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
var prop = tab.properties[p];
prop.propertyAlias = prop.alias;
prop.alias = $scope.model.alias + "___" + prop.alias;
prop.alias = model.alias + "___" + prop.alias;
// Force validation to occur server side as this is the
// only way we can have consistency between mandatory and
// regex validation messages. Not ideal, but it works.
prop.ncMandatory = prop.validation.mandatory;
prop.validation = {
mandatory: false,
pattern: ""
@@ -591,7 +517,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
}
$scope.nodes.push(node);
vm.nodes.push(node);
return node;
}
@@ -614,37 +540,78 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
return obj;
}
function syncCurrentNode() {
if ($scope.realCurrentNode) {
$scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key });
if (vm.currentNode) {
$scope.$broadcast("ncSyncVal", { key: vm.currentNode.key });
}
}
function updateModel() {
syncCurrentNode();
if ($scope.inited) {
if (vm.inited) {
var newValues = [];
for (var i = 0; i < $scope.nodes.length; i++) {
newValues.push(convertNodeIntoNCEntry($scope.nodes[i]));
for (var i = 0; i < vm.nodes.length; i++) {
newValues.push(convertNodeIntoNCEntry(vm.nodes[i]));
}
$scope.model.value = newValues;
model.value = newValues;
}
updatePropertyActionStates();
}
$scope.$watch("currentNode", function (newVal) {
updateModel();
$scope.realCurrentNode = newVal;
});
function updatePropertyActionStates() {
copyAllEntriesAction.isDisabled = !model.value || model.value.length === 0;
removeAllEntriesAction.isDisabled = !model.value || model.value.length === 0;
}
var propertyActions = [
copyAllEntriesAction,
removeAllEntriesAction
];
this.$onInit = function () {
if (this.umbProperty) {
this.umbProperty.setPropertyActions(propertyActions);
}
};
var unsubscribe = $scope.$on("formSubmitting", function (ev, args) {
updateModel();
});
var watcher = $scope.$watch(
function () {
return vm.nodes.length;
},
function () {
//Validate!
if (vm.nodes.length < vm.minItems) {
$scope.nestedContentForm.minCount.$setValidity("minCount", false);
}
else {
$scope.nestedContentForm.minCount.$setValidity("minCount", true);
}
if (vm.nodes.length > vm.maxItems) {
$scope.nestedContentForm.maxCount.$setValidity("maxCount", false);
}
else {
$scope.nestedContentForm.maxCount.$setValidity("maxCount", true);
}
}
);
$scope.$on("$destroy", function () {
unsubscribe();
watcher();
});
}
]);
})();

View File

@@ -0,0 +1,159 @@
angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.DocTypePickerController", [
"$scope",
"Umbraco.PropertyEditors.NestedContent.Resources",
"overlayService",
"localizationService",
"iconHelper",
function ($scope, ncResources, overlayService, localizationService, iconHelper) {
var selectElementTypeModalTitle = "";
$scope.elemTypeTabs = [];
init();
function init() {
localizationService.localize("content_nestedContentSelectElementTypeModalTitle").then(function (value) {
selectElementTypeModalTitle = value;
});
ncResources.getContentTypes().then(function (elemTypes) {
$scope.model.elemTypes = elemTypes;
// convert legacy icons
iconHelper.formatContentTypeIcons($scope.model.elemTypes);
// Count doctype name occurrences
var elTypeNameOccurrences= _.countBy(elemTypes, 'name');
// Populate document type tab dictionary
// And append alias to name if multiple doctypes have the same name
elemTypes.forEach(function (value) {
$scope.elemTypeTabs[value.alias] = value.tabs;
if (elTypeNameOccurrences[value.name] > 1) {
value.name += " (" + value.alias + ")";
}
});
});
}
$scope.add = function () {
$scope.model.value.push({
// As per PR #4, all stored content type aliases must be prefixed "nc" for easier recognition.
// For good measure we'll also prefix the tab alias "nc"
ncAlias: "",
ncTabAlias: "",
nameTemplate: ""
});
}
$scope.canAdd = function () {
return !$scope.model.docTypes || !$scope.model.value || $scope.model.value.length < $scope.model.docTypes.length;
}
$scope.remove = function (index) {
$scope.model.value.splice(index, 1);
}
$scope.sortableOptions = {
axis: "y",
cursor: "move",
handle: ".handle",
placeholder: 'sortable-placeholder',
forcePlaceholderSize: true,
helper: function (e, ui) {
// When sorting table rows, the cells collapse. This helper fixes that: https://www.foliotek.com/devblog/make-table-rows-sortable-using-jquery-ui-sortable/
ui.children().each(function () {
$(this).width($(this).width());
});
return ui;
},
start: function (e, ui) {
var cellHeight = ui.item.height();
// Build a placeholder cell that spans all the cells in the row: https://stackoverflow.com/questions/25845310/jquery-ui-sortable-and-table-cell-size
var cellCount = 0;
$('td, th', ui.helper).each(function () {
// For each td or th try and get it's colspan attribute, and add that or 1 to the total
var colspan = 1;
var colspanAttr = $(this).attr('colspan');
if (colspanAttr > 1) {
colspan = colspanAttr;
}
cellCount += colspan;
});
// Add the placeholder UI - note that this is the item's content, so td rather than tr - and set height of tr
ui.placeholder.html('<td colspan="' + cellCount + '"></td>').height(cellHeight);
}
};
$scope.placeholder = function (config) {
return _.find($scope.model.elemTypes, function (elType) {
return elType.alias === config.ncAlias;
});
}
$scope.selectableElemTypesFor = function (config) {
// return all elemTypes that are:
// 1. either already selected for this config, or
// 2. not selected in any other config
return _.filter($scope.model.elemTypes, function (elType) {
return elType.alias === config.ncAlias || !_.find($scope.model.value, function (c) {
return elType.alias === c.ncAlias;
});
});
}
$scope.canAdd = function () {
return !$scope.model.value || _.some($scope.model.elemTypes, function (elType) {
return !_.find($scope.model.value, function (c) {
return elType.alias === c.ncAlias;
});
});
}
$scope.openElemTypeModal = function ($event, config) {
//we have to add the alias to the objects (they are stored as ncAlias)
var selectedItems = _.each($scope.model.value, function (obj) {
obj.alias = obj.ncAlias;
return obj;
})
var elemTypeSelectorOverlay = {
view: "itempicker",
title: selectElementTypeModalTitle,
availableItems: $scope.selectableElemTypesFor(config),
selectedItems: selectedItems,
position: "target",
event: $event,
submit: function (model) {
config.ncAlias = model.selectedItem.alias;
overlayService.close();
},
close: function () {
overlayService.close();
}
};
overlayService.open(elemTypeSelectorOverlay);
}
if (!$scope.model.value) {
$scope.model.value = [];
$scope.add();
}
}
]);

View File

@@ -24,12 +24,7 @@
<td>
{{ph = placeholder(config);""}}
<div class="umb-nested-content__placeholder" ng-class="{'umb-nested-content__placeholder--selected':ph}" ng-click="openElemTypeModal($event, config)">
<span class="umb-nested-content__placeholder-icon-holder" ng-if="ph">
<i class="umb-nested-content__placeholder-icon {{ ph.icon }}"></i>
</span>
<span class="umb-nested-content__placeholder-name" ng-if="ph">
{{ ph.name }}
</span>
<umb-node-preview ng-if="ph" icon="ph.icon" name="ph.name"></umb-node-preview>
<localize key="content_nestedContentAddElementType" ng-if="!ph">Add element type</localize>
</div>

View File

@@ -1,7 +1,7 @@
<div class="umb-pane">
<div ng-repeat="property in tab.properties" class="umb-nested-content-property-container">
<umb-property property="property" ng-class="{'umb-nested-content--not-supported': property.notSupported}" data-element="property-{{property.alias}}">
<umb-property property="property" ng-class="{'umb-nested-content--not-supported': property.notSupported, 'umb-nested-content--mandatory': property.ncMandatory}" data-element="property-{{property.alias}}">
<umb-property-editor model="property"></umb-property-editor>
</umb-property>
@@ -10,4 +10,4 @@
<p ng-if="property.notSupported">{{property.notSupportedMessage}}</p>
</div>
</div>
</div>

View File

@@ -1,57 +1,5 @@
<div id="umb-nested-content--{{model.id}}" class="umb-nested-content"
ng-controller="Umbraco.PropertyEditors.NestedContent.PropertyEditorController"
ng-class="{'umb-nested-content--narrow':!wideMode, 'umb-nested-content--wide':wideMode}">
<div>
<umb-load-indicator ng-if="!inited"></umb-load-indicator>
<ng-form ng-if="inited">
<div class="umb-nested-content__items" ng-hide="nodes.length === 0" ui-sortable="sortableOptions" ng-model="nodes">
<div class="umb-nested-content__item" ng-repeat="node in nodes" ng-class="{ 'umb-nested-content__item--active' : $parent.realCurrentNode.key === node.key, 'umb-nested-content__item--single' : $parent.singleMode }">
<div class="umb-nested-content__header-bar" ng-click="$parent.editNode($index)" ng-hide="$parent.singleMode">
<div class="umb-nested-content__heading"><i ng-if="showIcons" class="icon" ng-class="$parent.getIcon($index)"></i><span class="umb-nested-content__item-name" ng-class="{'--has-icon': showIcons}" ng-bind="$parent.getName($index)"></span></div>
<div class="umb-nested-content__icons">
<a class="umb-nested-content__icon umb-nested-content__icon--copy" title="{{copyIconTitle}}" ng-click="clickCopy($event, node);" ng-if="showCopy" prevent-default>
<i class="icon icon-documents"></i>
</a>
<a class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-class="{ 'umb-nested-content__icon--disabled': $parent.nodes.length <= $parent.minItems }" ng-click="$parent.requestDeleteNode($index); $event.stopPropagation();" prevent-default>
<i class="icon icon-trash"></i>
</a>
</div>
</div>
<div class="umb-nested-content__content" ng-if="$parent.realCurrentNode.key === node.key && !$parent.sorting">
<umb-nested-content-editor ng-model="node" tab-alias="ncTabAlias" />
</div>
</div>
</div>
<div ng-hide="hasContentTypes">
<div class="umb-nested-content__help-text">
<localize key="content_nestedContentNoContentTypes"></localize>
</div>
</div>
<div class="umb-nested-content__footer-bar" ng-hide="hasContentTypes === false || nodes.length >= maxItems">
<a href class="umb-nested-content__add-content" ng-class="{ '--disabled': !scaffolds.length }" ng-click="openNodeTypePicker($event)" prevent-default>
<localize key="grid_addElement"></localize>
</a>
</div>
</ng-form>
<umb-overlay
ng-if="overlayMenu.show"
position="target"
size="overlayMenu.size"
view="overlayMenu.view"
model="overlayMenu">
</umb-overlay>
<nested-content-property-editor></nested-content-property-editor>
</div>

View File

@@ -0,0 +1,75 @@
<div id="umb-nested-content--{{model.id}}" class="umb-nested-content" ng-class="{'umb-nested-content--narrow':!vm.wideMode, 'umb-nested-content--wide':vm.wideMode}">
<umb-load-indicator ng-if="!vm.inited"></umb-load-indicator>
<ng-form name="nestedContentForm">
<div class="umb-nested-content__items" ng-hide="vm.nodes.length === 0" ui-sortable="vm.sortableOptions" ng-model="vm.nodes">
<div class="umb-nested-content__item" ng-repeat="node in vm.nodes" ng-class="{ 'umb-nested-content__item--active' : vm.currentNode.key === node.key, 'umb-nested-content__item--single' : vm.singleMode }">
<div class="umb-nested-content__header-bar" ng-click="vm.editNode($index)" ng-hide="vm.singleMode">
<div class="umb-nested-content__heading"><i ng-if="vm.showIcons" class="icon" ng-class="vm.getIcon($index)"></i><span class="umb-nested-content__item-name" ng-class="{'--has-icon': vm.showIcons}" ng-bind="vm.getName($index)"></span></div>
<div class="umb-nested-content__icons">
<button type="button" class="umb-nested-content__icon umb-nested-content__icon--copy" title="{{vm.labels.copy_icon_title}}" ng-click="vm.clickCopy($event, node);" ng-if="vm.showCopy">
<i class="icon icon-documents" aria-hidden="true"></i>
<span class="sr-only">{{vm.labels.copy_icon_title}}</span>
</button>
<button type="button" class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-click="vm.requestDeleteNode($index); $event.stopPropagation();">
<i class="icon icon-trash" aria-hidden="true"></i>
<span class="sr-only">
<localize key="general_delete">Delete</localize>
</span>
</button>
</div>
</div>
<div class="umb-nested-content__content" ng-if="vm.currentNode.key === node.key && !vm.sorting">
<umb-nested-content-editor ng-model="node" tab-alias="ncTabAlias" />
</div>
</div>
</div>
<div ng-hide="vm.hasContentTypes">
<div class="umb-nested-content__help-text">
<localize key="content_nestedContentNoContentTypes"></localize>
</div>
</div>
<div class="umb-nested-content__footer-bar" ng-hide="vm.hasContentTypes === false">
<button class="btn-reset umb-nested-content__add-content umb-focus" ng-class="{ '--disabled': (!vm.scaffolds.length || vm.nodes.length >= maxItems) }" ng-click="vm.openNodeTypePicker($event)" prevent-default>
<localize key="grid_addElement"></localize>
</button>
</div>
<!--These are here because we need ng-form fields to validate against-->
<input type="hidden" name="minCount" ng-model="vm.nodes" />
<input type="hidden" name="maxCount" ng-model="vm.nodes" />
<div ng-messages="nestedContentForm.minCount.$error" show-validation-on-submit>
<div class="help text-error" ng-message="minCount">
<localize key="validation_entriesShort" tokens="[vm.minItems, vm.minItems - vm.nodes.length]" watch-tokens="true">Minimum %0% entries, needs <strong>%1%</strong> more.</localize>
</div>
</div>
<div ng-if="nestedContentForm.minCount.$error === true || vm.nodes.length > vm.maxItems">
<div class="help text-error">
<localize key="validation_entriesExceed" tokens="[vm.maxItems, vm.nodes.length - vm.maxItems]" watch-tokens="true">Maximum %0% entries, <strong>%1%</strong> too many.</localize>
</div>
</div>
</ng-form>
<umb-overlay
ng-if="vm.overlayMenu.show"
position="target"
size="vm.overlayMenu.size"
view="vm.overlayMenu.view"
model="vm.overlayMenu">
</umb-overlay>
</div>

View File

@@ -1,5 +1,5 @@
angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsController",
function ($scope) {
function ($scope, validationMessageService) {
var vm = this;
@@ -23,6 +23,12 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.RadioButtonsContro
vm.viewItems = sortedItems;
}
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
}

View File

@@ -1,7 +1,15 @@
<div class="umb-property-editor umb-radiobuttons" ng-controller="Umbraco.PropertyEditors.RadioButtonsController as vm">
<ul class="unstyled">
<li ng-repeat="item in vm.viewItems track by item.key">
<umb-radiobutton name="{{model.alias}}" value="{{item.value}}" model="model.value" text="{{item.value}}" required="model.validation.mandatory && model.value == ''"></umb-radiobutton>
</li>
</ul>
<ng-form name="radioButtonsFieldForm">
<ul class="unstyled">
<li ng-repeat="item in vm.viewItems track by item.key">
<umb-radiobutton name="{{model.alias}}" value="{{item.value}}" model="model.value" text="{{item.value}}" required="model.validation.mandatory && model.value == ''"></umb-radiobutton>
</li>
</ul>
<div ng-messages="radioButtonsFieldForm[model.alias].$error" show-validation-on-submit>
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
</div>
</ng-form>
</div>

View File

@@ -1,4 +1,4 @@
function textAreaController($scope) {
function textAreaController($scope, validationMessageService) {
// macro parameter editor doesn't contains a config object,
// so we create a new one to hold any properties
@@ -22,5 +22,11 @@ function textAreaController($scope) {
}
}
$scope.model.change();
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function (value) {
$scope.mandatoryMessage = value;
});
}
angular.module('umbraco').controller("Umbraco.PropertyEditors.textAreaController", textAreaController);

View File

@@ -3,7 +3,7 @@
<textarea ng-model="model.value" id="{{model.alias}}" name="textarea" rows="{{model.config.rows || 10}}" class="umb-property-editor umb-textarea textstring" val-server="value" ng-keyup="model.change()" ng-required="model.validation.mandatory" aria-required="{{model.validation.mandatory}}"></textarea>
<span ng-messages="textareaFieldForm.textarea.$error" show-validation-on-submit >
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
<span class="help-inline" ng-message="required">{{mandatoryMessage}}</span>
<span class="help-inline" ng-message="valServer">{{textareaFieldForm.textarea.errorMsg}}</span>
</span>

View File

@@ -1,4 +1,4 @@
function textboxController($scope) {
function textboxController($scope, validationMessageService) {
// macro parameter editor doesn't contains a config object,
// so we create a new one to hold any properties
if (!$scope.model.config) {
@@ -18,6 +18,11 @@ function textboxController($scope) {
}
}
$scope.model.change();
// Set the message to use for when a mandatory field isn't completed.
// Will either use the one provided on the property type or a localised default.
validationMessageService.getMandatoryMessage($scope.model.validation).then(function(value) {
$scope.mandatoryMessage = value;
});
}
angular.module('umbraco').controller("Umbraco.PropertyEditors.textboxController", textboxController);

View File

@@ -12,7 +12,7 @@
<div ng-messages="textboxFieldForm.textbox.$error" show-validation-on-submit>
<p class="sr-only" ng-message="valServer" tabindex="0">{{model.label}} {{textboxFieldForm.textbox.errorMsg}}</p>
<p class="help-inline" ng-message="valServer" tabindex="0" aria-hidden="true">{{textboxFieldForm.textbox.errorMsg}}</p>
<p class="help-inline" ng-message="required"><localize key="general_required">Required</localize></p>
<p class="help-inline" ng-message="required">{{mandatoryMessage}}</p>
</div>
<div class="help" ng-if="model.count >= (model.config.maxChars*.8) && model.count <= model.config.maxChars">

View File

@@ -322,7 +322,7 @@
rows="4">
</textarea>
<umb-button type="button"
button-style="[info,block]"
button-style="[action,block]"
action="model.resendInvite()"
state="model.resendInviteButtonState"
label="Resend Invite"

View File

@@ -1,6 +1,7 @@
describe('umbRequestHelper tests', function () {
var umbRequestHelper;
beforeEach(module('umbraco'));
beforeEach(module('umbraco.services'));
beforeEach(inject(function ($injector) {
@@ -33,4 +34,4 @@ describe('umbRequestHelper tests', function () {
});
});
});
});