Merge remote-tracking branch 'origin/dev-v7-contenttypeeditor' into dev-v7-contenttypeeditor-validation

Conflicts:
	src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js
	src/Umbraco.Web.UI.Client/src/less/forms.less
This commit is contained in:
Shannon
2015-10-14 12:13:51 +02:00
74 changed files with 1552 additions and 535 deletions

View File

@@ -1,6 +1,6 @@
module.exports = function (grunt) {
// Default task.
grunt.registerTask('default', ['jshint:dev', 'build', 'karma:unit']);
@@ -18,7 +18,7 @@ module.exports = function (grunt) {
grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']);
//triggered from grunt
grunt.registerTask('build', ['clean:pre', 'concat', 'recess:build', 'recess:installer', 'recess:canvasdesigner', 'postcss', 'bower-install-simple', 'bower', 'copy', 'clean:post']);
grunt.registerTask('build', ['concat', 'recess:build', 'recess:installer', 'recess:canvasdesigner', 'postcss', 'bower-install-simple', 'bower', 'copy', 'clean:post']);
//triggered from grunt dev vs or grunt vs
grunt.registerTask('build-dev', ['clean:pre', 'concat', 'recess:build', 'recess:installer', 'postcss', 'bower-install-simple', 'bower', 'copy']);
@@ -92,7 +92,7 @@ module.exports = function (grunt) {
' * Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %>\n */\n',
src: {
js: ['src/**/*.js', 'src/*.js'],
common: ['src/common/**/*.js'],
controllers: ['src/**/*.controller.js'],
@@ -143,17 +143,21 @@ module.exports = function (grunt) {
vendor: {
files: [{ dest: '<%= distdir %>/lib', src: '**', expand: true, cwd: 'lib/' }]
},
views: {
files: [{ dest: '<%= distdir %>/views', src: ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views/' }]
files: [{ dest: '<%= distdir %>/views', src: ['**/*.*', '!**/*.controller.js'], expand: true, cwd: 'src/views' }]
},
app: {
files: [
{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/' }
]
},
mocks: {
files: [{ dest: '<%= distdir %>/js', src: '*.js', expand: true, cwd: 'src/common/mocks/' }]
},
vs: {
files: [
//everything except the index.html root file!
@@ -197,6 +201,7 @@ module.exports = function (grunt) {
footer: "\n\n})();"
}
},
canvasdesignerJs: {
src: ['src/canvasdesigner/canvasdesigner.global.js', 'src/canvasdesigner/canvasdesigner.controller.js', 'src/canvasdesigner/editors/*.js', 'src/canvasdesigner/lib/*.js'],
dest: '<%= distdir %>/js/canvasdesigner.panel.js'
@@ -324,6 +329,18 @@ module.exports = function (grunt) {
}
},
ngTemplateCache: {
views: {
files: {
'<%= distdir %>/js/umbraco.views.js': 'src/views/**/*.html'
},
options: {
trim: 'src/',
module: 'umbraco.views'
}
}
},
watch: {
docs: {
files: ['docs/src/**/*.md'],
@@ -435,7 +452,7 @@ module.exports = function (grunt) {
}
},
bower: {
bower: {
dev: {
dest: '<%= distdir %>/lib',
options: {
@@ -473,7 +490,7 @@ module.exports = function (grunt) {
},
'angular-dynamic-locale': {
files: ['tmhDynamicLocale.min.js', 'tmhDynamicLocale.min.js.map']
},
},
'ng-file-upload': {
keepExpandedHierarchy: false,
files: ['ng-file-upload.min.js']
@@ -530,4 +547,5 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-ngdocs');
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-hustler');
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -0,0 +1,105 @@
(function() {
'use strict';
function LayoutSelectorDirective() {
function link(scope, el, attr, ctrl) {
scope.layoutDropDownIsOpen = false;
scope.showLayoutSelector = true;
function activate() {
setVisibility();
setActiveLayout(scope.layouts);
}
function setVisibility() {
var numberOfAllowedLayouts = getNumberOfAllowedLayouts(scope.layouts);
if(numberOfAllowedLayouts === 1) {
scope.showLayoutSelector = false;
}
}
function getNumberOfAllowedLayouts(layouts) {
var allowedLayouts = 0;
for (var i = 0; layouts.length > i; i++) {
var layout = layouts[i];
if(layout.selected === true) {
allowedLayouts++;
}
}
return allowedLayouts;
}
function setActiveLayout(layouts) {
for (var i = 0; layouts.length > i; i++) {
var layout = layouts[i];
if(layout.name === scope.activeLayout.name && layout.path === scope.activeLayout.path) {
layout.active = true;
}
}
}
scope.pickLayout = function(selectedLayout) {
for (var i = 0; scope.layouts.length > i; i++) {
var layout = scope.layouts[i];
layout.active = false;
}
selectedLayout.active = true;
scope.activeLayout = selectedLayout;
scope.layoutDropDownIsOpen = false;
};
scope.toggleLayoutDropdown = function() {
scope.layoutDropDownIsOpen = !scope.layoutDropDownIsOpen;
};
scope.closeLayoutDropdown = function() {
scope.layoutDropDownIsOpen = false;
};
activate();
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-layout-selector.html',
scope: {
layouts: '=',
activeLayout: '='
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbLayoutSelector', LayoutSelectorDirective);
})();

View File

@@ -59,6 +59,7 @@
$timeout(function() {
input.focus();
input.select();
input.on("blur", onBlurHandler);
});

View File

@@ -0,0 +1,150 @@
(function() {
'use strict';
function MediaGridDirective($filter, mediaHelper) {
function link(scope, el, attr, ctrl) {
scope.folders = [];
scope.mediaItems = [];
var itemMaxHeight = 200;
function activate() {
scope.folders = [];
scope.mediaItems = [];
for (var i = 0; scope.items.length > i; i++) {
var item = scope.items[i];
setItemData(item);
setOriginalSize(item, itemMaxHeight);
seperateFolderAndMediaItems(item);
}
if(scope.mediaItems.length > 0) {
setFlexValues(scope.mediaItems);
}
}
function setItemData(item) {
item.isFolder = !mediaHelper.hasFilePropertyType(item);
if(!item.isFolder){
item.thumbnail = mediaHelper.resolveFile(item, true);
item.image = mediaHelper.resolveFile(item, false);
}
}
function setOriginalSize(item, maxHeight) {
//set to a square by default
item.originalWidth = maxHeight;
item.originalHeight = maxHeight;
item.aspectRatio = 1;
var widthProp = _.find(item.properties, function(v) { return (v.alias === "umbracoWidth"); });
if (widthProp && widthProp.value) {
item.originalWidth = parseInt(widthProp.value, 10);
if (isNaN(item.originalWidth)) {
item.originalWidth = maxHeight;
}
}
var heightProp = _.find(item.properties, function(v) { return (v.alias === "umbracoHeight"); });
if (heightProp && heightProp.value) {
item.originalHeight = parseInt(heightProp.value, 10);
if (isNaN(item.originalHeight)) {
item.originalHeight = maxHeight;
}
}
item.aspectRatio = item.originalWidth / item.originalHeight;
}
function seperateFolderAndMediaItems(item) {
if(item.isFolder){
scope.folders.push(item);
} else {
scope.mediaItems.push(item);
}
}
function setFlexValues(mediaItems) {
var flexSortArray = mediaItems;
var smallestImageWidth = null;
var widestImageAspectRatio = null;
// sort array after image width with the widest image first
flexSortArray = $filter('orderBy')(flexSortArray, 'originalWidth', true);
// find widest image aspect ratio
widestImageAspectRatio = flexSortArray[0].aspectRatio;
// find smallest image width
smallestImageWidth = flexSortArray[flexSortArray.length - 1].originalWidth;
for (var i = 0; flexSortArray.length > i; i++) {
var mediaItem = flexSortArray[i];
var flex = 1 / (widestImageAspectRatio / mediaItem.aspectRatio);
if (flex === 0) {
flex = 1;
}
var imageMinWidth = smallestImageWidth * flex;
var flexStyle = {
"flex": flex + " 1 " + imageMinWidth + "px",
"max-width": mediaItem.originalWidth + "px"
};
mediaItem.flexStyle = flexStyle;
}
}
scope.toggleSelectItem = function(item) {
item.selected = !item.selected;
};
var unbindItemsWatcher = scope.$watch('items', function(newValue, oldValue){
activate();
});
scope.$on('$destroy', function(){
unbindItemsWatcher();
});
}
var directive = {
restrict: 'E',
replace: true,
templateUrl: 'views/components/umb-media-grid.html',
scope: {
items: '='
},
link: link
};
return directive;
}
angular.module('umbraco.directives').directive('umbMediaGrid', MediaGridDirective);
})();

View File

@@ -29,7 +29,7 @@ angular.module("umbraco.directives")
scope.alias = "Generating Alias...";
generateAliasTimeout = $timeout(function () {
contentTypeResource.getSafeAlias(value, false).then(function(safeAlias){
contentTypeResource.getSafeAlias(value, true).then(function(safeAlias){
scope.alias = safeAlias.alias;
});
}, 500);

View File

@@ -211,7 +211,7 @@ angular.module('umbraco.directives')
link: function (scope, element, attrs, ctrl) {
var active = false;
var fn = $parse(attrs.delayedMouseleave);
var fn = $parse(attrs.onDelayedMouseleave);
var leave_f = function(event) {
var callback = function() {

View File

@@ -41,7 +41,7 @@ function valRegex() {
var patternValidator = function (viewValue) {
if (regex) {
//NOTE: we don't validate on empty values, use required validator for that
if (!viewValue || regex.test(viewValue)) {
if (!viewValue || regex.test(viewValue.toString())) {
// it is valid
ctrl.$setValidity('valRegex', true);
//assign a message to the validator

View File

@@ -2,13 +2,13 @@
* @ngdoc service
* @name umbraco.services.dialogService
*
* @requires $rootScope
* @requires $rootScope
* @requires $compile
* @requires $http
* @requires $log
* @requires $q
* @requires $templateCache
*
*
* @description
* Application-wide service for handling modals, overlays and dialogs
* By default it injects the passed template url into a div to body of the document
@@ -22,10 +22,10 @@
* <pre>
* var dialog = dialogService.open({template: 'path/to/page.html', show: true, callback: done});
* functon done(data){
* //The dialog has been submitted
* //The dialog has been submitted
* //data contains whatever the dialog has selected / attached
* }
* </pre>
* }
* </pre>
*/
angular.module('umbraco.services')
@@ -38,12 +38,12 @@ angular.module('umbraco.services')
for (var i = 0; i < dialogs.length; i++) {
var dialog = dialogs[i];
//very special flag which means that global events cannot close this dialog - currently only used on the login
//very special flag which means that global events cannot close this dialog - currently only used on the login
// dialog since it's special and cannot be closed without logging in.
if (!dialog.manualClose) {
dialog.close(args);
}
}
}
@@ -56,28 +56,18 @@ angular.module('umbraco.services')
//this is not entirely enough since the damn webforms scriploader still complains
if (dialog.iframe) {
dialog.element.find("iframe").attr("src", "about:blank");
$timeout(function () {
//we need to do more than just remove the element, this will not destroy the
// scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont
// take care of this ourselves we have memory leaks.
dialog.element.remove();
//SD: No idea why this is required but was there before - pretty sure it's not required
$("#" + dialog.element.attr("id")).remove();
dialog.scope.$destroy();
}, 1000);
} else {
//we need to do more than just remove the element, this will not destroy the
// scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont
// take care of this ourselves we have memory leaks.
dialog.element.remove();
//SD: No idea why this is required but was there before - pretty sure it's not required
$("#" + dialog.element.attr("id")).remove();
dialog.scope.$destroy();
}
}
//remove 'this' dialog from the dialogs array
dialogs = _.reject(dialogs, function (i) { return i === dialog; });
dialog.scope.$destroy();
//we need to do more than just remove the element, this will not destroy the
// scope in angular 1.1x, in angular 1.2x this is taken care of but if we dont
// take care of this ourselves we have memory leaks.
dialog.element.remove();
//remove 'this' dialog from the dialogs array
dialogs = _.reject(dialogs, function (i) { return i === dialog; });
}
}
/** Internal method that handles opening all dialogs */
@@ -93,17 +83,17 @@ angular.module('umbraco.services')
template: "views/common/notfound.html",
callback: undefined,
closeCallback: undefined,
element: undefined,
element: undefined,
// It will set this value as a property on the dialog controller's scope as dialogData,
// used to pass in custom data to the dialog controller's $scope. Though this is near identical to
// the dialogOptions property that is also set the the dialog controller's $scope object.
// used to pass in custom data to the dialog controller's $scope. Though this is near identical to
// the dialogOptions property that is also set the the dialog controller's $scope object.
// So there's basically 2 ways of doing the same thing which we're now stuck with and in fact
// dialogData has another specially attached property called .selection which gets used.
dialogData: undefined
};
var dialog = angular.extend(defaults, options);
//NOTE: People should NOT pass in a scope object that is legacy functoinality and causes problems. We will ALWAYS
// destroy the scope when the dialog is closed regardless if it is in use elsewhere which is why it shouldn't be done.
var scope = options.scope || $rootScope.$new();
@@ -156,7 +146,7 @@ angular.module('umbraco.services')
dialog.element.css("width", dialog.width);
//Autoshow
//Autoshow
if (dialog.show) {
dialog.element.modal('show');
}
@@ -167,7 +157,7 @@ angular.module('umbraco.services')
else {
//We need to load the template with an httpget and once it's loaded we'll compile and assign the result to the container
// object. However since the result could be a promise or just data we need to use a $q.when. We still need to return the
// object. However since the result could be a promise or just data we need to use a $q.when. We still need to return the
// $modal object so we'll actually return the modal object synchronously without waiting for the promise. Otherwise this openDialog
// method will always need to return a promise which gets nasty because of promises in promises plus the result just needs a reference
// to the $modal object which will not change (only it's contents will change).
@@ -177,7 +167,7 @@ angular.module('umbraco.services')
// Build modal object
dialog.element.html(template);
//append to body or other container element
//append to body or other container element
dialog.container.append(dialog.element);
// Compile modal content
@@ -224,8 +214,8 @@ angular.module('umbraco.services')
scope.close = function (data) {
dialog.close(data);
};
//NOTE: This can ONLY ever be used to show the dialog if dialog.show is false (autoshow).
//NOTE: This can ONLY ever be used to show the dialog if dialog.show is false (autoshow).
// You CANNOT call show() after you call hide(). hide = close, they are the same thing and once
// a dialog is closed it's resources are disposed of.
scope.show = function () {
@@ -237,7 +227,7 @@ angular.module('umbraco.services')
//just show normally
dialog.element.modal('show');
}
};
scope.select = function (item) {
@@ -266,11 +256,11 @@ angular.module('umbraco.services')
dialog.scope = scope;
//Autoshow
//Autoshow
if (dialog.show) {
scope.show();
}
});
//Return the modal object outside of the promise!
@@ -368,7 +358,7 @@ angular.module('umbraco.services')
* @param {Function} options.callback callback function
* @returns {Object} modal object
*/
contentPicker: function (options) {
contentPicker: function (options) {
options.treeAlias = "content";
options.section = "content";
@@ -424,7 +414,7 @@ angular.module('umbraco.services')
* @returns {Object} modal object
*/
memberPicker: function (options) {
options.treeAlias = "member";
options.section = "member";
@@ -511,7 +501,7 @@ angular.module('umbraco.services')
* @name umbraco.services.dialogService#embedDialog
* @methodOf umbraco.services.dialogService
* @description
* Opens a dialog to an embed dialog
* Opens a dialog to an embed dialog
*/
embedDialog: function (options) {
options.template = 'views/common/dialogs/rteembed.html';
@@ -546,4 +536,4 @@ angular.module('umbraco.services')
return openDialog(options);
}
};
});
});

View File

@@ -2,14 +2,14 @@
* @ngdoc service
* @name umbraco.services.navigationService
*
* @requires $rootScope
* @requires $rootScope
* @requires $routeParams
* @requires $log
* @requires $location
* @requires dialogService
* @requires treeService
* @requires sectionResource
*
*
* @description
* Service to handle the main application navigation. Responsible for invoking the tree
* Section navigation and search, and maintain their state for the entire application lifetime
@@ -17,10 +17,10 @@
*/
function navigationService($rootScope, $routeParams, $log, $location, $q, $timeout, $injector, dialogService, umbModelMapper, treeService, notificationsService, historyService, appState, angularHelper) {
//used to track the current dialog object
var currentDialog = null;
//the main tree event handler, which gets assigned via the setupTreeEvents method
var mainTreeEventHandler = null;
//tracks the user profile dialog
@@ -35,8 +35,8 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
appState.setMenuState("showMenuDialog", false);
appState.setGlobalState("stickyNavigation", false);
appState.setGlobalState("showTray", false);
//$("#search-form input").focus();
//$("#search-form input").focus();
break;
case 'menu':
appState.setGlobalState("navMode", "menu");
@@ -87,7 +87,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
/** initializes the navigation service */
init: function() {
//keep track of the current section - initially this will always be undefined so
//keep track of the current section - initially this will always be undefined so
// no point in setting it now until it changes.
$rootScope.$watch(function () {
return $routeParams.section;
@@ -95,7 +95,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
appState.setSectionState("currentSection", newVal);
});
},
/**
@@ -107,7 +107,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
* Shows the legacy iframe and loads in the content based on the source url
* @param {String} source The URL to load into the iframe
*/
loadLegacyIFrame: function (source) {
loadLegacyIFrame: function (source) {
$location.path("/" + appState.getSectionState("currentSection") + "/framed/" + encodeURIComponent(source));
},
@@ -149,7 +149,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
showTree: function (sectionAlias, syncArgs) {
if (sectionAlias !== appState.getSectionState("currentSection")) {
appState.setSectionState("currentSection", sectionAlias);
if (syncArgs) {
this.syncTree(syncArgs);
}
@@ -165,7 +165,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
appState.setGlobalState("showTray", false);
},
/**
/**
Called to assign the main tree event handler - this is called by the navigation controller.
TODO: Potentially another dev could call this which would kind of mung the whole app so potentially there's a better way.
*/
@@ -179,7 +179,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
//when a tree node is synced this event will fire, this allows us to set the currentNode
mainTreeEventHandler.bind("treeSynced", function (ev, args) {
if (args.activate === undefined || args.activate === true) {
//set the current selected node
appState.setTreeState("selectedNode", args.node);
@@ -196,7 +196,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
//Set the current action node (this is not the same as the current selected node!)
appState.setMenuState("currentNode", args.node);
if (args.event && args.event.altKey) {
args.skipDefault = true;
}
@@ -220,7 +220,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
ev.preventDefault();
if (n.metaData && n.metaData["jsClickCallback"] && angular.isString(n.metaData["jsClickCallback"]) && n.metaData["jsClickCallback"] !== "") {
//this is a legacy tree node!
//this is a legacy tree node!
var jsPrefix = "javascript:";
var js;
if (n.metaData["jsClickCallback"].startsWith(jsPrefix)) {
@@ -243,7 +243,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
else if (n.routePath) {
//add action to the history service
historyService.add({ name: n.name, link: n.routePath, icon: n.icon });
//put this node into the tree state
appState.setTreeState("selectedNode", args.node);
//when a node is clicked we also need to set the active menu node to this node
@@ -269,7 +269,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
* The path format is: ["itemId","itemId"], and so on
* so to sync to a specific document type node do:
* <pre>
* navigationService.syncTree({tree: 'content', path: ["-1","123d"], forceReload: true});
* navigationService.syncTree({tree: 'content', path: ["-1","123d"], forceReload: true});
* </pre>
* @param {Object} args arguments passed to the function
* @param {String} args.tree the tree alias to sync to
@@ -287,7 +287,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
if (!args.tree) {
throw "args.tree cannot be null";
}
if (mainTreeEventHandler) {
//returns a promise
return mainTreeEventHandler.syncTree(args);
@@ -297,8 +297,8 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
return angularHelper.rejectedPromise();
},
/**
Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
/**
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
*/
_syncPath: function(path, forceReload) {
@@ -322,8 +322,8 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
}
},
/**
Internal method that should ONLY be used by the legacy API wrapper, the legacy API used to
/**
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 syncTreePath
*/
_setActiveTreeType: function (treeAlias, loadChildren) {
@@ -331,7 +331,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
mainTreeEventHandler._setActiveTreeType(treeAlias, loadChildren);
}
},
/**
* @ngdoc method
* @name umbraco.services.navigationService#hideTree
@@ -356,7 +356,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
* @methodOf umbraco.services.navigationService
*
* @description
* Hides the tree by hiding the containing dom element.
* Hides the tree by hiding the containing dom element.
* This always returns a promise!
*
* @param {Event} event the click event triggering the method, passed from the DOM element
@@ -382,7 +382,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
//NOTE: This is assigning the current action node - this is not the same as the currently selected node!
appState.setMenuState("currentNode", args.node);
//ensure the current dialog is cleared before creating another!
if (currentDialog) {
dialogService.close(currentDialog);
@@ -400,13 +400,13 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
}
}
//there is no default or we couldn't find one so just continue showing the menu
//there is no default or we couldn't find one so just continue showing the menu
setMode("menu");
appState.setMenuState("currentNode", args.node);
appState.setMenuState("menuActions", data.menuItems);
appState.setMenuState("dialogTitle", args.node.name);
appState.setMenuState("dialogTitle", args.node.name);
//we're not opening a dialog, return null.
deferred.resolve(null);
@@ -437,7 +437,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
throw "action cannot be null";
}
if (!node) {
throw "node cannot be null";
throw "node cannot be null";
}
if (!section) {
throw "section cannot be null";
@@ -456,9 +456,9 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
var menuAction = action.metaData["jsAction"].split('.');
if (menuAction.length !== 2) {
//if it is not two parts long then this most likely means that it's a legacy action
//if it is not two parts long then this most likely means that it's a legacy action
var js = action.metaData["jsAction"].replace("javascript:", "");
//there's not really a different way to acheive this except for eval
//there's not really a different way to acheive this except for eval
eval(js);
}
else {
@@ -551,7 +551,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
modalClass: "umb-modal-left",
show: true
});
return service.helpDialog;
},
@@ -564,13 +564,13 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
* Opens a dialog, for a given action on a given tree node
* uses the dialogService to inject the selected action dialog
* into #dialog div.umb-panel-body
* the path to the dialog view is determined by:
* the path to the dialog view is determined by:
* "views/" + current tree + "/" + action alias + ".html"
* The dialog controller will get passed a scope object that is created here with the properties:
* scope.currentNode = the selected tree node
* scope.currentAction = the selected menu item
* so that the dialog controllers can use these properties
*
*
* @param {Object} args arguments passed to the function
* @param {Scope} args.scope current scope passed to the dialog
* @param {Object} args.action the clicked action containing `name` and `alias`
@@ -590,6 +590,7 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
//ensure the current dialog is cleared before creating another!
if (currentDialog) {
dialogService.close(currentDialog);
currentDialog = null;
}
setMode("dialog");
@@ -649,14 +650,14 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
}
//TODO: some action's want to launch a new window like live editing, we support this in the menu item's metadata with
// a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog
// if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window,
// a key called: "actionUrlMethod" which can be set to either: Dialog, BlankWindow. Normally this is always set to Dialog
// if a URL is specified in the "actionUrl" metadata. For now I'm not going to implement launching in a blank window,
// though would be v-easy, just not sure we want to ever support that?
var dialog = dialogService.open(
{
container: $("#dialog div.umb-modalcolumn-body"),
//The ONLY reason we're passing in scope to the dialogService (which is legacy functionality) is
//The ONLY reason we're passing in scope to the dialogService (which is legacy functionality) is
// for backwards compatibility since many dialogs require $scope.currentNode or $scope.currentAction
// to exist
scope: dialogScope,
@@ -685,9 +686,9 @@ function navigationService($rootScope, $routeParams, $log, $location, $q, $timeo
* hides the currently open dialog
*/
hideDialog: function (showMenu) {
setMode("default");
if(showMenu){
this.showMenu(undefined, { skipDefault: true, node: appState.getMenuState("currentNode") });
}

View File

@@ -95,6 +95,8 @@
@import "components/umb-tabs.less";
@import "components/umb-load-indicator.less";
@import "components/umb-breadcrumbs.less";
@import "components/umb-media-grid.less";
@import "components/umb-layout-selector.less";
@import "components/overlays/umb-overlay-backdrop.less";
@import "components/buttons/umb-button.less";

View File

@@ -0,0 +1,59 @@
.umb-layout-selector {
display: inline-block;
position: relative;
}
.umb-layout-selector__active-layout {
box-sizing: border-box;
border: 1px solid transparent;
cursor: pointer;
height: 30px;
width: 30px;
margin: 0 8px;
font-size: 20px;
display: flex;
justify-content: center;
align-items: center;
}
.umb-layout-selector__active-layout:hover {
border-color: @grayLight;
}
.umb-layout-selector__dropdown {
position: absolute;
padding: 5px;
background: #333;
z-index: 999;
display: flex;
background: #fff;
flex-wrap: wrap;
flex-direction: column;
transform: translate(-50%,0);
left: 50%;
}
.umb-layout-selector__dropdown-item {
padding: 5px;
margin: 3px 5px;
display: flex;
align-content: center;
justify-content: center;
border: 1px solid transparent;
flex-direction: column;
cursor: pointer;
}
.umb-layout-selector__dropdown-item:hover {
border: 1px solid @grayLight;
}
.umb-layout-selector__dropdown-item.-active {
border: 1px solid @blue;
}
.umb-layout-selector__dropdown-item-icon {
font-size: 20px;
color: @gray;
text-align: center;
}

View File

@@ -0,0 +1,172 @@
.umb-media-grid {
display: flex;
flex-wrap: wrap;
flex-direction: row;
width: 100%;
margin-bottom: 30px;
}
/* ---------- FOLDERS --------- */
.umb-media-grid__folders {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
margin-bottom: 20px;
}
.umb-media-grid__folder {
background: @grayLighter;
margin: 5px;
display: flex;
align-items: center;
padding: 10px 30px 10px 20px;
box-sizing: border-box;
flex: 1 1 200px;
border: 1px solid transparent;
transition: border 0.2s;
position: relative;
}
.umb-media-grid__folder.-selected {
background: rgba(255, 255, 255, 0.6);
}
.umb-media-grid__folder:focus,
.umb-media-grid__folder:active {
text-decoration: none;
}
.umb-media-grid__folder:hover {
text-decoration: none;
border: 1px solid @blue;
}
.umb-media-grid__folder-icon {
font-size: 20px;
color: @gray;
margin-right: 20px;
}
.umb-media-grid__folder-name {
font-size: 13px;
}
/* ---------- MEDIA ---------- */
.umb-media-grid__media {
display: flex;
flex-direction: row;
flex-wrap: wrap;
width: 100%;
align-items: center;
}
.umb-media-grid__item {
margin: 7px;
position: relative;
background: @grayLighter;
}
.umb-media-grid__item:hover {
text-decoration: none;
}
.umb-media-grid__item-image-placeholder {
width: 200px;
height: 200px;
display: inline-block;
}
.umb-media-grid__item-image {
width: 100%;
max-width: 100%;
height: auto;
}
.umb-media-grid__item-image.-faded {
opacity: 0.5;
transition: opacity 100ms ease-in;
}
.umb-media-grid__item-overlay {
display: none;
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
padding: 5px;
overflow: hidden;
color: white;
text-align: center;
white-space: nowrap;
background: black;
background: rgba(0, 0, 0, 0.4);
}
.umb-media-grid__item:hover .umb-media-grid__item-overlay {
display: block;
}
.umb-media-grid__item-name {
text-align: center;
color: @gray;
font-size: 12px;
position: absolute;
bottom: 20px;
right: 0;
left: 0;
}
.umb-media-grid__item-icon {
color: @gray;
position: absolute;
top: 50%;
left: 50%;
font-size: 40px;
transform: translate(-50%,-50%);
}
.umb-media-grid__select {
opacity: 0;
border: 1px solid #ffffff;
width: 30px;
height: 30px;
background: rgba(0, 0, 0, 0.4);
position: absolute;
top: 10px;
right: 10px;
z-index: 10;
border-radius: 50px;
}
.umb-media-grid__select.-size-20 {
width: 20px;
height: 20px;
}
.umb-media-grid__select:hover {
background: @blue;
transition: background 0.2s;
}
.umb-media-grid__select.-selected {
opacity: 1;
background: @blue;
}
.umb-media-grid__select-icon {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: white;
}
.umb-media-grid__item:hover .umb-media-grid__select,
.umb-media-grid__folder:hover .umb-media-grid__select{
opacity: 1;
transition: opacity 0.2s;
}

View File

@@ -167,6 +167,12 @@ input[type="color"],
vertical-align: middle;
}
input.-full-width-input {
width: 100%;
box-sizing: border-box;
padding: 4px 6px;
}
// Reset appearance properties for textual inputs and textarea
// Declare width for legacy (can't be on input[type=*] selectors or it's too specific)
input,
@@ -429,7 +435,7 @@ input[type="checkbox"][readonly] {
//SD: NOTE: Had to change these to use our 'form' prefixed colors since we cannot
// share colors with the notifications/alerts. Also had to change them so that
// share colors with the notifications/alerts. Also had to change them so that
// we do not show any errors unless the containing element has the show-validation
// class assigned.
@@ -792,4 +798,4 @@ legend + .control-group {
margin-left: 0;
}
}
}

View File

@@ -13,9 +13,9 @@
box-shadow: 0 5px 10px rgba(0, 0, 0, 0);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus,
.dropdown-submenu:hover > a,
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus,
.dropdown-submenu:hover > a,
.dropdown-submenu:focus > a {
color: @black;
background: @grayLighter;
@@ -124,7 +124,7 @@
}
.umb-listview .selected i.icon, .umb-listview tbody tr:hover i.icon{display: none}
.umb-listview .selected input[type="checkbox"],
.umb-listview .selected input[type="checkbox"],
.umb-listview tr:hover input[type="checkbox"]{display: inline-block !important;}
.umb-listview .inactive{color: @grayLight;}
@@ -181,6 +181,10 @@
color: #b0b0b0
}
.umb-listview .pagination {
text-align: center;
}
.umb-listview .pagination ul {
-webkit-border-radius: 0px;
-moz-border-radius: 0px;
@@ -195,7 +199,7 @@
border:none;
padding: 8px 4px 2px 4px;
background: none;
font-size: 11px;
font-size: 12px;
color: #b0b0b0
}
@@ -242,3 +246,87 @@
.table-striped tbody i:hover {
display: none !important
}
/* ---------- LAYOUTS ---------- */
.list-view-layouts {
}
.list-view-layout {
display: flex;
align-items: center;
padding: 10px 15px;
background: @grayLighter;
border-radius: 5px;
margin-bottom: 1px;
}
.list-view-layout__sort-handle {
font-size: 14px;
color: @grayLight;
margin-right: 15px;
}
.list-view-layout__name {
flex: 5;
font-weight: bold;
margin-right: 15px;
display: flex;
align-content: center;
flex-wrap: wrap;
line-height: 1.2em;
}
.list-view-layout__name-text {
margin-right: 3px;
}
.list-view-layout__system {
font-size: 10px;
font-weight: normal;
}
.list-view-layout__path {
flex: 10;
margin-right: 15px;
}
.list-view-layout__icon {
font-size: 18px;
margin-right: 10px;
vertical-align: middle;
border: 1px solid @grayLight;
border-radius: 5px;
background: #ffffff;
padding: 6px 8px;
display: block;
}
.list-view-layout__icon:hover,
.list-view-layout__icon:focus,
.list-view-layout__icon:active {
text-decoration: none;
}
.list-view-layout__remove-layout {
flex: 2;
text-align: right;
font-size: 20px;
}
.list-view-add-layout {
margin-top: 10px;
color: @blue;
border: 1px dashed #d9d9d9;
border-radius: 5px;
display: flex;
align-items: center;
justify-content: center;
padding: 5px 0;
box-sizing: border-box;
}
.list-view-add-layout:hover {
text-decoration: none;
}

View File

@@ -0,0 +1,15 @@
<div class="umb-layout-selector" ng-show="showLayoutSelector">
<div class="umb-layout-selector__active-layout" ng-click="toggleLayoutDropdown()">
<i class="{{ activeLayout.icon }}"></i>
</div>
<div ng-if="layoutDropDownIsOpen" class="umb-layout-selector__dropdown shadow-depth-3 animated -half-second fadeIn" on-outside-click="closeLayoutDropdown()">
<div ng-repeat="layout in layouts | filter:{selected:true}" class="umb-layout-selector__dropdown-item" ng-click="pickLayout(layout)" ng-class="{'-active': layout.active }">
<i class="{{ layout.icon }} umb-layout-selector__dropdown-item-icon"></i>
</div>
</div>
</div>

View File

@@ -0,0 +1,39 @@
<div class="umb-media-grid">
<div class="umb-media-grid__folders">
<a href="#{{folder.editPath}}" class="umb-media-grid__folder" ng-repeat="folder in folders">
<i class="umb-media-grid__folder-icon {{ folder.icon }}"></i>
<div class="umb-media-grid__folder-name">{{ folder.name }}</div>
<div class="umb-media-grid__select -size-20" ng-click="toggleSelectItem(folder)" ng-class="{'-selected': folder.selected}" prevent-default>
<i class="icon-check umb-media-grid__select-icon"></i>
</div>
</a>
</div>
<div class="umb-media-grid__media">
<a href="#{{item.editPath}}" ng-repeat="item in mediaItems" ng-style="item.flexStyle" class="umb-media-grid__item">
<div class="umb-media-grid__select" ng-click="toggleSelectItem(item)" ng-class="{'-selected': item.selected}" prevent-default>
<i class="icon-check umb-media-grid__select-icon"></i>
</div>
<div class="umb-media-grid__item-overlay">{{item.name}}</div>
<img class="umb-media-grid__item-image" ng-if="item.thumbnail" ng-src="{{item.thumbnail}}" alt="{{item.name}}" ng-class="{'-faded': item.selected}" />
<img class="umb-media-grid__item-image" ng-if="!item.thumbnail" src="assets/img/transparent.png" alt="{{item.name}}" />
<i class="umb-media-grid__item-icon {{ item.icon }}" ng-if="!item.thumbnail"></i>
</a>
</div>
</div>

View File

@@ -7,7 +7,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
searchText = value + "...";
});
$scope.relateToOriginal = false;
$scope.relateToOriginal = true;
$scope.dialogTreeEventHandler = $({});
$scope.busy = false;
$scope.searchInfo = {
@@ -42,7 +42,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
$scope.target = args.node;
$scope.target.selected = true;
}
}
function nodeExpandedHandler(ev, args) {
@@ -50,7 +50,7 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
//iterate children
_.each(args.children, function (child) {
//check if any of the items are list views, if so we need to add a custom
//check if any of the items are list views, if so we need to add a custom
// child: A node to activate the search
if (child.metaData.isContainer) {
child.hasChildren = true;
@@ -78,18 +78,18 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
$scope.searchInfo.results = [];
}
// method to select a search result
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, { event: evt, node: result });
};
//callback when there are search results
//callback when there are search results
$scope.onSearchResults = function (results) {
$scope.searchInfo.results = results;
$scope.searchInfo.showSearch = true;
};
$scope.copy = function () {
$scope.busy = true;
@@ -129,4 +129,4 @@ angular.module("umbraco").controller("Umbraco.Editors.Content.CopyController",
$scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
$scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
});
});
});

View File

@@ -6,14 +6,14 @@
</p>
<p>
<i class="icon-alert red"></i> <strong class="red">2345 Documents</strong> will be deleted permanently if you delete this document type,
please confirm you want to delete these as well.
<i class="icon-alert red"></i> <strong class="red">All Documents</strong>
using this document type will be deleted permanently, please confirm you want to delete these as well.
</p>
<hr />
<label class="checkbox">
<input type="checkbox" ng-model="confirmed" /> Delete all 2345 documents using {{currentNode.name}}
<input type="checkbox" ng-model="confirmed" /> Delete {{currentNode.name}} and all documents using this type
</label>
<umb-confirm ng-if="confirmed" on-confirm="performDelete" on-cancel="cancel">

View File

@@ -24,23 +24,23 @@
{
"name": "Design",
"icon": "icon-document-dashed-line",
"view": "views/documentType/views/design/design.html",
"view": "views/documenttypes/views/design/design.html",
"active": true
},
{
"name": "List view",
"icon": "icon-list",
"view": "views/documentType/views/listview/listview.html"
"view": "views/documenttypes/views/listview/listview.html"
},
{
"name": "Permissions",
"icon": "icon-keychain",
"view": "views/documentType/views/permissions/permissions.html"
"view": "views/documenttypes/views/permissions/permissions.html"
},
{
"name": "Templates",
"icon": "icon-layout",
"view": "views/documentType/views/templates/templates.html"
"view": "views/documenttypes/views/templates/templates.html"
}
];

View File

@@ -24,18 +24,18 @@
{
"name": "Design",
"icon": "icon-document-dashed-line",
"view": "views/mediaType/views/design/design.html",
"view": "views/mediatypes/views/design/design.html",
"active": true
},
{
"name": "List view",
"icon": "icon-list",
"view": "views/mediaType/views/listview/listview.html"
"view": "views/mediatypes/views/listview/listview.html"
},
{
"name": "Permissions",
"icon": "icon-keychain",
"view": "views/mediaType/views/permissions/permissions.html"
"view": "views/mediatypes/views/permissions/permissions.html"
}
];

View File

@@ -18,13 +18,13 @@
vm.currentNode = null;
vm.contentType = {};
vm.page = {};
vm.page.loading = false;
vm.page.saveButtonState = "init";
vm.page.loading = false;
vm.page.saveButtonState = "init";
vm.page.navigation = [
{
"name": "Design",
"icon": "icon-document-dashed-line",
"view": "views/membertype/views/design/design.html",
"view": "views/membertypes/views/design/design.html",
"active": true
}
];

View File

@@ -0,0 +1,11 @@
<div>
<input name="decimalField" class="umb-editor umb-number"
type="number"
ng-model="model.value"
val-server="value"
fix-number />
<span class="help-inline" val-msg-for="decimalField" val-toggle-msg="number">Not a number</span>
<span class="help-inline" val-msg-for="decimalField" val-toggle-msg="valServer">{{propertyForm.requiredField.errorMsg}}</span>
</div>

View File

@@ -3,7 +3,7 @@
type="number"
ng-model="model.value"
val-server="value"
fix-number />
fix-number />
<span class="help-inline" val-msg-for="numberField" val-toggle-msg="number">Not a number</span>
<span class="help-inline" val-msg-for="numberField" val-toggle-msg="valServer">{{propertyForm.requiredField.errorMsg}}</span>

View File

@@ -0,0 +1,11 @@
<div class="umb-editor">
<input name="decimalField"
type="number"
class="umb-editor umb-number"
ng-model="model.value"
val-server="value"
fix-number min="{{model.config.min}}" max="{{model.config.max}}" step="{{model.config.step}}" />
<span class="help-inline" val-msg-for="decimalField" val-toggle-msg="number">Not a number</span>
<span class="help-inline" val-msg-for="decimalField" val-toggle-msg="valServer">{{propertyForm.requiredField.errorMsg}}</span>
</div>

View File

@@ -1,10 +1,11 @@
<div class="umb-editor">
<input name="integerField" type="number" class="umb-editor umb-number "
ng-model="model.value"
val-server="value"
fix-number min="{{model.config.min}}" max="{{model.config.max}}" step="{{model.config.step}}" />
<input name="integerField"
type="number"
class="umb-editor umb-number "
ng-model="model.value"
val-server="value"
fix-number min="{{model.config.min}}" max="{{model.config.max}}" step="{{model.config.step}}" />
<span class="help-inline" val-msg-for="integerField" val-toggle-msg="number">Not a number</span>
<span class="help-inline" val-msg-for="integerField" val-toggle-msg="valServer">{{propertyForm.requiredField.errorMsg}}</span>
</div>

View File

@@ -0,0 +1,86 @@
/**
* @ngdoc controller
* @name Umbraco.PrevalueEditors.ListViewLayoutsPreValsController
* @function
*
* @description
* The controller for configuring layouts for list views
*/
(function() {
"use strict";
function ListViewLayoutsPreValsController($scope) {
var vm = this;
vm.focusLayoutName = false;
vm.layoutsSortableOptions = {
distance: 10,
tolerance: "pointer",
opacity: 0.7,
scroll: true,
cursor: "move",
handle: ".list-view-layout__sort-handle"
};
vm.addLayout = addLayout;
vm.removeLayout = removeLayout;
vm.openIconPicker = openIconPicker;
function activate() {
}
function addLayout() {
vm.focusLayoutName = false;
var layout = {
"name": "",
"path": "",
"icon": "icon-stop",
"selected": true
};
$scope.model.value.push(layout);
}
function removeLayout($index, layout) {
$scope.model.value.splice($index, 1);
}
function openIconPicker(layout) {
vm.iconPickerDialog = {};
vm.iconPickerDialog.view = "iconpicker";
vm.iconPickerDialog.show = true;
vm.iconPickerDialog.pickIcon = function(icon, color) {
layout.icon = icon;
vm.focusLayoutName = true;
vm.iconPickerDialog.show = false;
vm.iconPickerDialog = null;
};
vm.iconPickerDialog.close = function(oldModel) {
vm.iconPickerDialog.show = false;
vm.iconPickerDialog = null;
};
}
activate();
}
angular.module("umbraco").controller("Umbraco.PrevalueEditors.ListViewLayoutsPreValsController", ListViewLayoutsPreValsController);
})();

View File

@@ -0,0 +1,49 @@
<div ng-controller="Umbraco.PrevalueEditors.ListViewLayoutsPreValsController as vm">
<div class="list-view-layouts" ui-sortable="vm.layoutsSortableOptions" ng-model="model.value">
<div class="list-view-layout" ng-repeat="layout in model.value">
<i class="icon-navigation list-view-layout__sort-handle"></i>
<div>
<a ng-if="layout.isSystem !== 1" href="" ng-click="vm.openIconPicker(layout)" class="list-view-layout__icon" umb-auto-focus>
<i class="{{ layout.icon }}"></i>
</a>
<div ng-if="layout.isSystem === 1" class="list-view-layout__icon">
<i class="{{ layout.icon }}"></i>
</div>
</div>
<div class="list-view-layout__name">
<input ng-if="layout.isSystem !== 1" ng-model="layout.name" placeholder="Name..." class="-full-width-input" focus-when="{{ vm.focusLayoutName }}" />
<span ng-if="layout.isSystem === 1" class="list-view-layout__name-text">{{ layout.name }}</span>
<span ng-if="layout.isSystem === 1" class="list-view-layout__system">(system layout)</span>
</div>
<div class="list-view-layout__path">
<input ng-if="layout.isSystem !== 1" ng-model="layout.path" placeholder="Layout path..." class="-full-width-input" />
</div>
<div>
<input ng-if="layout.isSystem === 1" type="checkbox" ng-model="layout.selected" />
<umb-confirm-delete ng-if="layout.isSystem !== 1" confirm-action="vm.removeLayout($index, layout)"></umbraco-confirm-delete>
</div>
</div>
<a href="" class="list-view-add-layout" ng-click="vm.addLayout()">Add layout</a>
</div>
<umb-overlay
ng-if="vm.iconPickerDialog.show"
model="vm.iconPickerDialog"
view="vm.iconPickerDialog.view"
position="right">
</umb-overlay>
</div>

View File

@@ -0,0 +1,24 @@
<div ng-controller="Umbraco.PropertyEditors.ListView.GridLayoutController as vm">
<div
ng-if="entityType === 'media'"
on-drag-leave="vm.dragLeave()"
on-drag-end="vm.dragLeave()"
on-drag-enter="vm.dragEnter()">
<umb-file-dropzone
parent-id="{{vm.nodeId}}"
files-uploaded="vm.onUploadComplete"
accept="{{vm.acceptedFileTypes}}"
hide-dropzone="{{!vm.activeDrag && listViewResultSet.items.length > 0 }}"
compact="{{ listViewResultSet.items.length > 0 }}"
files-queued="vm.onFilesQueue">
</umb-file-dropzone>
<umb-media-grid
items="listViewResultSet.items">
</umb-media-grid>
<div>
</div>

View File

@@ -0,0 +1,48 @@
/**
* @ngdoc controller
* @name Umbraco.Editors.DocumentType.EditController
* @function
*
* @description
* The controller for the content type editor
*/
(function() {
"use strict";
function ListViewGridLayoutController($scope, $routeParams, mediaHelper) {
var vm = this;
vm.nodeId = $routeParams.id;
vm.acceptedFileTypes = mediaHelper.formatFileTypes(Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes);
vm.activeDrag = false;
vm.dragEnter = dragEnter;
vm.dragLeave = dragLeave;
vm.onFilesQueue = onFilesQueue;
vm.onUploadComplete = onUploadComplete;
function dragEnter(el, event) {
vm.activeDrag = true;
}
function dragLeave(el, event) {
vm.activeDrag = false;
}
function onFilesQueue() {
vm.activeDrag = false;
}
function onUploadComplete() {
// call reload function on list view parent controller
$scope.reloadView($scope.contentId);
}
}
angular.module("umbraco").controller("Umbraco.PropertyEditors.ListView.GridLayoutController", ListViewGridLayoutController);
})();

View File

@@ -0,0 +1,51 @@
<table class="table table-striped">
<thead>
<tr>
<td style="width: 35px">
<input type="checkbox" ng-click="selectAll($event)" ng-checked="isSelectedAll()">
</td>
<td>
<a href="#" ng-click="sort('Name', true)" prevent-default class="sortable">
<localize key="general_name">Name</localize>
<i class="icon" ng-class="{'icon-navigation-up': isSortDirection('Name', 'asc'), 'icon-navigation-down': isSortDirection('Name', 'desc')}"></i>
</a>
</td>
<td ng-repeat="column in options.includeProperties">
<a href="#" ng-click="sort(column.alias, column.allowSorting)" ng-class="{'sortable':column.allowSorting}" prevent-default>
<span ng-bind="column.header"></span>
<i class="icon" ng-class="{'icon-navigation-up': isSortDirection(column.alias, 'asc'), 'icon-navigation-down': isSortDirection(column.alias, 'desc')}"></i>
</a>
</td>
</tr>
</thead>
<tbody ng-show="listViewResultSet.totalItems === 0">
<tr>
<td colspan="{{options.includeProperties.length + 2}}">
<p><localize key="content_listViewNoItems">There are no items show in the list.</localize></p>
</td>
</tr>
</tbody>
<tbody ng-show="listViewResultSet.totalItems > 0">
<tr ng-repeat="result in listViewResultSet.items"
ng-class="{selected:result.selected}">
<td>
<i class="icon {{result.icon}}" ng-class="getIcon(result)"></i>
<input type="checkbox" ng-model="result.selected" no-dirty-check>
</td>
<td>
<a ng-class="{inactive: (entityType === 'content' && !result.published) || isTrashed}"
href="#{{result.editPath}}"
ng-bind="result.name"></a>
</td>
<td ng-repeat="column in options.includeProperties">
<span>{{result[column.alias]}}</span>
</td>
</tr>
</tbody>
</table>

View File

@@ -51,6 +51,8 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific
$scope.pagination = [];
$scope.isNew = false;
$scope.actionInProgress = false;
$scope.layout = {};
$scope.layout.activeLayout = {};
$scope.listViewResultSet = {
totalPages: 0,
items: []
@@ -71,6 +73,9 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific
allowBulkDelete: true,
};
// set active layout
$scope.layout.activeLayout = getFirstAllowedLayout($scope.model.config.layouts);
//update all of the system includeProperties to enable sorting
_.each($scope.options.includeProperties, function(e, i) {
@@ -91,6 +96,24 @@ function listViewController($rootScope, $scope, $routeParams, $injector, notific
}
});
function getFirstAllowedLayout(layouts) {
var firstAllowedLayout = {};
for (var i = 0; layouts.length > i; i++) {
var layout = layouts[i];
if (layout.selected === true) {
firstAllowedLayout = layout;
break;
}
}
return firstAllowedLayout;
}
function showNotificationsAndReset(err, reload, successMsg) {
//check if response is ysod

View File

@@ -5,37 +5,56 @@
</div>
<div class="row-fluid" ng-switch-when="false">
<div class="umb-sub-header">
<div class="btn-group" ng-show="listViewAllowedTypes && listViewAllowedTypes.length > 0">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<localize key="actions_create">Create</localize>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li ng-repeat="contentType in listViewAllowedTypes">
<a href="#/{{entityType}}/{{entityType}}/edit/{{contentId}}?doctype={{contentType.alias}}&create=true">
<i class="icon-{{contentType.cssClass}}"></i>
{{contentType.name}}
</a>
</li>
</ul>
<div class="umb-sub-header clearfix">
<div class="pull-left">
<div class="btn-group" ng-show="listViewAllowedTypes && listViewAllowedTypes.length > 0">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<localize key="actions_create">Create</localize>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
<li ng-repeat="contentType in listViewAllowedTypes">
<a href="#/{{entityType}}/{{entityType}}/edit/{{contentId}}?doctype={{contentType.alias}}&create=true">
<i class="icon-{{contentType.cssClass}}"></i>
{{contentType.name}}
</a>
</li>
</ul>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="entityType === 'content' && !isTrashed && options.allowBulkPublish">
<a class="btn btn-success" ng-disabled="actionInProgress" ng-click="publish()" prevent-default>
<localize key="actions_publish">Publish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="entityType === 'content' && !isTrashed && options.allowBulkUnpublish">
<a class="btn btn-warning" ng-disabled="actionInProgress" ng-click="unpublish()" prevent-default>
<localize key="actions_unpublish">Unpublish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="options.allowBulkDelete">
<a class="btn btn-danger" ng-disabled="actionInProgress" ng-click="delete()" prevent-default>
<localize key="actions_delete">Delete</localize>
</a>
</div>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="entityType === 'content' && !isTrashed && options.allowBulkPublish">
<a class="btn btn-success" ng-disabled="actionInProgress" ng-click="publish()" prevent-default>
<localize key="actions_publish">Publish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="entityType === 'content' && !isTrashed && options.allowBulkUnpublish">
<a class="btn btn-warning" ng-disabled="actionInProgress" ng-click="unpublish()" prevent-default>
<localize key="actions_unpublish">Unpublish</localize>
</a>
</div>
<div class="btn-group" ng-show="isAnythingSelected()" ng-if="options.allowBulkDelete">
<a class="btn btn-danger" ng-disabled="actionInProgress" ng-click="delete()" prevent-default>
<localize key="actions_delete">Delete</localize>
</a>
<div class="pull-right">
<form class="form-search pull-right" novalidate ng-show="!actionInProgress">
<div class="inner-addon left-addon">
<i class="icon icon-search" ng-click="enterSearch($event)"></i>
<input type="text" class="form-control" localize="placeholder" placeholder="@general_typeToSearch" ng-model="options.filter" prevent-enter-submit no-dirty-check>
</div>
</form>
<umb-layout-selector
ng-if="model.config.layouts"
layouts="model.config.layouts"
active-layout="layout.activeLayout">
</umb-layout-selector>
</div>
<div class="umb-loader-wrapper" ng-show="actionInProgress">
@@ -44,96 +63,36 @@
<span ng-bind="bulkStatus" ng-show="isAnythingSelected()" class="pull-right"></span>
<form class="form-search pull-right" novalidate ng-show="!actionInProgress">
<div class="inner-addon left-addon">
<i class="icon icon-search" ng-click="enterSearch($event)"></i>
<input type="text" class="form-control" localize="placeholder" placeholder="@general_typeToSearch" ng-model="options.filter" prevent-enter-submit no-dirty-check>
</div>
</form>
</div>
<table class="table table-striped">
<thead>
<tr>
<td style="width: 35px">
<input type="checkbox" ng-click="selectAll($event)" ng-checked="isSelectedAll()">
</td>
<td>
<a href="#" ng-click="sort('Name', true)" prevent-default class="sortable">
<localize key="general_name">Name</localize>
<i class="icon" ng-class="{'icon-navigation-up': isSortDirection('Name', 'asc'), 'icon-navigation-down': isSortDirection('Name', 'desc')}"></i>
</a>
</td>
<div ng-include="layout.activeLayout.path"></div>
<td ng-repeat="column in options.includeProperties">
<a href="#" ng-click="sort(column.alias, column.allowSorting)" ng-class="{'sortable':column.allowSorting}" prevent-default>
<span ng-bind="column.header"></span>
<i class="icon" ng-class="{'icon-navigation-up': isSortDirection(column.alias, 'asc'), 'icon-navigation-down': isSortDirection(column.alias, 'desc')}"></i>
</a>
</td>
</tr>
</thead>
<div class="pagination" ng-show="pagination.length > 1">
<tbody ng-show="listViewResultSet.totalItems === 0">
<tr>
<td colspan="{{options.includeProperties.length + 2}}">
<p><localize key="content_listViewNoItems">There are no items show in the list.</localize></p>
</td>
</tr>
</tbody>
<ul>
<li ng-class="{disabled:options.pageNumber <= 1}">
<a href="#" ng-click="prev()" prevent-default>
<localize key="general_previous">Previous</localize>
</a>
</li>
<tbody ng-show="listViewResultSet.totalItems > 0">
<tr ng-repeat="result in listViewResultSet.items"
ng-class="{selected:result.selected}">
<li ng-repeat="pgn in pagination"
ng-class="{active:pgn.isActive}">
<td>
<i class="icon {{result.icon}}" ng-class="getIcon(result)"></i>
<input type="checkbox" ng-model="result.selected" no-dirty-check>
</td>
<td>
<a ng-class="{inactive: (entityType === 'content' && !result.published) || isTrashed}"
href="#{{result.editPath}}"
ng-bind="result.name"></a>
</td>
<a href="#" ng-click="goToPage(pgn.val - 1)" prevent-default
ng-bind="pgn.name ? pgn.name : pgn.val"
ng-if="pgn.val != '...'"></a>
<span ng-bind="pgn.val" ng-if="pgn.val == '...'"></span>
</li>
<td ng-repeat="column in options.includeProperties">
<span>{{result[column.alias]}}</span>
</td>
</tr>
</tbody>
<li ng-class="{disabled:options.pageNumber >= listViewResultSet.totalPages}">
<a href="#" ng-click="next()" prevent-default>
<localize key="general_next">Next</localize>
</a>
</li>
</ul>
<tfoot ng-show="pagination.length > 1">
<tr>
<th colspan="{{options.includeProperties.length + 2}}">
<div class="pull-left">
</div>
<div class="pagination pagination-right">
<ul>
<li ng-class="{disabled:options.pageNumber <= 1}">
<a href="#" ng-click="prev()" prevent-default>
<localize key="general_previous">Previous</localize>
</a>
</li>
</div>
<li ng-repeat="pgn in pagination"
ng-class="{active:pgn.isActive}">
<a href="#" ng-click="goToPage(pgn.val - 1)" prevent-default
ng-bind="pgn.name ? pgn.name : pgn.val"
ng-if="pgn.val != '...'"></a>
<span ng-bind="pgn.val" ng-if="pgn.val == '...'"></span>
</li>
<li ng-class="{disabled:options.pageNumber >= listViewResultSet.totalPages}">
<a href="#" ng-click="next()" prevent-default>
<localize key="general_next">Next</localize>
</a>
</li>
</ul>
</div>
</th>
</tr>
</tfoot>
</table>
</div>
</div>