Merge from 7.1.1

This commit is contained in:
AndyButland
2014-04-13 22:52:41 +02:00
1243 changed files with 42944 additions and 31462 deletions

View File

@@ -125,7 +125,7 @@ loadMenu: function (node) {
return umbRequestHelper.resourcePromise(
$http.get(getTreeMenuUrl(node)),
"Failed to retreive data for a node's menu " + node.id);
"Failed to retrieve data for a node's menu " + node.id);
}</code></pre>
<p>HTTP error handling is performed automatically inside of the <code>umbRequestHelper.resourcePromise</code> and inside of Umbraco&#39;s response interceptors.</p>
<h2>Consuming Umbraco resources</h2>

View File

@@ -23,7 +23,7 @@ Here's an example of the usage in an Umbraco resource. This example is the metho
return umbRequestHelper.resourcePromise(
$http.get(getTreeMenuUrl(node)),
"Failed to retreive data for a node's menu " + node.id);
"Failed to retrieve data for a node's menu " + node.id);
}
HTTP error handling is performed automatically inside of the `umbRequestHelper.resourcePromise` and inside of Umbraco's response interceptors.
@@ -105,4 +105,4 @@ The good news is, this is very simple to do, example:
});
}
The next thing that is important to note is that **you don't have to do anything** if you don't want to do anything with the error but still want the error bubbled up to your promises handlers. So for example, if you are expecting the handler of this promise to handle the error and display something in the UI, just leave out the function(err) callback which would look exactly the same as the example for 'Transforming result data'
The next thing that is important to note is that **you don't have to do anything** if you don't want to do anything with the error but still want the error bubbled up to your promises handlers. So for example, if you are expecting the handler of this promise to handle the error and display something in the UI, just leave out the function(err) callback which would look exactly the same as the example for 'Transforming result data'

View File

@@ -3,17 +3,18 @@ module.exports = function (grunt) {
// Default task.
grunt.registerTask('default', ['jshint:dev','build','karma:unit']);
grunt.registerTask('dev', ['jshint:dev', 'build', 'webserver', 'open:dev', 'watch']);
//run by the watch task
grunt.registerTask('watch-js', ['jshint:dev','concat','copy:app','copy:mocks','copy:packages','copy:vs','karma:unit']);
grunt.registerTask('watch-less', ['recess:build','copy:assets','copy:vs']);
grunt.registerTask('watch-less', ['recess:build','recess:installer','copy:assets','copy:vs']);
grunt.registerTask('watch-html', ['copy:views', 'copy:vs']);
grunt.registerTask('watch-packages', ['copy:packages']);
grunt.registerTask('watch-installer', ['concat:install','concat:installJs','copy:installer', 'copy:vs']);
grunt.registerTask('watch-test', ['jshint:dev', 'karma:unit']);
//triggered from grunt dev or grunt
grunt.registerTask('build', ['clean','concat','recess:min','copy']);
grunt.registerTask('build', ['clean','concat','recess:min','recess:installer','copy']);
//utillity tasks
grunt.registerTask('docs', ['ngdocs']);
grunt.registerTask('webserver', ['connect:devserver']);
@@ -68,7 +69,7 @@ module.exports = function (grunt) {
specs: ['test/**/*.spec.js'],
scenarios: ['test/**/*.scenario.js'],
samples: ['sample files/*.js'],
html: ['src/index.html'],
html: ['src/index.html','src/install.html'],
everything:['src/**/*.*', 'test/**/*.*', 'docs/**/*.*'],
@@ -86,6 +87,11 @@ module.exports = function (grunt) {
assets: {
files: [{ dest: '<%= distdir %>/assets', src : '**', expand: true, cwd: 'src/assets/' }]
},
installer: {
files: [{ dest: '<%= distdir %>/views/install', src : '**/*.html', expand: true, cwd: 'src/installer/steps' }]
},
vendor: {
files: [{ dest: '<%= distdir %>/lib', src : '**', expand: true, cwd: 'lib/' }]
},
@@ -130,8 +136,24 @@ module.exports = function (grunt) {
process: true
}
},
install: {
src: ['src/installer/installer.html'],
dest: '<%= distdir %>/installer.html',
options: {
process: true
}
},
installJs: {
src: ['src/installer/**/*.js'],
dest: '<%= distdir %>/js/umbraco.installer.js',
options: {
banner: "<%= banner %>\n(function() { \n\n angular.module('umbraco.install', []); \n",
footer: "\n\n})();"
}
},
controllers: {
src:['src/views/**/*.controller.js'],
src:['src/controllers/**/*.controller.js','src/views/**/*.controller.js'],
dest: '<%= distdir %>/js/umbraco.controllers.js',
options: {
banner: "<%= banner %>\n(function() { \n\n",
@@ -198,7 +220,7 @@ module.exports = function (grunt) {
}
}
},
recess: {
build: {
files: {
@@ -208,6 +230,14 @@ module.exports = function (grunt) {
compile: true
}
},
installer: {
files: {
'<%= distdir %>/assets/css/installer.css':
['src/less/installer.less'] },
options: {
compile: true
}
},
min: {
files: {
'<%= distdir %>/assets/css/<%= pkg.name %>.css': ['<%= src.less %>']
@@ -220,7 +250,6 @@ module.exports = function (grunt) {
},
watch:{
css: {
files: '**/*.less',
@@ -237,6 +266,10 @@ module.exports = function (grunt) {
files: ['test/**/*.js'],
tasks: ['watch-test', 'timestamp'],
},
installer: {
files: ['src/installer/**/*.*'],
tasks: ['watch-installer', 'timestamp'],
},
html: {
files: ['src/views/**/*.html', 'src/*.html'],
tasks:['watch-html','timestamp']
@@ -268,7 +301,9 @@ module.exports = function (grunt) {
jshint:{
dev:{
files:['<%= src.common %>', '<%= src.specs %>', '<%= src.scenarios %>', '<%= src.samples %>'],
files: {
src: ['<%= src.common %>', '<%= src.specs %>', '<%= src.scenarios %>', '<%= src.samples %>']
},
options:{
curly:true,
eqeqeq:true,
@@ -288,11 +323,13 @@ module.exports = function (grunt) {
//NOTE: we ignore tabs vs spaces because enforcing that causes lots of errors depending on the text editor being used
smarttabs: true,
globals:{}
}
}
},
build:{
files:['<%= src.prod %>'],
options:{
files: {
src: ['<%= src.prod %>']
},
options:{
curly:true,
eqeqeq:true,
immed:true,
@@ -312,13 +349,13 @@ module.exports = function (grunt) {
smarttabs: true,
globalstrict:true,
globals:{$:false, jQuery:false,define:false,require:false,window:false}
}
}
}
}
});
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-clean');
@@ -328,11 +365,10 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-recess');
grunt.loadNpmTasks('grunt-karma');
grunt.loadNpmTasks('grunt-open');
grunt.loadNpmTasks('grunt-contrib-connect');
grunt.loadNpmTasks('grunt-ngdocs');
grunt.loadNpmTasks('grunt-ngmin');
};

View File

@@ -21,6 +21,9 @@
float: left; // Explicity set the float since we don't require .span* classes
margin-bottom: @baseLineHeight;
margin-left: @gridGutterWidth;
a:hover{
text-decoration:none;
}
}
// The actual thumbnail (can be `a` or `div`)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,519 @@
/***
Spectrum Colorpicker v1.3.3
https://github.com/bgrins/spectrum
Author: Brian Grinstead
License: MIT
***/
.sp-container {
position:absolute;
top:0;
left:0;
display:inline-block;
*display: inline;
*zoom: 1;
/* https://github.com/bgrins/spectrum/issues/40 */
z-index: 9999994;
overflow: hidden;
}
.sp-container.sp-flat {
position: relative;
}
/* Fix for * { box-sizing: border-box; } */
.sp-container,
.sp-container * {
-webkit-box-sizing: content-box;
-moz-box-sizing: content-box;
box-sizing: content-box;
}
/* http://ansciath.tumblr.com/post/7347495869/css-aspect-ratio */
.sp-top {
position:relative;
width: 100%;
display:inline-block;
}
.sp-top-inner {
position:absolute;
top:0;
left:0;
bottom:0;
right:0;
}
.sp-color {
position: absolute;
top:0;
left:0;
bottom:0;
right:20%;
}
.sp-hue {
position: absolute;
top:0;
right:0;
bottom:0;
left:84%;
height: 100%;
}
.sp-clear-enabled .sp-hue {
top:33px;
height: 77.5%;
}
.sp-fill {
padding-top: 80%;
}
.sp-sat, .sp-val {
position: absolute;
top:0;
left:0;
right:0;
bottom:0;
}
.sp-alpha-enabled .sp-top {
margin-bottom: 18px;
}
.sp-alpha-enabled .sp-alpha {
display: block;
}
.sp-alpha-handle {
position:absolute;
top:-4px;
bottom: -4px;
width: 6px;
left: 50%;
cursor: pointer;
border: 1px solid black;
background: white;
opacity: .8;
}
.sp-alpha {
display: none;
position: absolute;
bottom: -14px;
right: 0;
left: 0;
height: 8px;
}
.sp-alpha-inner {
border: solid 1px #333;
}
.sp-clear {
display: none;
}
.sp-clear.sp-clear-display {
background-position: center;
}
.sp-clear-enabled .sp-clear {
display: block;
position:absolute;
top:0px;
right:0;
bottom:0;
left:84%;
height: 28px;
}
/* Don't allow text selection */
.sp-container, .sp-replacer, .sp-preview, .sp-dragger, .sp-slider, .sp-alpha, .sp-clear, .sp-alpha-handle, .sp-container.sp-dragging .sp-input, .sp-container button {
-webkit-user-select:none;
-moz-user-select: -moz-none;
-o-user-select:none;
user-select: none;
}
.sp-container.sp-input-disabled .sp-input-container {
display: none;
}
.sp-container.sp-buttons-disabled .sp-button-container {
display: none;
}
.sp-palette-only .sp-picker-container {
display: none;
}
.sp-palette-disabled .sp-palette-container {
display: none;
}
.sp-initial-disabled .sp-initial {
display: none;
}
/* Gradients for hue, saturation and value instead of images. Not pretty... but it works */
.sp-sat {
background-image: -webkit-gradient(linear, 0 0, 100% 0, from(#FFF), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(left, #FFF, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(left, #fff, rgba(204, 154, 129, 0));
background-image: linear-gradient(to right, #fff, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr=#FFFFFFFF, endColorstr=#00CC9A81)";
filter : progid:DXImageTransform.Microsoft.gradient(GradientType = 1, startColorstr='#FFFFFFFF', endColorstr='#00CC9A81');
}
.sp-val {
background-image: -webkit-gradient(linear, 0 100%, 0 0, from(#000000), to(rgba(204, 154, 129, 0)));
background-image: -webkit-linear-gradient(bottom, #000000, rgba(204, 154, 129, 0));
background-image: -moz-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -o-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: -ms-linear-gradient(bottom, #000, rgba(204, 154, 129, 0));
background-image: linear-gradient(to top, #000, rgba(204, 154, 129, 0));
-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#00CC9A81, endColorstr=#FF000000)";
filter : progid:DXImageTransform.Microsoft.gradient(startColorstr='#00CC9A81', endColorstr='#FF000000');
}
.sp-hue {
background: -moz-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -ms-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -o-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
background: -webkit-gradient(linear, left top, left bottom, from(#ff0000), color-stop(0.17, #ffff00), color-stop(0.33, #00ff00), color-stop(0.5, #00ffff), color-stop(0.67, #0000ff), color-stop(0.83, #ff00ff), to(#ff0000));
background: -webkit-linear-gradient(top, #ff0000 0%, #ffff00 17%, #00ff00 33%, #00ffff 50%, #0000ff 67%, #ff00ff 83%, #ff0000 100%);
}
/* IE filters do not support multiple color stops.
Generate 6 divs, line them up, and do two color gradients for each.
Yes, really.
*/
.sp-1 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0000', endColorstr='#ffff00');
}
.sp-2 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffff00', endColorstr='#00ff00');
}
.sp-3 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ff00', endColorstr='#00ffff');
}
.sp-4 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00ffff', endColorstr='#0000ff');
}
.sp-5 {
height:16%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#0000ff', endColorstr='#ff00ff');
}
.sp-6 {
height:17%;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff00ff', endColorstr='#ff0000');
}
.sp-hidden {
display: none !important;
}
/* Clearfix hack */
.sp-cf:before, .sp-cf:after { content: ""; display: table; }
.sp-cf:after { clear: both; }
.sp-cf { *zoom: 1; }
/* Mobile devices, make hue slider bigger so it is easier to slide */
@media (max-device-width: 480px) {
.sp-color { right: 40%; }
.sp-hue { left: 63%; }
.sp-fill { padding-top: 60%; }
}
.sp-dragger {
border-radius: 5px;
height: 5px;
width: 5px;
border: 1px solid #fff;
background: #000;
cursor: pointer;
position:absolute;
top:0;
left: 0;
}
.sp-slider {
position: absolute;
top:0;
cursor:pointer;
height: 3px;
left: -1px;
right: -1px;
border: 1px solid #000;
background: white;
opacity: .8;
}
/*
Theme authors:
Here are the basic themeable display options (colors, fonts, global widths).
See http://bgrins.github.io/spectrum/themes/ for instructions.
*/
.sp-container {
border-radius: 0;
background-color: #ECECEC;
border: solid 1px #f0c49B;
padding: 0;
}
.sp-container, .sp-container button, .sp-container input, .sp-color, .sp-hue, .sp-clear
{
font: normal 12px "Lucida Grande", "Lucida Sans Unicode", "Lucida Sans", Geneva, Verdana, sans-serif;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.sp-top
{
margin-bottom: 3px;
}
.sp-color, .sp-hue, .sp-clear
{
border: solid 1px #666;
}
/* Input */
.sp-input-container {
float:right;
width: 100px;
margin-bottom: 4px;
}
.sp-initial-disabled .sp-input-container {
width: 100%;
}
.sp-input {
font-size: 12px !important;
border: 1px inset;
padding: 4px 5px;
margin: 0;
width: 100%;
background:transparent;
border-radius: 3px;
color: #222;
}
.sp-input:focus {
border: 1px solid orange;
}
.sp-input.sp-validation-error
{
border: 1px solid red;
background: #fdd;
}
.sp-picker-container , .sp-palette-container
{
float:left;
position: relative;
padding: 10px;
padding-bottom: 300px;
margin-bottom: -290px;
}
.sp-picker-container
{
width: 172px;
border-left: solid 1px #fff;
}
/* Palettes */
.sp-palette-container
{
border-right: solid 1px #ccc;
}
.sp-palette .sp-thumb-el {
display: block;
position:relative;
float:left;
width: 24px;
height: 15px;
margin: 3px;
cursor: pointer;
border:solid 2px transparent;
}
.sp-palette .sp-thumb-el:hover, .sp-palette .sp-thumb-el.sp-thumb-active {
border-color: orange;
}
.sp-thumb-el
{
position:relative;
}
/* Initial */
.sp-initial
{
float: left;
border: solid 1px #333;
}
.sp-initial span {
width: 30px;
height: 25px;
border:none;
display:block;
float:left;
margin:0;
}
.sp-initial .sp-clear-display {
background-position: center;
}
/* Buttons */
.sp-button-container {
float: right;
}
/* Replacer (the little preview div that shows up instead of the <input>) */
.sp-replacer {
margin:0;
overflow:hidden;
cursor:pointer;
padding: 4px;
display:inline-block;
*zoom: 1;
*display: inline;
border: solid 1px #91765d;
background: #eee;
color: #333;
vertical-align: middle;
}
.sp-replacer:hover, .sp-replacer.sp-active {
border-color: #F0C49B;
color: #111;
}
.sp-replacer.sp-disabled {
cursor:default;
border-color: silver;
color: silver;
}
.sp-dd {
padding: 2px 0;
height: 16px;
line-height: 16px;
float:left;
font-size:10px;
}
.sp-preview
{
position:relative;
width:25px;
height: 20px;
border: solid 1px #222;
margin-right: 5px;
float:left;
z-index: 0;
}
.sp-palette
{
*width: 220px;
max-width: 220px;
}
.sp-palette .sp-thumb-el
{
width:16px;
height: 16px;
margin:2px 1px;
border: solid 1px #d0d0d0;
}
.sp-container
{
padding-bottom:0;
}
/* Buttons: http://hellohappy.org/css3-buttons/ */
.sp-container button {
background-color: #eeeeee;
background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc);
background-image: -moz-linear-gradient(top, #eeeeee, #cccccc);
background-image: -ms-linear-gradient(top, #eeeeee, #cccccc);
background-image: -o-linear-gradient(top, #eeeeee, #cccccc);
background-image: linear-gradient(to bottom, #eeeeee, #cccccc);
border: 1px solid #ccc;
border-bottom: 1px solid #bbb;
border-radius: 3px;
color: #333;
font-size: 14px;
line-height: 1;
padding: 5px 4px;
text-align: center;
text-shadow: 0 1px 0 #eee;
vertical-align: middle;
}
.sp-container button:hover {
background-color: #dddddd;
background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb);
background-image: -o-linear-gradient(top, #dddddd, #bbbbbb);
background-image: linear-gradient(to bottom, #dddddd, #bbbbbb);
border: 1px solid #bbb;
border-bottom: 1px solid #999;
cursor: pointer;
text-shadow: 0 1px 0 #ddd;
}
.sp-container button:active {
border: 1px solid #aaa;
border-bottom: 1px solid #888;
-webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-moz-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-ms-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
-o-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee;
}
.sp-cancel
{
font-size: 11px;
color: #d93f3f !important;
margin:0;
padding:2px;
margin-right: 5px;
vertical-align: middle;
text-decoration:none;
}
.sp-cancel:hover
{
color: #d93f3f !important;
text-decoration: underline;
}
.sp-palette span:hover, .sp-palette span.sp-thumb-active
{
border-color: #000;
}
.sp-preview, .sp-alpha, .sp-thumb-el
{
position:relative;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==);
}
.sp-preview-inner, .sp-alpha-inner, .sp-thumb-inner
{
display:block;
position:absolute;
top:0;left:0;bottom:0;right:0;
}
.sp-palette .sp-thumb-inner
{
background-position: 50% 50%;
background-repeat: no-repeat;
}
.sp-palette .sp-thumb-light.sp-thumb-active .sp-thumb-inner
{
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAIVJREFUeNpiYBhsgJFMffxAXABlN5JruT4Q3wfi/0DsT64h8UD8HmpIPCWG/KemIfOJCUB+Aoacx6EGBZyHBqI+WsDCwuQ9mhxeg2A210Ntfo8klk9sOMijaURm7yc1UP2RNCMbKE9ODK1HM6iegYLkfx8pligC9lCD7KmRof0ZhjQACDAAceovrtpVBRkAAAAASUVORK5CYII=);
}
.sp-palette .sp-thumb-dark.sp-thumb-active .sp-thumb-inner
{
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAAAMdJREFUOE+tkgsNwzAMRMugEAahEAahEAZhEAqlEAZhEAohEAYh81X2dIm8fKpEspLGvudPOsUYpxE2BIJCroJmEW9qJ+MKaBFhEMNabSy9oIcIPwrB+afvAUFoK4H0tMaQ3XtlrggDhOVVMuT4E5MMG0FBbCEYzjYT7OxLEvIHQLY2zWwQ3D+9luyOQTfKDiFD3iUIfPk8VqrKjgAiSfGFPecrg6HN6m/iBcwiDAo7WiBeawa+Kwh7tZoSCGLMqwlSAzVDhoK+6vH4G0P5wdkAAAAASUVORK5CYII=);
}
.sp-clear-display {
background-repeat:no-repeat;
background-position: center;
background-image: url(data:image/gif;base64,R0lGODlhFAAUAPcAAAAAAJmZmZ2dnZ6enqKioqOjo6SkpKWlpaampqenp6ioqKmpqaqqqqurq/Hx8fLy8vT09PX19ff39/j4+Pn5+fr6+vv7+wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAAP8ALAAAAAAUABQAAAihAP9FoPCvoMGDBy08+EdhQAIJCCMybCDAAYUEARBAlFiQQoMABQhKUJBxY0SPICEYHBnggEmDKAuoPMjS5cGYMxHW3IiT478JJA8M/CjTZ0GgLRekNGpwAsYABHIypcAgQMsITDtWJYBR6NSqMico9cqR6tKfY7GeBCuVwlipDNmefAtTrkSzB1RaIAoXodsABiZAEFB06gIBWC1mLVgBa0AAOw==);
}

File diff suppressed because one or more lines are too long

View File

@@ -92,4 +92,7 @@ td.mce-item-selected, th.mce-item-selected {
-webkit-background-size: 18px;
background-size: 18px;
background-position-x: 99%;
}
}
/* TINYMCE IMAGE RESIZING LIMITS */
#mceResizeHandlen, #mceResizeHandles, #mceResizeHandlee, #mceResizeHandlew{display: none !important; visibility: hidden !important;}

View File

@@ -364,6 +364,12 @@ Umbraco.Sys.registerNamespace("Umbraco.Application");
getRootScope().$emit("app.closeDialogs", undefined);
}
},
/* This is used for the package installer to call in order to reload all app assets so we don't have to reload the window */
_packageInstalled: function() {
var injector = getRootInjector();
var packageHelper = injector.get("packageHelper");
packageHelper.packageInstalled();
},
_debug: function(strMsg) {
if (this._isDebug) {
Sys.Debug.trace("UmbClientMgr: " + strMsg);

View File

@@ -1,2 +1,2 @@
/*yepnope1.5.x|WTFPL*/
(function (a, b, c) { function d(a) { return "[object Function]" == o.call(a) } function e(a) { return "string" == typeof a } function f() { } function g(a) { return !a || "loaded" == a || "complete" == a || "uninitialized" == a } function h() { var a = p.shift(); q = 1, a ? a.t ? m(function () { ("c" == a.t ? B.injectCss : B.injectJs)(a.s, 0, a.a, a.x, a.e, 1) }, 0) : (a(), h()) : q = 0 } function i(a, c, d, e, f, i, j) { function k(b) { if (!o && g(l.readyState) && (u.r = o = 1, !q && h(), l.onload = l.onreadystatechange = null, b)) { "img" != a && m(function () { t.removeChild(l) }, 50); for (var d in y[c]) y[c].hasOwnProperty(d) && y[c][d].onload() } } var j = j || B.errorTimeout, l = b.createElement(a), o = 0, r = 0, u = { t: d, s: c, e: f, a: i, x: j }; 1 === y[c] && (r = 1, y[c] = []), "object" == a ? l.data = c : (l.src = c, l.type = a), l.width = l.height = "0", l.onerror = l.onload = l.onreadystatechange = function () { k.call(this, r) }, p.splice(e, 0, u), "img" != a && (r || 2 === y[c] ? (t.insertBefore(l, s ? null : n), m(k, j)) : y[c].push(l)) } function j(a, b, c, d, f) { return q = 0, b = b || "j", e(a) ? i("c" == b ? v : u, a, b, this.i++, c, d, f) : (p.splice(this.i++, 0, a), 1 == p.length && h()), this } function k() { var a = B; return a.loader = { load: j, i: 0 }, a } var l = b.documentElement, m = a.setTimeout, n = b.getElementsByTagName("script")[0], o = {}.toString, p = [], q = 0, r = "MozAppearance" in l.style, s = r && !!b.createRange().compareNode, t = s ? l : n.parentNode, l = a.opera && "[object Opera]" == o.call(a.opera), l = !!b.attachEvent && !l, u = r ? "object" : l ? "script" : "img", v = l ? "script" : u, w = Array.isArray || function (a) { return "[object Array]" == o.call(a) }, x = [], y = {}, z = { timeout: function (a, b) { return b.length && (a.timeout = b[0]), a } }, A, B; B = function (a) { function b(a) { var a = a.split("!"), b = x.length, c = a.pop(), d = a.length, c = { url: c, origUrl: c, prefixes: a }, e, f, g; for (f = 0; f < d; f++) g = a[f].split("="), (e = z[g.shift()]) && (c = e(c, g)); for (f = 0; f < b; f++) c = x[f](c); return c } function g(a, e, f, g, h) { var i = b(a), j = i.autoCallback; i.url.split(".").pop().split("?").shift(), i.bypass || (e && (e = d(e) ? e : e[a] || e[g] || e[a.split("/").pop().split("?")[0]]), i.instead ? i.instead(a, e, f, g, h) : (y[i.url] ? i.noexec = !0 : y[i.url] = 1, f.load(i.url, i.forceCSS || !i.forceJS && "css" == i.url.split(".").pop().split("?").shift() ? "c" : c, i.noexec, i.attrs, i.timeout), (d(e) || d(j)) && f.load(function () { k(), e && e(i.origUrl, h, g), j && j(i.origUrl, h, g), y[i.url] = 2 }))) } function h(a, b) { function c(a, c) { if (a) { if (e(a)) c || (j = function () { var a = [].slice.call(arguments); k.apply(this, a), l() }), g(a, j, b, 0, h); else if (Object(a) === a) for (n in m = function () { var b = 0, c; for (c in a) a.hasOwnProperty(c) && b++; return b }(), a) a.hasOwnProperty(n) && (!c && !--m && (d(j) ? j = function () { var a = [].slice.call(arguments); k.apply(this, a), l() } : j[n] = function (a) { return function () { var b = [].slice.call(arguments); a && a.apply(this, b), l() } }(k[n])), g(a[n], j, b, n, h)) } else !c && l() } var h = !!a.test, i = a.load || a.both, j = a.callback || f, k = j, l = a.complete || f, m, n; c(h ? a.yep : a.nope, !!i), i && c(i) } var i, j, l = this.yepnope.loader; if (e(a)) g(a, 0, l, 0); else if (w(a)) for (i = 0; i < a.length; i++) j = a[i], e(j) ? g(j, 0, l, 0) : w(j) ? B(j) : Object(j) === j && h(j, l); else Object(a) === a && h(a, l) }, B.addPrefix = function (a, b) { z[a] = b }, B.addFilter = function (a) { x.push(a) }, B.errorTimeout = 1e4, null == b.readyState && b.addEventListener && (b.readyState = "loading", b.addEventListener("DOMContentLoaded", A = function () { b.removeEventListener("DOMContentLoaded", A, 0), b.readyState = "complete" }, 0)), a.yepnope = k(), a.yepnope.executeStack = h, a.yepnope.injectJs = function (a, c, d, e, i, j) { var k = b.createElement("script"), l, o, e = e || B.errorTimeout; k.src = a; for (o in d) k.setAttribute(o, d[o]); c = j ? h : c || f, k.onreadystatechange = k.onload = function () { !l && g(k.readyState) && (l = 1, c(), k.onload = k.onreadystatechange = null) }, m(function () { l || (l = 1, c(1)) }, e), i ? k.onload() : n.parentNode.insertBefore(k, n) }, a.yepnope.injectCss = function (a, c, d, e, g, i) { var e = b.createElement("link"), j, c = i ? h : c || f; e.href = a, e.rel = "stylesheet", e.type = "text/css"; for (j in d) e.setAttribute(j, d[j]); g || (n.parentNode.insertBefore(e, n), m(c, 0)) } })(this, document);
(function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}})(this,document);

View File

@@ -42,7 +42,6 @@
"karma-coffee-preprocessor": "0.0.1",
"karma": "~0.9",
"karma-phantomjs-launcher": "0.0.2",
"grunt-ngdocs": "~0.1.2",
"grunt-ngmin": "0.0.3"
"grunt-ngdocs": "~0.1.2"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 763 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

View File

@@ -0,0 +1,49 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbProperty
* @restrict E
**/
angular.module("umbraco.directives")
.directive('buttonGroup', function (contentEditingHelper) {
return {
scope: {
actions: "=",
handler: "="
},
transclude: true,
restrict: 'E',
replace: true,
templateUrl: 'views/directives/button-group.html',
link: function (scope, element, attrs, ctrl) {
scope.buttons = [];
scope.handle = function(action){
if(scope.handler){
}
};
function processActions() {
var buttons = [];
angular.forEach(scope.actions, function(action){
if(angular.isObject(action)){
buttons.push(action);
}else{
var btn = contentEditingHelper.getButtonFromAction(action);
if(btn){
buttons.push(btn);
}
}
});
scope.defaultButton = buttons.pop(0);
scope.buttons = buttons;
}
scope.$watchCollection(scope.actions, function(){
processActions();
});
}
};
});

View File

@@ -1,59 +0,0 @@
angular.module('umbraco.directives.editors').directive('ace', function(assetsService) {
var ACE_EDITOR_CLASS = 'ace-editor';
function loadAceEditor(element, mode) {
assetsService.loadJs("lib/ace/noconflict/ace.js").then(function(){
var editor = ace.edit($(element).find('.' + ACE_EDITOR_CLASS)[0]);
editor.session.setMode("ace/mode/" + mode);
editor.renderer.setShowPrintMargin(false);
return editor;
});
}
function valid(editor) {
return (Object.keys(editor.getSession().getAnnotations()).length === 0);
}
return {
restrict: 'A',
require: '?ngModel',
transclude: true,
template: '<div class="transcluded" ng-transclude></div><div class="' + ACE_EDITOR_CLASS + '"></div>',
link: function(scope, element, attrs, ngModel) {
function read() {
ngModel.$setViewValue(editor.getValue());
textarea.val(editor.getValue());
}
var textarea = $(element).find('textarea');
textarea.hide();
var mode = attrs.ace;
var editor = loadAceEditor(element, mode);
scope.ace = editor;
if (!ngModel)
{
return; // do nothing if no ngModel
}
ngModel.$render = function() {
var value = ngModel.$viewValue || '';
editor.getSession().setValue(value);
textarea.val(value);
};
editor.getSession().on('changeAnnotation', function() {
if (valid(editor)) {
scope.$apply(read);
}
});
editor.getSession().setValue(textarea.val());
read();
}
};
});

View File

@@ -1,33 +1,33 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbFileUpload
* @function
* @restrict A
* @scope
* @description
* A single file upload field that will reset itself based on the object passed in for the rebuild parameter. This
* is required because the only way to reset an upload control is to replace it's html.
**/
function umbSingleFileUpload($compile) {
return {
restrict: "E",
scope: {
rebuild: "="
},
replace: true,
template: "<div><input type='file' umb-file-upload /></div>",
link: function (scope, el, attrs) {
scope.$watch("rebuild", function (newVal, oldVal) {
if (newVal && newVal !== oldVal) {
//recompile it!
el.html("<input type='file' umb-file-upload />");
$compile(el.contents())(scope);
}
});
}
};
}
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbFileUpload
* @function
* @restrict A
* @scope
* @description
* A single file upload field that will reset itself based on the object passed in for the rebuild parameter. This
* is required because the only way to reset an upload control is to replace it's html.
**/
function umbSingleFileUpload($compile) {
return {
restrict: "E",
scope: {
rebuild: "="
},
replace: true,
template: "<div><input type='file' umb-file-upload /></div>",
link: function (scope, el, attrs) {
scope.$watch("rebuild", function (newVal, oldVal) {
if (newVal && newVal !== oldVal) {
//recompile it!
el.html("<input type='file' umb-file-upload />");
$compile(el.contents())(scope);
}
});
}
};
}
angular.module('umbraco.directives').directive("umbSingleFileUpload", umbSingleFileUpload);

View File

@@ -1,60 +0,0 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:fixNumber
* @restrict A
* @description Used in conjunction with type='number' input fields to ensure that the bound value is converted to a number when using ng-model
* because normally it thinks it's a string and also validation doesn't work correctly due to an angular bug.
**/
function fixNumber() {
return {
restrict: "A",
require: "ngModel",
link: function (scope, element, attr, ngModel) {
//This fixes the issue of when your model contains a number as a string (i.e. "1" instead of 1)
// which will not actually work on initial load and the browser will say you have an invalid number
// entered. So if it parses to a number, we call setViewValue which sets the bound model value
// to the real number. It should in theory update the view but it doesn't so we need to manually set
// the element's value. I'm sure there's a bug logged for this somewhere for angular too.
var modelVal = scope.$eval(attr.ngModel);
if (modelVal) {
var asNum = parseFloat(modelVal, 10);
if (!isNaN(asNum)) {
ngModel.$setViewValue(asNum);
element.val(asNum);
}
else {
ngModel.$setViewValue(null);
element.val("");
}
}
ngModel.$formatters.push(function (value) {
if (angular.isString(value)) {
return parseFloat(value);
}
return value;
});
//This fixes this angular issue:
//https://github.com/angular/angular.js/issues/2144
// which doesn't actually validate the number input properly since the model only changes when a real number is entered
// but the input box still allows non-numbers to be entered which do not validate (only via html5)
if (typeof element.prop('validity') === 'undefined') {
return;
}
element.bind('input', function (e) {
var validity = element.prop('validity');
scope.$apply(function () {
ngModel.$setValidity('number', !validity.badInput);
});
});
}
};
}
angular.module('umbraco.directives').directive("fixNumber", fixNumber);

View File

@@ -1,38 +1,42 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:hexBgColor
* @restrict A
* @description Used to set a hex background color on an element, this will detect valid hex and when it is valid it will set the color, otherwise
* a color will not be set.
**/
function hexBgColor() {
return {
restrict: "A",
link: function (scope, element, attr, formCtrl) {
var origColor = null;
if (attr.hexBgOrig) {
//set the orig based on the attribute if there is one
origColor = attr.hexBgOrig;
}
attr.$observe("hexBgColor", function (newVal) {
if (newVal) {
if (!origColor) {
//get the orig color before changing it
origColor = element.css("border-color");
}
//validate it
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
element.css("background-color", "#" + newVal);
return;
}
}
element.css("background-color", origColor);
});
}
};
}
/**
* @ngdoc directive
* @name umbraco.directives.directive:hexBgColor
* @restrict A
* @description Used to set a hex background color on an element, this will detect valid hex and when it is valid it will set the color, otherwise
* a color will not be set.
**/
function hexBgColor() {
return {
restrict: "A",
link: function (scope, element, attr, formCtrl) {
var origColor = null;
if (attr.hexBgOrig) {
//set the orig based on the attribute if there is one
origColor = attr.hexBgOrig;
}
attr.$observe("hexBgColor", function (newVal) {
if (newVal) {
if (!origColor) {
//get the orig color before changing it
origColor = element.css("border-color");
}
//validate it - test with and without the leading hash.
if (/^([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
element.css("background-color", "#" + newVal);
return;
}
if (/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(newVal)) {
element.css("background-color", newVal);
return;
}
}
element.css("background-color", origColor);
});
}
};
}
angular.module('umbraco.directives').directive("hexBgColor", hexBgColor);

View File

@@ -1,30 +1,30 @@
angular.module("umbraco.directives")
.directive('sectionIcon', function ($compile, iconHelper) {
return {
restrict: 'E',
replace: true,
link: function (scope, element, attrs) {
var icon = attrs.icon;
if (iconHelper.isLegacyIcon(icon)) {
//its a known legacy icon, convert to a new one
element.html("<i class='" + iconHelper.convertFromLegacyIcon(icon) + "'></i>");
}
else if (iconHelper.isFileBasedIcon(icon)) {
var convert = iconHelper.convertFromLegacyImage(icon);
if(convert){
element.html("<i class='icon-section " + convert + "'></i>");
}else{
element.html("<img src='images/tray/" + icon + "'>");
}
//it's a file, normally legacy so look in the icon tray images
}
else {
//it's normal
element.html("<i class='icon-section " + icon + "'></i>");
}
}
};
angular.module("umbraco.directives")
.directive('sectionIcon', function ($compile, iconHelper) {
return {
restrict: 'E',
replace: true,
link: function (scope, element, attrs) {
var icon = attrs.icon;
if (iconHelper.isLegacyIcon(icon)) {
//its a known legacy icon, convert to a new one
element.html("<i class='" + iconHelper.convertFromLegacyIcon(icon) + "'></i>");
}
else if (iconHelper.isFileBasedIcon(icon)) {
var convert = iconHelper.convertFromLegacyImage(icon);
if(convert){
element.html("<i class='icon-section " + convert + "'></i>");
}else{
element.html("<img src='images/tray/" + icon + "'>");
}
//it's a file, normally legacy so look in the icon tray images
}
else {
//it's normal
element.html("<i class='icon-section " + icon + "'></i>");
}
}
};
});

View File

@@ -1,27 +1,27 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbAvatar
* @restrict E
**/
function avatarDirective() {
return {
restrict: "E", // restrict to an element
replace: true, // replace the html element with the template
templateUrl: 'views/directives/umb-avatar.html',
scope: {
name: '@',
email: '@',
hash: '@'
},
link: function(scope, element, attr, ctrl) {
scope.$watch("hash", function (val) {
//set the gravatar url
scope.gravatar = "http://www.gravatar.com/avatar/" + val + "?s=40";
});
}
};
}
angular.module('umbraco.directives').directive("umbAvatar", avatarDirective);
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbAvatar
* @restrict E
**/
function avatarDirective() {
return {
restrict: "E", // restrict to an element
replace: true, // replace the html element with the template
templateUrl: 'views/directives/umb-avatar.html',
scope: {
name: '@',
email: '@',
hash: '@'
},
link: function(scope, element, attr, ctrl) {
scope.$watch("hash", function (val) {
//set the gravatar url
scope.gravatar = "http://www.gravatar.com/avatar/" + val + "?s=40";
});
}
};
}
angular.module('umbraco.directives').directive("umbAvatar", avatarDirective);

View File

@@ -1,79 +1,79 @@
angular.module("umbraco.directives")
.directive('umbHeader', function($parse, $timeout){
return {
restrict: 'E',
replace: true,
transclude: 'true',
templateUrl: 'views/directives/umb-header.html',
//create a new isolated scope assigning a tabs property from the attribute 'tabs'
//which is bound to the parent scope property passed in
scope: {
tabs: "="
},
link: function (scope, iElement, iAttrs) {
var maxTabs = 4;
function collectFromDom(activeTab){
var $panes = $('div.tab-content');
angular.forEach($panes.find('.tab-pane'), function (pane, index) {
var $this = angular.element(pane);
var id = $this.attr("rel");
var label = $this.attr("label");
var tab = {id: id, label: label, active: false};
if(!activeTab){
tab.active = true;
activeTab = tab;
}
if ($this.attr("rel") === String(activeTab.id)) {
$this.addClass('active');
}
else {
$this.removeClass('active');
}
if(label){
scope.visibleTabs.push(tab);
}
});
}
scope.showTabs = iAttrs.tabs ? true : false;
scope.visibleTabs = [];
scope.overflownTabs = [];
$timeout(function () {
collectFromDom(undefined);
}, 500);
//when the tabs change, we need to hack the planet a bit and force the first tab content to be active,
//unfortunately twitter bootstrap tabs is not playing perfectly with angular.
scope.$watch("tabs", function (newValue, oldValue) {
angular.forEach(newValue, function(val, index){
var tab = {id: val.id, label: val.label};
scope.visibleTabs.push(tab);
});
//don't process if we cannot or have already done so
if (!newValue) {return;}
if (!newValue.length || newValue.length === 0){return;}
var activeTab = _.find(newValue, function (item) {
return item.active;
});
//we need to do a timeout here so that the current sync operation can complete
// and update the UI, then this will fire and the UI elements will be available.
$timeout(function () {
collectFromDom(activeTab);
}, 500);
});
}
};
angular.module("umbraco.directives")
.directive('umbHeader', function($parse, $timeout){
return {
restrict: 'E',
replace: true,
transclude: 'true',
templateUrl: 'views/directives/umb-header.html',
//create a new isolated scope assigning a tabs property from the attribute 'tabs'
//which is bound to the parent scope property passed in
scope: {
tabs: "="
},
link: function (scope, iElement, iAttrs) {
var maxTabs = 4;
function collectFromDom(activeTab){
var $panes = $('div.tab-content');
angular.forEach($panes.find('.tab-pane'), function (pane, index) {
var $this = angular.element(pane);
var id = $this.attr("rel");
var label = $this.attr("label");
var tab = {id: id, label: label, active: false};
if(!activeTab){
tab.active = true;
activeTab = tab;
}
if ($this.attr("rel") === String(activeTab.id)) {
$this.addClass('active');
}
else {
$this.removeClass('active');
}
if(label){
scope.visibleTabs.push(tab);
}
});
}
scope.showTabs = iAttrs.tabs ? true : false;
scope.visibleTabs = [];
scope.overflownTabs = [];
$timeout(function () {
collectFromDom(undefined);
}, 500);
//when the tabs change, we need to hack the planet a bit and force the first tab content to be active,
//unfortunately twitter bootstrap tabs is not playing perfectly with angular.
scope.$watch("tabs", function (newValue, oldValue) {
angular.forEach(newValue, function(val, index){
var tab = {id: val.id, label: val.label};
scope.visibleTabs.push(tab);
});
//don't process if we cannot or have already done so
if (!newValue) {return;}
if (!newValue.length || newValue.length === 0){return;}
var activeTab = _.find(newValue, function (item) {
return item.active;
});
//we need to do a timeout here so that the current sync operation can complete
// and update the UI, then this will fire and the UI elements will be available.
$timeout(function () {
collectFromDom(activeTab);
}, 500);
});
}
};
});

View File

@@ -4,59 +4,11 @@
* @restrict E
**/
angular.module("umbraco.directives.html")
.directive('umbPanel', function($timeout){
.directive('umbPanel', function($timeout, $log){
return {
restrict: 'E',
replace: true,
transclude: 'true',
templateUrl: 'views/directives/html/umb-panel.html',
link: function (scope, el, attrs) {
function _setClass(resize){
var bar = $(".tab-content .active .umb-tab-buttons");
//incase this runs without any tabs
if(bar.length === 0){
bar = $(".tab-content .umb-tab-buttons");
}
//no need to process
if(resize){
bar.removeClass("umb-bottom-bar");
}
//already positioned
if(bar.hasClass("umb-bottom-bar")){
return;
}
var offset = bar.offset();
if(offset){
var bottom = bar.offset().top + bar.height();
if(bottom > $(window).height()){
bar.addClass("umb-bottom-bar");
$(".tab-content .active").addClass("with-buttons");
}else{
bar.removeClass("umb-bottom-bar");
$(".tab-content .active").removeClass("with-buttons");
}
}
}
//initial loading
$timeout(function(){
$('a[data-toggle="tab"]').on('shown', function (e) {
_setClass();
});
_setClass();
}, 1000, false);
$(window).bind("resize", function () {
_setClass(true);
});
}
templateUrl: 'views/directives/html/umb-panel.html'
};
});

View File

@@ -14,6 +14,8 @@ angular.module("umbraco.directives.html")
templateUrl: 'views/directives/html/umb-photo-folder.html',
link: function(scope, element, attrs, ngModel) {
var lastWatch = null;
ngModel.$render = function() {
if (ngModel.$modelValue) {
@@ -22,9 +24,10 @@ angular.module("umbraco.directives.html")
scope.clickHandler = scope.$eval(element.attr('on-click'));
//todo: this doesn't do anything
var imagesOnly = element.attr('imagesOnly');
var imagesOnly = element.attr('images-only') === "true";
var margin = element.attr('border') ? parseInt(element.attr('border'), 10) : 5;
var startingIndex = element.attr('baseline') ? parseInt(element.attr('baseline'), 10) : 0;
var minWidth = element.attr('min-width') ? parseInt(element.attr('min-width'), 10) : 420;
@@ -34,15 +37,22 @@ angular.module("umbraco.directives.html")
var fixedRowWidth = Math.max(element.width(), minWidth);
scope.containerStyle = { width: fixedRowWidth + "px" };
scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin);
scope.rows = umbPhotoFolderHelper.buildGrid(photos, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly);
if (attrs.filterBy) {
scope.$watch(attrs.filterBy, function(newVal, oldVal) {
if (newVal !== oldVal) {
//we track the watches that we create, we don't want to create multiple, so clear it
// if it already exists before creating another.
if (lastWatch) {
lastWatch();
}
//TODO: Need to debounce this so it doesn't filter too often!
lastWatch = scope.$watch(attrs.filterBy, function (newVal, oldVal) {
if (newVal && newVal !== oldVal) {
var p = $filter('filter')(photos, newVal, false);
scope.baseline = 0;
var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, 400);
var m = umbPhotoFolderHelper.buildGrid(p, fixedRowWidth, maxHeight, startingIndex, minHeight, idealImgPerRow, margin, imagesOnly);
scope.rows = m;
}
});

View File

@@ -1,14 +1,14 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbTab
* @restrict E
**/
angular.module("umbraco.directives")
.directive('umbTab', function(){
return {
restrict: 'E',
replace: true,
transclude: 'true',
templateUrl: 'views/directives/umb-tab.html'
};
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbTab
* @restrict E
**/
angular.module("umbraco.directives")
.directive('umbTab', function(){
return {
restrict: 'E',
replace: true,
transclude: 'true',
templateUrl: 'views/directives/umb-tab.html'
};
});

View File

@@ -1,14 +1,14 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbTabView
* @restrict E
**/
angular.module("umbraco.directives")
.directive('umbTabView', function($timeout, $log){
return {
restrict: 'E',
replace: true,
transclude: 'true',
templateUrl: 'views/directives/umb-tab-view.html'
};
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbTabView
* @restrict E
**/
angular.module("umbraco.directives")
.directive('umbTabView', function($timeout, $log){
return {
restrict: 'E',
replace: true,
transclude: 'true',
templateUrl: 'views/directives/umb-tab-view.html'
};
});

View File

@@ -0,0 +1,284 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbImageCrop
* @restrict E
* @function
**/
angular.module("umbraco.directives")
.directive('umbImageCrop',
function ($timeout, localizationService, cropperHelper, $log) {
return {
restrict: 'E',
replace: true,
templateUrl: 'views/directives/imaging/umb-image-crop.html',
scope: {
src: '=',
width: '@',
height: '@',
crop: "=",
center: "=",
maxSize: '@'
},
link: function(scope, element, attrs) {
scope.width = 400;
scope.height = 320;
scope.dimensions = {
image: {},
cropper:{},
viewport:{},
margin: 20,
scale: {
min: 0.3,
max: 3,
current: 1
}
};
//live rendering of viewport and image styles
scope.style = function () {
return {
'height': (parseInt(scope.dimensions.viewport.height, 10)) + 'px',
'width': (parseInt(scope.dimensions.viewport.width, 10)) + 'px'
};
};
//elements
var $viewport = element.find(".viewport");
var $image = element.find("img");
var $overlay = element.find(".overlay");
var $container = element.find(".crop-container");
//default constraints for drag n drop
var constraints = {left: {max: scope.dimensions.margin, min: scope.dimensions.margin}, top: {max: scope.dimensions.margin, min: scope.dimensions.margin}, };
scope.constraints = constraints;
//set constaints for cropping drag and drop
var setConstraints = function(){
constraints.left.min = scope.dimensions.margin + scope.dimensions.cropper.width - scope.dimensions.image.width;
constraints.top.min = scope.dimensions.margin + scope.dimensions.cropper.height - scope.dimensions.image.height;
};
var setDimensions = function(originalImage){
originalImage.width("auto");
originalImage.height("auto");
var image = {};
image.originalWidth = originalImage.width();
image.originalHeight = originalImage.height();
image.width = image.originalWidth;
image.height = image.originalHeight;
image.left = originalImage[0].offsetLeft;
image.top = originalImage[0].offsetTop;
scope.dimensions.image = image;
//unscaled editor size
//var viewPortW = $viewport.width();
//var viewPortH = $viewport.height();
var _viewPortW = parseInt(scope.width, 10);
var _viewPortH = parseInt(scope.height, 10);
//if we set a constraint we will scale it down if needed
if(scope.maxSize){
var ratioCalculation = cropperHelper.scaleToMaxSize(
_viewPortW,
_viewPortH,
scope.maxSize);
//so if we have a max size, override the thumb sizes
_viewPortW = ratioCalculation.width;
_viewPortH = ratioCalculation.height;
}
scope.dimensions.viewport.width = _viewPortW + 2 * scope.dimensions.margin;
scope.dimensions.viewport.height = _viewPortH + 2 * scope.dimensions.margin;
scope.dimensions.cropper.width = _viewPortW; // scope.dimensions.viewport.width - 2 * scope.dimensions.margin;
scope.dimensions.cropper.height = _viewPortH; // scope.dimensions.viewport.height - 2 * scope.dimensions.margin;
};
//when loading an image without any crop info, we center and fit it
var resizeImageToEditor = function(){
//returns size fitting the cropper
var size = cropperHelper.calculateAspectRatioFit(
scope.dimensions.image.width,
scope.dimensions.image.height,
scope.dimensions.cropper.width,
scope.dimensions.cropper.height,
true);
//sets the image size and updates the scope
scope.dimensions.image.width = size.width;
scope.dimensions.image.height = size.height;
//calculate the best suited ratios
scope.dimensions.scale.min = size.ratio;
scope.dimensions.scale.max = 2;
scope.dimensions.scale.current = size.ratio;
//center the image
var position = cropperHelper.centerInsideViewPort(scope.dimensions.image, scope.dimensions.cropper);
scope.dimensions.top = position.top;
scope.dimensions.left = position.left;
setConstraints();
};
//resize to a given ratio
var resizeImageToScale = function(ratio){
//do stuff
var size = cropperHelper.calculateSizeToRatio(scope.dimensions.image.originalWidth, scope.dimensions.image.originalHeight, ratio);
scope.dimensions.image.width = size.width;
scope.dimensions.image.height = size.height;
setConstraints();
validatePosition(scope.dimensions.image.left, scope.dimensions.image.top);
};
//resize the image to a predefined crop coordinate
var resizeImageToCrop = function(){
scope.dimensions.image = cropperHelper.convertToStyle(
scope.crop,
{width: scope.dimensions.image.originalWidth, height: scope.dimensions.image.originalHeight},
scope.dimensions.cropper,
scope.dimensions.margin);
var ratioCalculation = cropperHelper.calculateAspectRatioFit(
scope.dimensions.image.originalWidth,
scope.dimensions.image.originalHeight,
scope.dimensions.cropper.width,
scope.dimensions.cropper.height,
true);
scope.dimensions.scale.current = scope.dimensions.image.ratio;
//min max based on original width/height
scope.dimensions.scale.min = ratioCalculation.ratio;
scope.dimensions.scale.max = 2;
};
var validatePosition = function(left, top){
if(left > constraints.left.max)
{
left = constraints.left.max;
}
if(left <= constraints.left.min){
left = constraints.left.min;
}
if(top > constraints.top.max)
{
top = constraints.top.max;
}
if(top <= constraints.top.min){
top = constraints.top.min;
}
if(scope.dimensions.image.left !== left){
scope.dimensions.image.left = left;
}
if(scope.dimensions.image.top !== top){
scope.dimensions.image.top = top;
}
};
//sets scope.crop to the recalculated % based crop
var calculateCropBox = function(){
scope.crop = cropperHelper.pixelsToCoordinates(scope.dimensions.image, scope.dimensions.cropper.width, scope.dimensions.cropper.height, scope.dimensions.margin);
};
//Drag and drop positioning, using jquery ui draggable
var onStartDragPosition, top, left;
$overlay.draggable({
drag: function(event, ui) {
scope.$apply(function(){
validatePosition(ui.position.left, ui.position.top);
});
},
stop: function(event, ui){
scope.$apply(function(){
//make sure that every validates one more time...
validatePosition(ui.position.left, ui.position.top);
calculateCropBox();
scope.dimensions.image.rnd = Math.random();
});
}
});
var init = function(image){
scope.loaded = false;
//set dimensions on image, viewport, cropper etc
setDimensions(image);
//if we have a crop already position the image
if(scope.crop){
resizeImageToCrop();
}else{
resizeImageToEditor();
}
//sets constaints for the cropper
setConstraints();
scope.loaded = true;
};
/// WATCHERS ////
scope.$watchCollection('[width, height]', function(newValues, oldValues){
//we have to reinit the whole thing if
//one of the external params changes
if(newValues !== oldValues){
setDimensions($image);
setConstraints();
}
});
var throttledResizing = _.throttle(function(){
resizeImageToScale(scope.dimensions.scale.current);
calculateCropBox();
}, 100);
//happens when we change the scale
scope.$watch("dimensions.scale.current", function(){
if(scope.loaded){
throttledResizing();
}
});
//ie hack
if(window.navigator.userAgent.indexOf("MSIE ")){
var ranger = element.find("input");
ranger.bind("change",function(){
scope.$apply(function(){
scope.dimensions.scale.current = ranger.val();
});
});
}
//// INIT /////
$image.load(function(){
$timeout(function(){
init($image);
});
});
}
};
});

View File

@@ -0,0 +1,96 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbCropsy
* @restrict E
* @function
* @description
* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form.
**/
angular.module("umbraco.directives")
.directive('umbImageGravity', function ($timeout, localizationService, $log) {
return {
restrict: 'E',
replace: true,
templateUrl: 'views/directives/imaging/umb-image-gravity.html',
scope: {
src: '=',
center: "="
},
link: function(scope, element, attrs) {
//Internal values for keeping track of the dot and the size of the editor
scope.dimensions = {
width: 0,
height: 0,
left: 0,
top: 0
};
//elements
var $viewport = element.find(".viewport");
var $image = element.find("img");
var $overlay = element.find(".overlay");
scope.style = function () {
if(scope.dimensions.width <= 0){
setDimensions();
}
return {
'top': scope.dimensions.top + 'px',
'left': scope.dimensions.left + 'px'
};
};
var setDimensions = function(){
scope.dimensions.width = $image.width();
scope.dimensions.height = $image.height();
if(scope.center){
scope.dimensions.left = scope.center.left * scope.dimensions.width -10;
scope.dimensions.top = scope.center.top * scope.dimensions.height -10;
}
};
var calculateGravity = function(){
scope.dimensions.left = $overlay[0].offsetLeft;
scope.dimensions.top = $overlay[0].offsetTop;
scope.center.left = (scope.dimensions.left+10) / scope.dimensions.width;
scope.center.top = (scope.dimensions.top+10) / scope.dimensions.height;
};
var lazyEndEvent = _.debounce(function(){
scope.$apply(function(){
scope.$emit("imageFocalPointStop");
});
}, 2000);
//Drag and drop positioning, using jquery ui draggable
//TODO ensure that the point doesnt go outside the box
$overlay.draggable({
containment: "parent",
start: function(){
scope.$apply(function(){
scope.$emit("imageFocalPointStart");
});
},
stop: function() {
scope.$apply(function(){
calculateGravity();
});
lazyEndEvent();
}
});
//// INIT /////
$image.load(function(){
$timeout(function(){
setDimensions();
});
});
}
};
});

View File

@@ -0,0 +1,101 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbCropsy
* @restrict E
* @function
* @description
* Used by editors that require naming an entity. Shows a textbox/headline with a required validator within it's own form.
**/
angular.module("umbraco.directives")
.directive('umbImageThumbnail',
function ($timeout, localizationService, cropperHelper, $log) {
return {
restrict: 'E',
replace: true,
templateUrl: 'views/directives/imaging/umb-image-thumbnail.html',
scope: {
src: '=',
width: '@',
height: '@',
center: "=",
crop: "=",
maxSize: '@'
},
link: function(scope, element, attrs) {
//// INIT /////
var $image = element.find("img");
$image.load(function(){
$timeout(function(){
$image.width("auto");
$image.height("auto");
scope.image = {};
scope.image.width = $image[0].width;
scope.image.height = $image[0].height;
//we force a lower thumbnail size to fit the max size
//we do not compare to the image dimensions, but the thumbs
if(scope.maxSize){
var ratioCalculation = cropperHelper.calculateAspectRatioFit(
scope.width,
scope.height,
scope.maxSize,
scope.maxSize,
true);
//so if we have a max size, override the thumb sizes
scope.width = ratioCalculation.width;
scope.height = ratioCalculation.height;
}
setPreviewStyle();
});
});
/// WATCHERS ////
scope.$watchCollection('[crop, center]', function(newValues, oldValues){
//we have to reinit the whole thing if
//one of the external params changes
setPreviewStyle();
});
scope.$watch("center", function(){
setPreviewStyle();
}, true);
function setPreviewStyle(){
if(scope.crop && scope.image){
scope.preview = cropperHelper.convertToStyle(
scope.crop,
scope.image,
{width: scope.width, height: scope.height},
0);
}else if(scope.image){
//returns size fitting the cropper
var p = cropperHelper.calculateAspectRatioFit(
scope.image.width,
scope.image.height,
scope.width,
scope.height,
true);
if(scope.center){
var xy = cropperHelper.alignToCoordinates(p, scope.center, {width: scope.width, height: scope.height});
p.top = xy.top;
p.left = xy.left;
}else{
}
p.position = "absolute";
scope.preview = p;
}
}
}
};
});

View File

@@ -1,8 +0,0 @@
angular.module("umbraco.directives")
.directive('selectOnFocus', function () {
return function (scope, el, attrs) {
$(el).bind("click", function(){
this.select();
});
};
});

View File

@@ -65,6 +65,11 @@ function sectionsDirective($timeout, $window, navigationService, treeService, se
scope.currentSection = args.value;
}
});
eventsService.on("app.reInitialize", function (e, args) {
//re-load the sections if we're re-initializing (i.e. package installed)
loadSections();
});
//on page resize
window.onresize = calculateHeight;

View File

@@ -177,7 +177,10 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
function doLoad(tree) {
var childrenAndSelf = [tree].concat(tree.children);
scope.activeTree = _.find(childrenAndSelf, function (node) {
return node.metaData.treeAlias === treeAlias;
if(node && node.metaData){
return node.metaData.treeAlias === treeAlias;
}
return false;
});
if (!scope.activeTree) {
@@ -235,8 +238,9 @@ function umbTreeDirective($compile, $log, $q, $rootScope, treeService, notificat
//set the root as the current active tree
scope.activeTree = scope.tree.root;
emitEvent("treeLoaded", { tree: scope.tree.root });
emitEvent("treeLoaded", { tree: scope.tree });
emitEvent("treeNodeExpanded", { tree: scope.tree, node: scope.tree.root, children: scope.tree.root.children });
}, function(reason) {
scope.loading = false;
notificationsService.error("Tree Error", reason);

View File

@@ -1,38 +1,38 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:autoScale
* @element div
* @function
*
* @description
* Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set
* So if you only want to scale the div to 70 pixels from the bottom you pass "70"
*
* @example
<example module="umbraco.directives">
<file name="index.html">
<div auto-scale="70" class="input-block-level"></div>
</file>
</example>
*/
angular.module("umbraco.directives")
.directive('autoScale', function ($window) {
return function (scope, el, attrs) {
var totalOffset = 0;
var offsety = parseInt(attrs.autoScale, 10);
var window = angular.element($window);
if (offsety !== undefined){
totalOffset += offsety;
}
setTimeout(function () {
el.height(window.height() - (el.offset().top + totalOffset));
}, 500);
window.bind("resize", function () {
el.height(window.height() - (el.offset().top + totalOffset));
});
};
/**
* @ngdoc directive
* @name umbraco.directives.directive:autoScale
* @element div
* @function
*
* @description
* Resize div's automatically to fit to the bottom of the screen, as an optional parameter an y-axis offset can be set
* So if you only want to scale the div to 70 pixels from the bottom you pass "70"
*
* @example
<example module="umbraco.directives">
<file name="index.html">
<div auto-scale="70" class="input-block-level"></div>
</file>
</example>
*/
angular.module("umbraco.directives")
.directive('autoScale', function ($window) {
return function (scope, el, attrs) {
var totalOffset = 0;
var offsety = parseInt(attrs.autoScale, 10);
var window = angular.element($window);
if (offsety !== undefined){
totalOffset += offsety;
}
setTimeout(function () {
el.height(window.height() - (el.offset().top + totalOffset));
}, 500);
window.bind("resize", function () {
el.height(window.height() - (el.offset().top + totalOffset));
});
};
});

View File

@@ -0,0 +1,49 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:umbPanel
* @restrict E
**/
angular.module("umbraco.directives.html")
.directive('detectFold', function($timeout, $log){
return {
restrict: 'A',
link: function (scope, el, attrs) {
var state = false,
parent = $(".umb-panel-body"),
winHeight = $(window).height(),
calculate = _.throttle(function(){
if(el && el.is(":visible") && !el.hasClass("umb-bottom-bar")){
//var parent = el.parent();
var hasOverflow = parent.innerHeight() < parent[0].scrollHeight;
//var belowFold = (el.offset().top + el.height()) > winHeight;
if(hasOverflow){
el.addClass("umb-bottom-bar");
}
}
return state;
}, 1000);
scope.$watch(calculate, function(newVal, oldVal) {
if(newVal !== oldVal){
if(newVal){
el.addClass("umb-bottom-bar");
}else{
el.removeClass("umb-bottom-bar");
}
}
});
$(window).bind("resize", function () {
winHeight = $(window).height();
el.removeClass("umb-bottom-bar");
state = false;
calculate();
});
$('a[data-toggle="tab"]').on('shown', function (e) {
calculate();
});
}
};
});

View File

@@ -0,0 +1,55 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:fixNumber
* @restrict A
* @description Used in conjunction with type='number' input fields to ensure that the bound value is converted to a number when using ng-model
* because normally it thinks it's a string and also validation doesn't work correctly due to an angular bug.
**/
function fixNumber($parse) {
return {
restrict: "A",
require: "ngModel",
link: function (scope, elem, attrs, ctrl) {
//parse ngModel onload
var modelVal = scope.$eval(attrs.ngModel);
if (modelVal) {
var asNum = parseFloat(modelVal, 10);
if (!isNaN(asNum)) {
$parse(attrs.ngModel).assign(scope, asNum);
}
}
//always return an int to the model
ctrl.$parsers.push(function (value) {
return parseFloat(value || '', 10);
});
//always try to format the model value as an int
ctrl.$formatters.push(function (value) {
if (angular.isString(value)) {
return parseFloat(value, 10);
}
return value;
});
//This fixes this angular issue:
//https://github.com/angular/angular.js/issues/2144
// which doesn't actually validate the number input properly since the model only changes when a real number is entered
// but the input box still allows non-numbers to be entered which do not validate (only via html5)
if (typeof elem.prop('validity') === 'undefined') {
return;
}
elem.bind('input', function (e) {
var validity = elem.prop('validity');
scope.$apply(function () {
ctrl.$setValidity('number', !validity.badInput);
});
});
}
};
}
angular.module('umbraco.directives').directive("fixNumber", fixNumber);

View File

@@ -1,27 +1,28 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:headline
**/
angular.module("umbraco.directives")
.directive('hotkey', function ($window, keyboardService, $log) {
return function (scope, el, attrs) {
//support data binding
var keyCombo = scope.$eval(attrs["hotkey"]);
if (!keyCombo) {
keyCombo = attrs["hotkey"];
}
keyboardService.bind(keyCombo, function() {
var element = $(el);
if(element.is("a,button,input[type='button'],input[type='submit']")){
element.click();
}else{
element.focus();
}
});
};
/**
* @ngdoc directive
* @name umbraco.directives.directive:headline
**/
angular.module("umbraco.directives")
.directive('hotkey', function ($window, keyboardService, $log) {
return function (scope, el, attrs) {
//support data binding
var keyCombo = scope.$eval(attrs["hotkey"]);
if (!keyCombo) {
keyCombo = attrs["hotkey"];
}
keyboardService.bind(keyCombo, function() {
var element = $(el);
if(element.is("a,button,input[type='button'],input[type='submit']") && !element.is(':disabled') ){
element.click();
}else{
element.focus();
}
});
};
});

View File

@@ -1,29 +1,29 @@
/**
* @ngdoc directive
* @name umbraco.directives.directive:preventDefault
**/
angular.module("umbraco.directives")
.directive('preventDefault', function() {
return function(scope, element, attrs) {
var enabled = true;
//check if there's a value for the attribute, if there is and it's false then we conditionally don't
//prevent default.
if (attrs.preventDefault) {
attrs.$observe("preventDefault", function (newVal) {
enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true;
});
}
$(element).click(function (event) {
if (event.metaKey || event.ctrlKey) {
return;
}
else {
if (enabled === true) {
event.preventDefault();
}
}
});
};
/**
* @ngdoc directive
* @name umbraco.directives.directive:preventDefault
**/
angular.module("umbraco.directives")
.directive('preventDefault', function() {
return function(scope, element, attrs) {
var enabled = true;
//check if there's a value for the attribute, if there is and it's false then we conditionally don't
//prevent default.
if (attrs.preventDefault) {
attrs.$observe("preventDefault", function (newVal) {
enabled = (newVal === "false" || newVal === 0 || newVal === false) ? false : true;
});
}
$(element).click(function (event) {
if (event.metaKey || event.ctrlKey) {
return;
}
else {
if (enabled === true) {
event.preventDefault();
}
}
});
};
});

View File

@@ -0,0 +1,19 @@
angular.module("umbraco.directives")
.directive('selectOnFocus', function () {
return function (scope, el, attrs) {
$(el).bind("click", function () {
var editmode = $(el).data("editmode");
//If editmode is true a click is handled like a normal click
if (!editmode) {
//Initial click, select entire text
this.select();
//Set the edit mode so subsequent clicks work normally
$(el).data("editmode", true);
}
}).
bind("blur", function () {
//Reset on focus lost
$(el).data("editmode", false);
});
};
});

View File

@@ -12,7 +12,7 @@
* Another thing this directive does is to ensure that any .control-group that contains form elements that are invalid will
* be marked with the 'error' css class. This ensures that labels included in that control group are styled correctly.
**/
function valFormManager(serverValidationManager) {
function valFormManager(serverValidationManager, $rootScope, $log, $timeout, notificationsService) {
return {
require: "form",
restrict: "A",
@@ -49,8 +49,46 @@ function valFormManager(serverValidationManager) {
//listen for the forms saved event
scope.$on(savedEvent, function (ev, args) {
//remove validation class
element.removeClass(className);
//clear form state as at this point we retrieve new data from the server
//and all validation will have cleared at this point
formCtrl.$setPristine();
});
//if we wish to turn of the unsaved changes confirmation msg
//this is the place to do it
var locationEvent = $rootScope.$on('$locationChangeStart', function (event, nextLocation, currentLocation) {
if (!formCtrl.$dirty) {
return;
}
var path = nextLocation.split("#")[1];
if (path) {
if (path.indexOf("%253") || path.indexOf("%252")) {
path = decodeURIComponent(path);
}
if(!notificationsService.hasView()){
var msg = { view: "confirmroutechange", args: { path: path, listener: locationEvent } };
notificationsService.add(msg);
}
event.preventDefault();
}
});
scope.$on('$destroy', function() {
if(locationEvent){
locationEvent();
}
});
$timeout(function(){
formCtrl.$setPristine();
}, 1000);
}
};
}

View File

@@ -59,8 +59,7 @@ angular.module('umbraco.mocks').
id: 1,
active: true,
properties: [
{ alias: "list", label: "List", view: "listview", value: "", hideLabel: true },
{ alias: "media", label: "Media picker", view: "mediapicker", value: "" }
{ alias: "list", label: "List", view: "listview", value: "", hideLabel: true, config:{entityType: "content"} },
]
},
{
@@ -70,9 +69,7 @@ angular.module('umbraco.mocks').
{ alias: "valTest", label: "Validation test", view: "validationtest", value: "asdfasdf" },
{ alias: "bodyText", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>", config: {} },
{ alias: "textarea", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "map", label: "Map", view: "googlemaps", value: "37.4419,-122.1419", config: { mapType: "ROADMAP", zoom: 4 } },
{ alias: "content", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" }
{ alias: "media", label: "Media picker", view: "mediapicker", value: "1234,23242,23232,23231", config: {multiPicker: 1} }
]
},
{
@@ -90,8 +87,6 @@ angular.module('umbraco.mocks').
{ alias: "valTest4", label: "Validation test", view: "validationtest", value: "asdfasdf" },
{ alias: "bodyText4", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>", config: {} },
{ alias: "textarea4", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "map4", label: "Map", view: "googlemaps", value: "37.4419,-122.1419", config: { mapType: "ROADMAP", zoom: 4 } },
{ alias: "content4", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" }
]
},
@@ -102,8 +97,6 @@ angular.module('umbraco.mocks').
{ alias: "valTest5", label: "Validation test", view: "validationtest", value: "asdfasdf" },
{ alias: "bodyText5", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>", config: {} },
{ alias: "textarea5", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "map5", label: "Map", view: "googlemaps", value: "37.4419,-122.1419", config: { mapType: "ROADMAP", zoom: 4 } },
{ alias: "content5", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" }
]
},
@@ -114,8 +107,6 @@ angular.module('umbraco.mocks').
{ alias: "valTest6", label: "Validation test", view: "validationtest", value: "asdfasdf" },
{ alias: "bodyText6", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>", config: {} },
{ alias: "textarea6", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "map6", label: "Map", view: "googlemaps", value: "37.4419,-122.1419", config: { mapType: "ROADMAP", zoom: 4 } },
{ alias: "content6", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" }
]
},
@@ -126,8 +117,6 @@ angular.module('umbraco.mocks').
{ alias: "valTest7", label: "Validation test", view: "validationtest", value: "asdfasdf" },
{ alias: "bodyText7", label: "Body Text", description: "Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>", config: {} },
{ alias: "textarea7", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "map7", label: "Map", view: "googlemaps", value: "37.4419,-122.1419", config: { mapType: "ROADMAP", zoom: 4 } },
{ alias: "content7", label: "Content picker", view: "contentpicker", value: "1234,23242,23232,23231" }
]
},

View File

@@ -1,7 +1,7 @@
angular.module('umbraco.mocks').
factory('entityMocks', ['$httpBackend', 'mocksUtils', function ($httpBackend, mocksUtils) {
'use strict';
function returnEntitybyId(status, data, headers) {
if (!mocksUtils.checkAuth()) {
@@ -15,21 +15,21 @@ angular.module('umbraco.mocks').
return [200, node, null];
}
function returnEntitybyIds(status, data, headers) {
if (!mocksUtils.checkAuth()) {
return [401, null, null];
}
var ids = mocksUtils.getParametersByName(data, "ids") || [1234,23324,2323,23424];
var ids = mocksUtils.getParametersByName(data, "ids") || [1234, 23324, 2323, 23424];
var nodes = [];
$(ids).each(function(i, id){
var _id = parseInt(id, 10);
nodes.push(mocksUtils.getMockEntity(_id));
$(ids).each(function (i, id) {
var _id = parseInt(id, 10);
nodes.push(mocksUtils.getMockEntity(_id));
});
return [200, nodes, null];
}
@@ -41,9 +41,13 @@ angular.module('umbraco.mocks').
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetByIds'))
.respond(returnEntitybyIds);
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetAncestors'))
.respond(returnEntitybyIds);
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Entity/GetById?'))
.respond(returnEntitybyId);
.respond(returnEntitybyId);
}
};
}]);

View File

@@ -7,6 +7,17 @@ angular.module('umbraco.mocks').
return [200, nodes, null];
}
function returnNodebyIds(status, data, headers) {
var ids = mocksUtils.getParameterByName(data, "ids") || "1234,1234,4234";
var items = [];
_.each(ids, function(id){
items.push(_getNode( parseInt( id, 10 )) );
});
return [200, items, null];
}
function returnNodebyId(status, data, headers) {
if (!mocksUtils.checkAuth()) {
@@ -16,86 +27,47 @@ angular.module('umbraco.mocks').
var id = mocksUtils.getParameterByName(data, "id") || 1234;
id = parseInt(id, 10);
var node = {
name: "My content with id: " + id,
updateDate: new Date(),
publishDate: new Date(),
id: id,
parentId: 1234,
icon: "icon-file-alt",
owner: {name: "Administrator", id: 0},
updater: {name: "Per Ploug Krogslund", id: 1},
path: "-1,1234,2455",
tabs: [
{
label: "Child documents",
alias: "tab00",
id: 0,
active: true,
properties: [
{
alias: "list", label: "List", view: "listview", value: "", hideLabel: true }
]
},
{
label: "Content",
alias: "tab01",
id: 1,
properties: [
{ alias: "bodyText", label: "Body Text", description:"Here you enter the primary article contents", view: "rte", value: "<p>askjdkasj lasjd</p>" },
{ alias: "textarea", label: "textarea", view: "textarea", value: "ajsdka sdjkds", config: { rows: 4 } },
{ alias: "map", label: "Map", view: "googlemaps", value: "37.4419,-122.1419", config: { mapType: "ROADMAP", zoom: 4 } },
{ alias: "media", label: "Media picker", view: "mediapicker", value: "" },
{ alias: "content", label: "Content picker", view: "contentpicker", value: "" }
]
},
{
label: "Sample Editor",
alias: "tab02",
id: 2,
properties: [
{ alias: "datepicker", label: "Datepicker", view: "datepicker", config: { rows: 7 } },
{ alias: "tags", label: "Tags", view: "tags", value: ""}
]
},
{
label: "Grid",
alias: "tab03",
id: 3,
properties: [
{ alias: "grid", label: "Grid", view: "grid", value: "test", hideLabel: true }
]
},{
label: "WIP",
alias: "tab04",
id: 4,
properties: [
{ alias: "tes", label: "Stuff", view: "test", value: "",
config: {
fields: [
{ alias: "embedded", label: "Embbeded", view: "textstring", value: ""},
{ alias: "embedded2", label: "Embbeded 2", view: "contentpicker", value: ""},
{ alias: "embedded3", label: "Embbeded 3", view: "textarea", value: ""},
{ alias: "embedded4", label: "Embbeded 4", view: "datepicker", value: ""}
]
}
}
]
}
]
};
return [200, node, null];
return [200, _getNode(id), null];
}
function _getNode(id){
var node = {
name: "My media with id: " + id,
updateDate: new Date(),
publishDate: new Date(),
id: id,
parentId: 1234,
icon: "icon-file-alt",
owner: {name: "Administrator", id: 0},
updater: {name: "Per Ploug Krogslund", id: 1},
path: "-1,1234,2455",
tabs: [
{
label: "Media",
alias: "tab0",
id: 0,
properties: [
{ alias: "umbracoFile", label: "File", description:"Some file", view: "rte", value: "/media/1234/random.jpg" }
]
}
]
};
return node;
}
return {
register: function() {
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetById'))
.respond(returnNodebyId);
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetById?'))
.respond(returnNodebyId);
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetByIds?'))
.respond(returnNodebyIds);
$httpBackend
.whenGET(mocksUtils.urlRegex('/umbraco/UmbracoApi/Media/GetChildren'))
.respond(returnNodeCollection);

View File

@@ -0,0 +1,141 @@
/**
* @ngdoc service
* @name umbraco.mocks.mediaHelperService
* @description A helper object used for dealing with media items
**/
function mediaHelper(umbRequestHelper) {
return {
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#getImagePropertyValue
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Returns the file path associated with the media property if there is one
*
* @param {object} options Options object
* @param {object} options.mediaModel The media object to retrieve the image path from
* @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image
*/
getMediaPropertyValue: function (options) {
return "assets/img/mocks/big-image.jpg";
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#getImagePropertyValue
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Returns the actual image path associated with the image property if there is one
*
* @param {object} options Options object
* @param {object} options.imageModel The media object to retrieve the image path from
*/
getImagePropertyValue: function (options) {
return "assets/img/mocks/big-image.jpg";
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#getThumbnail
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* formats the display model used to display the content to the model used to save the content
*
* @param {object} options Options object
* @param {object} options.imageModel The media object to retrieve the image path from
*/
getThumbnail: function (options) {
if (!options || !options.imageModel) {
throw "The options objet does not contain the required parameters: imageModel";
}
var imagePropVal = this.getImagePropertyValue(options);
if (imagePropVal !== "") {
return this.getThumbnailFromPath(imagePropVal);
}
return "";
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#scaleToMaxSize
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios
*
* @param {number} maxSize Maximum width & height
* @param {number} width Current width
* @param {number} height Current height
*/
scaleToMaxSize: function (maxSize, width, height) {
var retval = { width: width, height: height };
var maxWidth = maxSize; // Max width for the image
var maxHeight = maxSize; // Max height for the image
var ratio = 0; // Used for aspect ratio
// Check if the current width is larger than the max
if (width > maxWidth) {
ratio = maxWidth / width; // get ratio for scaling image
retval.width = maxWidth;
retval.height = height * ratio;
height = height * ratio; // Reset height to match scaled image
width = width * ratio; // Reset width to match scaled image
}
// Check if current height is larger than max
if (height > maxHeight) {
ratio = maxHeight / height; // get ratio for scaling image
retval.height = maxHeight;
retval.width = width * ratio;
width = width * ratio; // Reset width to match scaled image
}
return retval;
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#getThumbnailFromPath
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Returns the path to the thumbnail version of a given media library image path
*
* @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
*/
getThumbnailFromPath: function (imagePath) {
return "assets/img/mocks/big-thumb.jpg";
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#detectIfImageByExtension
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Returns true/false, indicating if the given path has an allowed image extension
*
* @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
*/
detectIfImageByExtension: function (imagePath) {
var lowered = imagePath.toLowerCase();
var ext = lowered.substr(lowered.lastIndexOf(".") + 1);
return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1;
}
};
}
angular.module('umbraco.mocks').factory('mediaHelper', mediaHelper);

View File

@@ -18,7 +18,6 @@ function initBackEnd($httpBackend, contentMocks, mediaMocks, treeMocks, userMock
utilMocks.register();
localizationMocks.register();
prevaluesMocks.register();
entityMocks.register();
$httpBackend.whenGET(/^views\//).passThrough();

View File

@@ -15,6 +15,8 @@ Umbraco.Sys.ServerVariables = {
"authenticationApiBaseUrl": "/umbraco/UmbracoApi/Authentication/",
//For this we'll just provide a file that exists during the mock session since we don't really have legay js tree stuff
"legacyTreeJs": "/belle/lib/yepnope/empty.js",
"serverVarsJs": "/belle/lib/yepnope/empty.js",
"imagesApiBaseUrl": "/umbraco/UmbracoApi/Images/",
"entityApiBaseUrl": "/umbraco/UmbracoApi/Entity/",
"dashboardApiBaseUrl": "/umbraco/UmbracoApi/Dashboard/",
"updateCheckApiBaseUrl": "/umbraco/Api/UpdateCheck/",

View File

@@ -288,7 +288,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
"contentApiBaseUrl",
"GetById",
[{ id: id }])),
'Failed to retreive data for content id ' + id);
'Failed to retrieve data for content id ' + id);
},
/**
@@ -325,7 +325,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
"contentApiBaseUrl",
"GetByIds",
idQuery)),
'Failed to retreive data for content with multiple ids');
'Failed to retrieve data for content with multiple ids');
},
@@ -369,7 +369,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
"contentApiBaseUrl",
"GetEmpty",
[{ contentTypeAlias: alias }, { parentId: parentId }])),
'Failed to retreive data for empty content item type ' + alias);
'Failed to retrieve data for empty content item type ' + alias);
},
/**
@@ -465,7 +465,7 @@ function contentResource($q, $http, umbDataFormatter, umbRequestHelper) {
{ orderDirection: options.orderDirection },
{ filter: options.filter }
])),
'Failed to retreive children for content item ' + parentId);
'Failed to retrieve children for content item ' + parentId);
},
/**

View File

@@ -67,9 +67,9 @@ function contentTypeResource($q, $http, umbRequestHelper) {
"contentTypeApiBaseUrl",
"GetAllowedChildren",
[{ contentId: contentId }])),
'Failed to retreive data for content id ' + contentId);
'Failed to retrieve data for content id ' + contentId);
}
};
}
angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource);
angular.module('umbraco.resources').factory('contentTypeResource', contentTypeResource);

View File

@@ -45,7 +45,7 @@ function currentUserResource($q, $http, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"currentUserApiBaseUrl",
"GetMembershipProviderConfig")),
'Failed to retreive membership provider config');
'Failed to retrieve membership provider config');
},
};
}

View File

@@ -40,7 +40,7 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) {
"dataTypeApiBaseUrl",
"GetPreValues",
[{ editorAlias: editorAlias }, { dataTypeId: dataTypeId }])),
'Failed to retreive pre values for editor alias ' + editorAlias);
'Failed to retrieve pre values for editor alias ' + editorAlias);
},
/**
@@ -71,7 +71,7 @@ function dataTypeResource($q, $http, umbDataFormatter, umbRequestHelper) {
"dataTypeApiBaseUrl",
"GetById",
[{ id: id }])),
'Failed to retreive data for data type id ' + id);
'Failed to retrieve data for data type id ' + id);
},
getAll: function () {

View File

@@ -96,9 +96,19 @@ function entityResource($q, $http, umbRequestHelper) {
"entityApiBaseUrl",
"GetById",
[{ id: id}, {type: type }])),
'Failed to retreive entity data for id ' + id);
'Failed to retrieve entity data for id ' + id);
},
getByQuery: function (query, nodeContextId, type) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetByQuery",
[{query: query},{ nodeContextId: nodeContextId}, {type: type }])),
'Failed to retrieve entity data for query ' + query);
},
/**
* @ngdoc method
* @name umbraco.resources.entityResource#getByIds

View File

@@ -37,7 +37,7 @@ function logResource($q, $http, umbRequestHelper) {
"logApiBaseUrl",
"GetEntityLog",
[{ id: id }])),
'Failed to retreive user data for id ' + id);
'Failed to retrieve user data for id ' + id);
},
/**
@@ -68,7 +68,7 @@ function logResource($q, $http, umbRequestHelper) {
"logApiBaseUrl",
"GetCurrentUserLog",
[{ logtype: type, sinceDate: since }])),
'Failed to retreive user data for id ' + id);
'Failed to retrieve user data for id ' + id);
},
/**

View File

@@ -27,7 +27,7 @@ function macroResource($q, $http, umbRequestHelper) {
"macroApiBaseUrl",
"GetMacroParameters",
[{ macroId: macroId }])),
'Failed to retreive macro parameters for macro with id ' + macroId);
'Failed to retrieve macro parameters for macro with id ' + macroId);
},
/**
@@ -49,7 +49,21 @@ function macroResource($q, $http, umbRequestHelper) {
var query = "macroAlias=" + macroAlias + "&pageId=" + pageId;
if (macroParamDictionary) {
var counter = 0;
_.each(macroParamDictionary, function(val, key) {
_.each(macroParamDictionary, function (val, key) {
//check for null
val = val ? val : "";
//need to detect if the val is a string or an object
if (!angular.isString(val)) {
//if it's not a string we'll send it through the json serializer
var json = angular.toJson(val);
//then we need to url encode it so that it's safe
val = encodeURIComponent(json);
}
else {
//we still need to encode the string, it could contain line breaks, etc...
val = encodeURIComponent(val);
}
query += "&macroParams[" + counter + "].key=" + key + "&macroParams[" + counter + "].value=" + val;
counter++;
});
@@ -61,7 +75,7 @@ function macroResource($q, $http, umbRequestHelper) {
"macroApiBaseUrl",
"GetMacroResultAsHtmlForEditor",
query)),
'Failed to retreive macro result for macro with alias ' + macroAlias);
'Failed to retrieve macro result for macro with alias ' + macroAlias);
}
};

View File

@@ -137,7 +137,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
"mediaApiBaseUrl",
"GetById",
[{ id: id }])),
'Failed to retreive data for media id ' + id);
'Failed to retrieve data for media id ' + id);
},
/**
@@ -204,7 +204,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
"mediaApiBaseUrl",
"GetByIds",
idQuery)),
'Failed to retreive data for media ids ' + ids);
'Failed to retrieve data for media ids ' + ids);
},
/**
@@ -247,7 +247,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
"mediaApiBaseUrl",
"GetEmpty",
[{ contentTypeAlias: alias }, { parentId: parentId }])),
'Failed to retreive data for empty media item type ' + alias);
'Failed to retrieve data for empty media item type ' + alias);
},
@@ -258,7 +258,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"mediaApiBaseUrl",
"GetRootMedia")),
'Failed to retreive data for root media');
'Failed to retrieve data for root media');
},
@@ -326,7 +326,7 @@ function mediaResource($q, $http, umbDataFormatter, umbRequestHelper) {
{ orderDirection: options.orderDirection },
{ filter: options.filter }
])),
'Failed to retreive children for media item ' + parentId);
'Failed to retrieve children for media item ' + parentId);
},
/**

View File

@@ -34,9 +34,9 @@ function mediaTypeResource($q, $http, umbRequestHelper) {
"mediaTypeApiBaseUrl",
"GetAllowedChildren",
[{ contentId: mediaId }])),
'Failed to retreive data for media id ' + mediaId);
'Failed to retrieve data for media id ' + mediaId);
}
};
}
angular.module('umbraco.resources').factory('mediaTypeResource', mediaTypeResource);
angular.module('umbraco.resources').factory('mediaTypeResource', mediaTypeResource);

View File

@@ -53,7 +53,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
"memberApiBaseUrl",
"GetByKey",
[{ key: key }])),
'Failed to retreive data for member id ' + key);
'Failed to retrieve data for member id ' + key);
},
/**
@@ -125,7 +125,7 @@ function memberResource($q, $http, umbDataFormatter, umbRequestHelper) {
"memberApiBaseUrl",
"GetEmpty",
[{ contentTypeAlias: alias }])),
'Failed to retreive data for empty member item type ' + alias);
'Failed to retrieve data for empty member item type ' + alias);
}
else {
return umbRequestHelper.resourcePromise(

View File

@@ -15,9 +15,9 @@ function memberTypeResource($q, $http, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"memberTypeApiBaseUrl",
"GetAllTypes")),
'Failed to retreive data for member types id');
'Failed to retrieve data for member types id');
}
};
}
angular.module('umbraco.resources').factory('memberTypeResource', memberTypeResource);
angular.module('umbraco.resources').factory('memberTypeResource', memberTypeResource);

View File

@@ -20,7 +20,7 @@ function sectionResource($q, $http, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"sectionApiBaseUrl",
"GetSections")),
'Failed to retreive data for sections');
'Failed to retrieve data for sections');
}
};

View File

@@ -35,7 +35,7 @@ function stylesheetResource($q, $http, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"stylesheetApiBaseUrl",
"GetAll")),
'Failed to retreive stylesheets ');
'Failed to retrieve stylesheets ');
},
/**
@@ -63,8 +63,8 @@ function stylesheetResource($q, $http, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"stylesheetApiBaseUrl",
"GetRules",
[{ id: id }]) +"&rnd=" + Math.floor(Math.random()*1001), {cache: false}),
'Failed to retreive stylesheets ');
[{ id: id }])),
'Failed to retrieve stylesheets ');
},
/**
@@ -92,7 +92,7 @@ function stylesheetResource($q, $http, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"stylesheetApiBaseUrl",
"GetRulesByName",
[{ name: name }]) +"&rnd=" + Math.floor(Math.random()*1001), {cache: false}),
[{ name: name }])),
'Failed to retreive stylesheets ');
}
};

View File

@@ -29,7 +29,7 @@ function treeResource($q, $http, umbRequestHelper) {
return umbRequestHelper.resourcePromise(
$http.get(getTreeMenuUrl(node)),
"Failed to retreive data for a node's menu " + node.id);
"Failed to retrieve data for a node's menu " + node.id);
},
/** Loads in the data to display the nodes for an application */
@@ -60,7 +60,7 @@ function treeResource($q, $http, umbRequestHelper) {
"treeApplicationApiBaseUrl",
"GetApplicationTrees",
query)),
'Failed to retreive data for application tree ' + options.section);
'Failed to retrieve data for application tree ' + options.section);
},
/** Loads in the data to display the child nodes for a given node */
@@ -77,4 +77,4 @@ function treeResource($q, $http, umbRequestHelper) {
};
}
angular.module('umbraco.resources').factory('treeResource', treeResource);
angular.module('umbraco.resources').factory('treeResource', treeResource);

View File

@@ -41,9 +41,20 @@
* </pre>
*/
angular.module('umbraco.services')
.factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope) {
.factory('assetsService', function ($q, $log, angularHelper, umbRequestHelper, $rootScope, $http) {
var initAssetsLoaded = false;
var appendRnd = function(url){
//if we don't have a global umbraco obj yet, the app is bootstrapping
if(!Umbraco.Sys.ServerVariables.application){
return url;
}
var rnd = Umbraco.Sys.ServerVariables.isDebuggingEnabled ? (new Date()).getTime() : Umbraco.Sys.ServerVariables.application.version +"."+Umbraco.Sys.ServerVariables.application.cdf;
var _op = (url.indexOf("?")>0) ? "&" : "?";
url = url + _op + "umb__rnd=" + rnd;
return url;
};
return {
@@ -85,25 +96,23 @@ angular.module('umbraco.services')
* @param {Number} timeout in milliseconds
* @returns {Promise} Promise object which resolves when the file has loaded
*/
loadCss : function(path, scope, attributes, timeout){
var deferred = $q.defer();
var t = timeout || 5000;
var a = attributes || undefined;
loadCss : function(path, scope, attributes, timeout){
var deferred = $q.defer();
var t = timeout || 5000;
var a = attributes || undefined;
yepnope.injectCss(path, function () {
yepnope.injectCss(appendRnd(path), function () {
if (!scope) {
deferred.resolve(true);
}else{
angularHelper.safeApply(scope, function () {
deferred.resolve(true);
});
}
},a,t);
if (!scope) {
deferred.resolve(true);
}else{
angularHelper.safeApply(scope, function () {
deferred.resolve(true);
});
}
},a,t);
return deferred.promise;
},
return deferred.promise;
},
/**
* @ngdoc method
@@ -123,9 +132,8 @@ angular.module('umbraco.services')
var deferred = $q.defer();
var t = timeout || 5000;
var a = attributes || undefined;
yepnope.injectJs(path, function () {
yepnope.injectJs(appendRnd(path), function () {
if (!scope) {
deferred.resolve(true);
}else{
@@ -133,9 +141,9 @@ angular.module('umbraco.services')
deferred.resolve(true);
});
}
},a,t);
return deferred.promise;
},
@@ -155,14 +163,14 @@ angular.module('umbraco.services')
*/
load: function (pathArray, scope) {
var deferred = $q.defer();
var nonEmpty = _.reject(pathArray, function(item) {
return item === undefined || item === "";
});
//don't load anything if there's nothing to load
if (nonEmpty.length > 0) {
yepnope({
load: pathArray,
complete: function() {

View File

@@ -31,6 +31,111 @@ function contentEditingHelper($location, $routeParams, notificationsService, ser
return allProps;
},
/**
* @ngdoc method
* @name umbraco.services.contentEditingHelper#configureButtons
* @methodOf umbraco.services.contentEditingHelper
* @function
*
* @description
* Returns a letter array for buttons, with the primary one first based on content model, permissions and editor state
*/
getAllowedActions : function(content, creating){
//This is the ideal button order but depends on circumstance, we'll use this array to create the button list
// Publish, SendToPublish, Save
var actionOrder = ["U", "H", "A"];
var defaultActions;
var actions = [];
//Create the first button (primary button)
//We cannot have the Save or SaveAndPublish buttons if they don't have create permissions when we are creating a new item.
if (!creating || _.contains(content.allowedActions, "C")) {
for (var b in actionOrder) {
if (_.contains(content.allowedActions, actionOrder[b])) {
defaultAction = actionOrder[b];
break;
}
}
}
actions.push(defaultAction);
//Now we need to make the drop down button list, this is also slightly tricky because:
//We cannot have any buttons if there's no default button above.
//We cannot have the unpublish button (Z) when there's no publish permission.
//We cannot have the unpublish button (Z) when the item is not published.
if (defaultAction) {
//get the last index of the button order
var lastIndex = _.indexOf(actionOrder, defaultAction);
//add the remaining
for (var i = lastIndex + 1; i < actionOrder.length; i++) {
if (_.contains(content.allowedActions, actionOrder[i])) {
actions.push(actionOrder[i]);
}
}
//if we are not creating, then we should add unpublish too,
// so long as it's already published and if the user has access to publish
if (!creating) {
if (content.publishDate && _.contains(content.allowedActions,"U")) {
actions.push("Z");
}
}
}
return actions;
},
/**
* @ngdoc method
* @name umbraco.services.contentEditingHelper#getButtonFromAction
* @methodOf umbraco.services.contentEditingHelper
* @function
*
* @description
* Returns a button object to render a button for the tabbed editor
* currently only returns built in system buttons for content and media actions
* returns label, alias, action char and hot-key
*/
getButtonFromAction : function(ch){
switch (ch) {
case "U":
return {
letter: ch,
labelKey: "buttons_saveAndPublish",
handler: "saveAndPublish",
hotKey: "ctrl+p"
};
case "H":
//send to publish
return {
letter: ch,
labelKey: "buttons_saveToPublish",
handler: "sendToPublish",
hotKey: "ctrl+p"
};
case "A":
return {
letter: ch,
labelKey: "buttons_save",
handler: "save",
hotKey: "ctrl+s"
};
case "Z":
return {
letter: ch,
labelKey: "content_unPublish",
handler: "unPublish"
};
default:
return null;
}
},
/**
* @ngdoc method
* @name umbraco.services.contentEditingHelper#reBindChangedProperties

View File

@@ -0,0 +1,171 @@
/**
* @ngdoc service
* @name umbraco.services.cropperHelper
* @description A helper object used for dealing with image cropper data
**/
function cropperHelper(umbRequestHelper, $http) {
var service = {
/**
* @ngdoc method
* @name umbraco.services.cropperHelper#configuration
* @methodOf umbraco.services.cropperHelper
*
* @description
* Returns a collection of plugins available to the tinyMCE editor
*
*/
configuration: function (mediaTypeAlias) {
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"imageCropperApiBaseUrl",
"GetConfiguration",
[{ mediaTypeAlias: mediaTypeAlias}])),
'Failed to retrieve tinymce configuration');
},
//utill for getting either min/max aspect ratio to scale image after
calculateAspectRatioFit : function(srcWidth, srcHeight, maxWidth, maxHeight, maximize) {
var ratio = [maxWidth / srcWidth, maxHeight / srcHeight ];
if(maximize){
ratio = Math.max(ratio[0], ratio[1]);
}else{
ratio = Math.min(ratio[0], ratio[1]);
}
return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio};
},
//utill for scaling width / height given a ratio
calculateSizeToRatio : function(srcWidth, srcHeight, ratio) {
return { width:srcWidth*ratio, height:srcHeight*ratio, ratio: ratio};
},
scaleToMaxSize : function(srcWidth, srcHeight, maxSize) {
var retVal = {height: srcHeight, width: srcWidth};
if(srcWidth > maxSize ||srcHeight > maxSize){
var ratio = [maxSize / srcWidth, maxSize / srcHeight ];
ratio = Math.min(ratio[0], ratio[1]);
retVal.height = srcHeight * ratio;
retVal.width = srcWidth * ratio;
}
return retVal;
},
//returns a ng-style object with top,left,width,height pixel measurements
//expects {left,right,top,bottom} - {width,height}, {width,height}, int
//offset is just to push the image position a number of pixels from top,left
convertToStyle : function(coordinates, originalSize, viewPort, offset){
var coordinates_px = service.coordinatesToPixels(coordinates, originalSize, offset);
var _offset = offset || 0;
var x = 1 - (coordinates.x1 + Math.abs(coordinates.x2));
var left_of_x = originalSize.width * x;
var ratio = viewPort.width / left_of_x;
var style = {
position: "absolute",
top: -(coordinates_px.y1*ratio)+ _offset,
left: -(coordinates_px.x1* ratio)+ _offset,
width: Math.floor(originalSize.width * ratio),
height: Math.floor(originalSize.height * ratio),
originalWidth: originalSize.width,
originalHeight: originalSize.height,
ratio: ratio
};
return style;
},
coordinatesToPixels : function(coordinates, originalSize, offset){
var coordinates_px = {
x1: Math.floor(coordinates.x1 * originalSize.width),
y1: Math.floor(coordinates.y1 * originalSize.height),
x2: Math.floor(coordinates.x2 * originalSize.width),
y2: Math.floor(coordinates.y2 * originalSize.height)
};
return coordinates_px;
},
pixelsToCoordinates : function(image, width, height, offset){
var x1_px = Math.abs(image.left-offset);
var y1_px = Math.abs(image.top-offset);
var x2_px = image.width - (x1_px + width);
var y2_px = image.height - (y1_px + height);
//crop coordinates in %
var crop = {};
crop.x1 = x1_px / image.width;
crop.y1 = y1_px / image.height;
crop.x2 = x2_px / image.width;
crop.y2 = y2_px / image.height;
_.forEach(crop, function(coord){
if(coord < 0){
coord = 0;
}
});
return crop;
},
centerInsideViewPort : function(img, viewport){
var left = viewport.width/ 2 - img.width / 2,
top = viewport.height / 2 - img.height / 2;
return {left: left, top: top};
},
alignToCoordinates : function(image, center, viewport){
var min_left = (image.width) - (viewport.width);
var min_top = (image.height) - (viewport.height);
var c_top = -(center.top * image.height) + (viewport.height / 2);
var c_left = -(center.left * image.width) + (viewport.width / 2);
if(c_top < -min_top){
c_top = -min_top;
}
if(c_top > 0){
c_top = 0;
}
if(c_left < -min_left){
c_left = -min_left;
}
if(c_left > 0){
c_left = 0;
}
return {left: c_left, top: c_top};
},
syncElements : function(source, target){
target.height(source.height());
target.width(source.width());
target.css({
"top": source[0].offsetTop,
"left": source[0].offsetLeft
});
}
};
return service;
}
angular.module('umbraco.services').factory('cropperHelper', cropperHelper);

View File

@@ -65,7 +65,7 @@ function eventsService($q, $rootScope) {
return $rootScope.$on(name, callback);
},
/** pass in the result of subscribe to this method, or just call the method returned from subscribe to unsubscribe */
/** pass in the result of 'on' to this method, or just call the method returned from 'on' to unsubscribe */
unsubscribe: function(handle) {
if (angular.isFunction(handle)) {
handle();

View File

@@ -1,9 +1,9 @@
/**
/**
* @ngdoc service
* @name umbraco.services.imageHelper
* @description A helper object used for parsing image paths
* @deprecated
**/
function imageHelper(umbRequestHelper) {
function imageHelper(umbRequestHelper, mediaHelper) {
return {
/**
* @ngdoc function
@@ -11,63 +11,10 @@ function imageHelper(umbRequestHelper) {
* @methodOf umbraco.services.imageHelper
* @function
*
* @description
* Returns the actual image path associated with the image property if there is one
*
* @param {object} options Options object
* @param {object} options.imageModel The media object to retrieve the image path from
* @deprecated
*/
getImagePropertyValue: function (options) {
if (!options && !options.imageModel) {
throw "The options objet does not contain the required parameters: imageModel";
}
//combine all props, TODO: we really need a better way then this
var props = [];
if (options.imageModel.properties) {
props = options.imageModel.properties;
} else {
$(options.imageModel.tabs).each(function (i, tab) {
props = props.concat(tab.properties);
});
}
var mediaRoot = Umbraco.Sys.ServerVariables.umbracoSettings.mediaPath;
var imageProp = _.find(props, function (item) {
if (item.alias === "umbracoFile") {
return true;
}
//this performs a simple check to see if we have a media file as value
//it doesnt catch everything, but better then nothing
if (item.value.indexOf(mediaRoot) === 0) {
return true;
}
return false;
});
if (!imageProp) {
return "";
}
var imageVal;
//our default images might store one or many images (as csv)
var split = imageProp.value.split(',');
var self = this;
imageVal = _.map(split, function (item) {
return { file: item, isImage: self.detectIfImageByExtension(item) };
});
//for now we'll just return the first image in the collection.
//TODO: we should enable returning many to be displayed in the picker if the uploader supports many.
if (imageVal.length && imageVal.length > 0 && imageVal[0].isImage) {
return imageVal[0].file;
}
return "";
return mediaHelper.getImagePropertyValue(options);
},
/**
* @ngdoc function
@@ -75,23 +22,10 @@ function imageHelper(umbRequestHelper) {
* @methodOf umbraco.services.imageHelper
* @function
*
* @description
* formats the display model used to display the content to the model used to save the content
*
* @param {object} options Options object
* @param {object} options.imageModel The media object to retrieve the image path from
* @deprecated
*/
getThumbnail: function (options) {
if (!options && !options.imageModel) {
throw "The options objet does not contain the required parameters: imageModel";
}
var imagePropVal = this.getImagePropertyValue(options);
if (imagePropVal !== "") {
return this.getThumbnailFromPath(imagePropVal);
}
return "";
return mediaHelper.getThumbnail(options);
},
/**
@@ -100,41 +34,10 @@ function imageHelper(umbRequestHelper) {
* @methodOf umbraco.services.imageHelper
* @function
*
* @description
* Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios
*
* @param {number} maxSize Maximum width & height
* @param {number} width Current width
* @param {number} height Current height
* @deprecated
*/
scaleToMaxSize: function (maxSize, width, height) {
var retval = { width: width, height: height };
var maxWidth = maxSize; // Max width for the image
var maxHeight = maxSize; // Max height for the image
var ratio = 0; // Used for aspect ratio
// Check if the current width is larger than the max
if (width > maxWidth) {
ratio = maxWidth / width; // get ratio for scaling image
retval.width = maxWidth;
retval.height = height * ratio;
height = height * ratio; // Reset height to match scaled image
width = width * ratio; // Reset width to match scaled image
}
// Check if current height is larger than max
if (height > maxHeight) {
ratio = maxHeight / height; // get ratio for scaling image
retval.height = maxHeight;
retval.width = width * ratio;
width = width * ratio; // Reset width to match scaled image
}
return retval;
return mediaHelper.scaleToMaxSize(maxSize, width, height);
},
/**
@@ -143,23 +46,10 @@ function imageHelper(umbRequestHelper) {
* @methodOf umbraco.services.imageHelper
* @function
*
* @description
* Returns the path to the thumbnail version of a given media library image path
*
* @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
* @deprecated
*/
getThumbnailFromPath: function (imagePath) {
//get the proxy url for big thumbnails (this ensures one is always generated)
var thumbnailUrl = umbRequestHelper.getApiUrl(
"imagesApiBaseUrl",
"GetBigThumbnail",
[{ originalImagePath: imagePath }]);
//var ext = imagePath.substr(imagePath.lastIndexOf('.'));
//return imagePath.substr(0, imagePath.lastIndexOf('.')) + "_big-thumb" + ".jpg";
return thumbnailUrl;
return mediaHelper.getThumbnailFromPath(imagePath);
},
/**
@@ -168,15 +58,10 @@ function imageHelper(umbRequestHelper) {
* @methodOf umbraco.services.imageHelper
* @function
*
* @description
* Returns true/false, indicating if the given path has an allowed image extension
*
* @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
* @deprecated
*/
detectIfImageByExtension: function (imagePath) {
var lowered = imagePath.toLowerCase();
var ext = lowered.substr(lowered.lastIndexOf(".") + 1);
return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1;
return mediaHelper.detectIfImageByExtension(imagePath);
}
};
}

View File

@@ -9,11 +9,11 @@
function macroService() {
return {
/** parses the special macro syntax like <?UMBRACO_MACRO macroAlias="Map" /> and returns an object with the macro alias and it's parameters */
parseMacroSyntax: function (syntax) {
var expression = /(<\?UMBRACO_MACRO macroAlias=["'](\w+?)["'].+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/im;
var expression = /(<\?UMBRACO_MACRO macroAlias=["']([\w\.]+?)["'][\s\S]+?)(\/>|>.*?<\/\?UMBRACO_MACRO>)/i;
var match = expression.exec(syntax);
if (!match || match.length < 3) {
return null;
@@ -23,14 +23,15 @@ function macroService() {
//this will leave us with just the parameters
var paramsChunk = match[1].trim().replace(new RegExp("UMBRACO_MACRO macroAlias=[\"']" + alias + "[\"']"), "").trim();
var paramExpression = new RegExp("(\\w+?)=['\"](.*?)['\"]", "g");
var paramExpression = /(\w+?)=['\"]([\s\S]*?)['\"]/g;
var paramMatch;
var returnVal = {
macroAlias: alias,
marcoParamsDictionary: {}
macroParamsDictionary: {}
};
while (paramMatch = paramExpression.exec(paramsChunk)) {
returnVal.marcoParamsDictionary[paramMatch[1]] = paramMatch[2];
returnVal.macroParamsDictionary[paramMatch[1]] = paramMatch[2];
}
return returnVal;
},
@@ -52,10 +53,24 @@ function macroService() {
var macroString = '<?UMBRACO_MACRO macroAlias=\"' + args.macroAlias + "\" ";
if (args.marcoParamsDictionary) {
if (args.macroParamsDictionary) {
_.each(args.marcoParamsDictionary, function (val, key) {
var keyVal = key + "=\"" + (val ? val : "") + "\" ";
_.each(args.macroParamsDictionary, function (val, key) {
//check for null
val = val ? val : "";
//need to detect if the val is a string or an object
var keyVal;
if (angular.isString(val)) {
keyVal = key + "=\"" + (val ? val : "") + "\" ";
}
else {
//if it's not a string we'll send it through the json serializer
var json = angular.toJson(val);
//then we need to url encode it so that it's safe
var encoded = encodeURIComponent(json);
keyVal = key + "=\"" + encoded + "\" ";
}
macroString += keyVal;
});
@@ -81,9 +96,9 @@ function macroService() {
var macroString = '<umbraco:Macro ';
if (args.marcoParamsDictionary) {
if (args.macroParamsDictionary) {
_.each(args.marcoParamsDictionary, function (val, key) {
_.each(args.macroParamsDictionary, function (val, key) {
var keyVal = key + "=\"" + (val ? val : "") + "\" ";
macroString += keyVal;
});
@@ -112,11 +127,11 @@ function macroService() {
var hasParams = false;
var paramString;
if (args.marcoParamsDictionary) {
if (args.macroParamsDictionary) {
paramString = ", new {";
_.each(args.marcoParamsDictionary, function(val, key) {
_.each(args.macroParamsDictionary, function(val, key) {
hasParams = true;

View File

@@ -0,0 +1,288 @@
/**
* @ngdoc service
* @name umbraco.services.mediaHelper
* @description A helper object used for dealing with media items
**/
function mediaHelper(umbRequestHelper) {
//container of fileresolvers
var _mediaFileResolvers = {};
return {
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#getImagePropertyValue
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Returns the file path associated with the media property if there is one
*
* @param {object} options Options object
* @param {object} options.mediaModel The media object to retrieve the image path from
* @param {object} options.imageOnly Optional, if true then will only return a path if the media item is an image
*/
getMediaPropertyValue: function (options) {
if (!options || !options.mediaModel) {
throw "The options objet does not contain the required parameters: mediaModel";
}
//combine all props, TODO: we really need a better way then this
var props = [];
if (options.mediaModel.properties) {
props = options.mediaModel.properties;
} else {
$(options.mediaModel.tabs).each(function (i, tab) {
props = props.concat(tab.properties);
});
}
var mediaRoot = Umbraco.Sys.ServerVariables.umbracoSettings.mediaPath;
var imageProp = _.find(props, function (item) {
if (item.alias === "umbracoFile") {
return true;
}
//this performs a simple check to see if we have a media file as value
//it doesnt catch everything, but better then nothing
if (angular.isString(item.value) && item.value.indexOf(mediaRoot) === 0) {
return true;
}
return false;
});
if (!imageProp) {
return "";
}
var mediaVal;
//our default images might store one or many images (as csv)
var split = imageProp.value.split(',');
var self = this;
mediaVal = _.map(split, function (item) {
return { file: item, isImage: self.detectIfImageByExtension(item) };
});
//for now we'll just return the first image in the collection.
//TODO: we should enable returning many to be displayed in the picker if the uploader supports many.
if (mediaVal.length && mediaVal.length > 0) {
if (!options.imageOnly || (options.imageOnly === true && mediaVal[0].isImage)) {
return mediaVal[0].file;
}
}
return "";
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#getImagePropertyValue
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Returns the actual image path associated with the image property if there is one
*
* @param {object} options Options object
* @param {object} options.imageModel The media object to retrieve the image path from
*/
getImagePropertyValue: function (options) {
if (!options || (!options.imageModel && !options.mediaModel)) {
throw "The options objet does not contain the required parameters: imageModel";
}
//required to support backwards compatibility.
options.mediaModel = options.imageModel ? options.imageModel : options.mediaModel;
options.imageOnly = true;
return this.getMediaPropertyValue(options);
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#getThumbnail
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* formats the display model used to display the content to the model used to save the content
*
* @param {object} options Options object
* @param {object} options.imageModel The media object to retrieve the image path from
*/
getThumbnail: function (options) {
if (!options || !options.imageModel) {
throw "The options objet does not contain the required parameters: imageModel";
}
var imagePropVal = this.getImagePropertyValue(options);
if (imagePropVal !== "") {
return this.getThumbnailFromPath(imagePropVal);
}
return "";
},
registerFileResolver: function(propertyEditorAlias, func){
_mediaFileResolvers[propertyEditorAlias] = func;
},
/*jshint loopfunc: true */
resolveFile : function(mediaItem, thumbnail){
var _props = [];
function _iterateProps(props){
var result = null;
for(var resolver in _mediaFileResolvers) {
var property = _.find(props, function(property){ return property.editor === resolver; });
if(property){
result = _mediaFileResolvers[resolver](property, mediaItem, thumbnail);
break;
}
}
return result;
}
//we either have properties raw on the object, or spread out on tabs
var result = "";
if(mediaItem.properties){
result = _iterateProps(mediaItem.properties);
}else if(mediaItem.tabs){
for(var tab in mediaItem.tabs) {
if(mediaItem.tabs[tab].properties){
result = _iterateProps(mediaItem.tabs[tab].properties);
if(result){
break;
}
}
}
}
return result;
},
/*jshint loopfunc: true */
hasFilePropertyType : function(mediaItem){
function _iterateProps(props){
var result = false;
for(var resolver in _mediaFileResolvers) {
var property = _.find(props, function(property){ return property.editor === resolver; });
if(property){
result = true;
break;
}
}
return result;
}
//we either have properties raw on the object, or spread out on tabs
var result = false;
if(mediaItem.properties){
result = _iterateProps(mediaItem.properties);
}else if(mediaItem.tabs){
for(var tab in mediaItem.tabs) {
if(mediaItem.tabs[tab].properties){
result = _iterateProps(mediaItem.tabs[tab].properties);
if(result){
break;
}
}
}
}
return result;
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#scaleToMaxSize
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Finds the corrct max width and max height, given maximum dimensions and keeping aspect ratios
*
* @param {number} maxSize Maximum width & height
* @param {number} width Current width
* @param {number} height Current height
*/
scaleToMaxSize: function (maxSize, width, height) {
var retval = { width: width, height: height };
var maxWidth = maxSize; // Max width for the image
var maxHeight = maxSize; // Max height for the image
var ratio = 0; // Used for aspect ratio
// Check if the current width is larger than the max
if (width > maxWidth) {
ratio = maxWidth / width; // get ratio for scaling image
retval.width = maxWidth;
retval.height = height * ratio;
height = height * ratio; // Reset height to match scaled image
width = width * ratio; // Reset width to match scaled image
}
// Check if current height is larger than max
if (height > maxHeight) {
ratio = maxHeight / height; // get ratio for scaling image
retval.height = maxHeight;
retval.width = width * ratio;
width = width * ratio; // Reset width to match scaled image
}
return retval;
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#getThumbnailFromPath
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Returns the path to the thumbnail version of a given media library image path
*
* @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
*/
getThumbnailFromPath: function (imagePath) {
//If the path is not an image we cannot get a thumb
if (!this.detectIfImageByExtension(imagePath)) {
return null;
}
//get the proxy url for big thumbnails (this ensures one is always generated)
var thumbnailUrl = umbRequestHelper.getApiUrl(
"imagesApiBaseUrl",
"GetBigThumbnail",
[{ originalImagePath: imagePath }]);
//var ext = imagePath.substr(imagePath.lastIndexOf('.'));
//return imagePath.substr(0, imagePath.lastIndexOf('.')) + "_big-thumb" + ".jpg";
return thumbnailUrl;
},
/**
* @ngdoc function
* @name umbraco.services.mediaHelper#detectIfImageByExtension
* @methodOf umbraco.services.mediaHelper
* @function
*
* @description
* Returns true/false, indicating if the given path has an allowed image extension
*
* @param {string} imagePath Image path, ex: /media/1234/my-image.jpg
*/
detectIfImageByExtension: function (imagePath) {
var lowered = imagePath.toLowerCase();
var ext = lowered.substr(lowered.lastIndexOf(".") + 1);
return ("," + Umbraco.Sys.ServerVariables.umbracoSettings.imageFileTypes + ",").indexOf("," + ext + ",") !== -1;
}
};
}
angular.module('umbraco.services').factory('mediaHelper', mediaHelper);

View File

@@ -39,8 +39,8 @@ function umbracoMenuActions($q, treeService, $location, navigationService, appSt
//to find a visible tree node, we'll go get the currently loaded root node from appState
var treeRoot = appState.getTreeState("currentRootNode");
if (treeRoot) {
var treeNode = treeService.getDescendantNode(treeRoot, args.entity.id, args.treeAlias);
if (treeRoot && treeRoot.root) {
var treeNode = treeService.getDescendantNode(treeRoot.root, args.entity.id, args.treeAlias);
if (treeNode) {
treeService.loadNodeChildren({ node: treeNode, section: args.section });
}

View File

@@ -25,6 +25,18 @@ angular.module('umbraco.services')
.factory('notificationsService', function ($rootScope, $timeout, angularHelper) {
var nArray = [];
function setViewPath(view){
if(view.indexOf('/') < 0)
{
view = "views/common/notifications/" + view;
}
if(view.indexOf('.html') < 0)
{
view = view + ".html";
}
return view;
}
var service = {
@@ -40,6 +52,8 @@ angular.module('umbraco.services')
* @param {String} item.message longer text for the notication, trimmed after 200 characters, which can then be exanded
* @param {String} item.type Notification type, can be: "success","warning","error" or "info"
* @param {String} item.url url to open when notification is clicked
* @param {String} item.view path to custom view to load into the notification box
* @param {Array} item.actions Collection of button actions to append (label, func, cssClass)
* @param {Boolean} item.sticky if set to true, the notification will not auto-close
* @returns {Object} args notification object
*/
@@ -47,14 +61,22 @@ angular.module('umbraco.services')
add: function(item) {
angularHelper.safeApply($rootScope, function () {
if(item.view){
item.view = setViewPath(item.view);
item.sticky = true;
item.type = "form";
item.headline = null;
}
//add a colon after the headline if there is a message as well
if (item.message) {
item.headline += ":";
item.headline += ": ";
if(item.message.length > 200) {
item.sticky = true;
}
}
}
//we need to ID the item, going by index isn't good enough because people can remove at different indexes
// whenever they want. Plus once we remove one, then the next index will be different. The only way to
// effectively remove an item is by an Id.
@@ -81,6 +103,23 @@ angular.module('umbraco.services')
},
hasView : function(view){
if(!view){
return _.find(nArray, function(notification){ return notification.view;});
}else{
view = setViewPath(view).toLowerCase();
return _.find(nArray, function(notification){ return notification.view.toLowerCase() === view;});
}
},
addView: function(view, args){
var item = {
args: args,
view: view
};
service.add(item);
},
/**
* @ngdoc method
* @name umbraco.services.notificationsService#showNotification
@@ -205,10 +244,17 @@ angular.module('umbraco.services')
*
* @param {Int} index index where the notication should be removed from
*/
remove: function (index) {
angularHelper.safeApply($rootScope, function() {
nArray.splice(index, 1);
});
remove: function (index) {
if(angular.isObject(index)){
var i = nArray.indexOf(index);
angularHelper.safeApply($rootScope, function() {
nArray.splice(i, 1);
});
}else{
angularHelper.safeApply($rootScope, function() {
nArray.splice(index, 1);
});
}
},
/**

View File

@@ -6,7 +6,7 @@
* @description
* A service containing all logic for all of the Umbraco TinyMCE plugins
*/
function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper) {
function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macroResource, macroService, $routeParams, umbRequestHelper, angularHelper) {
return {
/**
@@ -24,7 +24,7 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
umbRequestHelper.getApiUrl(
"rteApiBaseUrl",
"GetConfiguration")),
'Failed to retreive entity data for id ');
'Failed to retrieve tinymce configuration');
},
/**
@@ -85,19 +85,35 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
icon: 'custom icon-picture',
tooltip: 'Media Picker',
onclick: function () {
var selectedElm = editor.selection.getNode(),
currentTarget;
if(selectedElm.nodeName === 'IMG'){
var img = $(selectedElm);
currentTarget = {
name: img.attr("alt"),
url: img.attr("src"),
id: img.attr("rel")
};
}
dialogService.mediaPicker({
currentTarget: currentTarget,
onlyImages: true,
showDetails: true,
scope: $scope, callback: function (img) {
if (img) {
var imagePropVal = imageHelper.getImagePropertyValue({ imageModel: img, scope: $scope });
var data = {
alt: "Some description",
src: (imagePropVal) ? imagePropVal : "nothing.jpg",
alt: img.name,
src: (img.url) ? img.url : "nothing.jpg",
rel: img.id,
id: '__mcenew'
};
editor.insertContent(editor.dom.createHTML('img', data));
$timeout(function () {
@@ -105,11 +121,16 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
var size = editor.dom.getSize(imgElm);
var newSize = imageHelper.scaleToMaxSize(500, size.w, size.h);
var s = "width: " + newSize.width + "px; height:" + newSize.height + "px;";
editor.dom.setAttrib(imgElm, 'style', s);
editor.dom.setAttrib(imgElm, 'rel', newSize.width + "," + newSize.height);
editor.dom.setAttrib(imgElm, 'id', null);
if(img.url){
var src = img.url + "?width=" + newSize.width + "&height=" + newSize.height;
editor.dom.setAttrib(imgElm, 'data-mce-src', src);
}
}, 500);
}
}
@@ -131,8 +152,6 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
*/
createLinkPicker: function (editor, $scope) {
/*
editor.addButton('link', {
icon: 'custom icon-link',
@@ -235,7 +254,9 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
var contentId = $routeParams.id;
macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.marcoParamsDictionary)
//need to wrap in safe apply since this might be occuring outside of angular
angularHelper.safeApply($scope, function() {
macroResource.getMacroResultAsHtmlForEditor(macroData.macroAlias, contentId, macroData.macroParamsDictionary)
.then(function (htmlResult) {
$macroDiv.removeClass("loading");
@@ -244,6 +265,8 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
$ins.html(htmlResult);
}
});
});
}
/** Adds the button instance */
@@ -503,4 +526,4 @@ function tinyMceService(dialogService, $log, imageHelper, $http, $timeout, macro
};
}
angular.module('umbraco.services').factory('tinyMceService', tinyMceService);
angular.module('umbraco.services').factory('tinyMceService', tinyMceService);

View File

@@ -61,7 +61,9 @@ angular.module('umbraco.services')
there are no more seconds remaining.
*/
function countdownUserTimeout() {
$timeout(function() {
$timeout(function () {
if (currentUser) {
//countdown by 2 seconds since that is how long our timer is for.
currentUser.remainingAuthSeconds -= 2;
@@ -70,18 +72,24 @@ angular.module('umbraco.services')
if (currentUser.remainingAuthSeconds > 30) {
//we need to check when the last time the timeout was set from the server, if
// it has been more than 30 seconds then we'll manually go and retreive it from the
// it has been more than 30 seconds then we'll manually go and retrieve it from the
// server - this helps to keep our local countdown in check with the true timeout.
if (lastServerTimeoutSet != null) {
var now = new Date();
var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000;
if (seconds > 30) {
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
//now go get it from the server
authResource.getRemainingTimeoutSeconds().then(function(result) {
setUserTimeoutInternal(result);
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function() {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
});
}
}
@@ -91,10 +99,10 @@ angular.module('umbraco.services')
}
else {
//we are either timed out or very close to timing out so we need to show the login dialog.
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
//we are either timed out or very close to timing out so we need to show the login dialog.
if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) {
angularHelper.safeApply($rootScope, function() {
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function () {
userAuthExpired();
});
}
@@ -105,9 +113,13 @@ angular.module('umbraco.services')
//first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we
// wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait.
lastServerTimeoutSet = null;
//now go get it from the server
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
//NOTE: the safeApply because our timeout is set to not run digests (performance reasons)
angularHelper.safeApply($rootScope, function() {
authResource.getRemainingTimeoutSeconds().then(function (result) {
setUserTimeoutInternal(result);
});
});
}

View File

@@ -1,22 +1,36 @@
/*Contains multiple services for various helper tasks */
function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, umbRequestHelper) {
return {
/** sets the image's url - will check if it is a folder or a real image */
setImageUrl: function(img) {
//get the image property (if one exists)
var imageProp = imageHelper.getImagePropertyValue({ imageModel: img });
if (!imageProp) {
img.thumbnail = "none";
}
else {
function packageHelper(assetsService, treeService, eventsService, $templateCache) {
//get the proxy url for big thumbnails (this ensures one is always generated)
var thumbnailUrl = umbRequestHelper.getApiUrl(
"imagesApiBaseUrl",
"GetBigThumbnail",
[{ mediaId: img.id }]);
img.thumbnail = thumbnailUrl;
return {
/** Called when a package is installed, this resets a bunch of data and ensures the new package assets are loaded in */
packageInstalled: function () {
//clears the tree
treeService.clearCache();
//clears the template cache
$templateCache.removeAll();
//emit event to notify anything else
eventsService.emit("app.reInitialize");
}
};
}
angular.module('umbraco.services').factory('packageHelper', packageHelper);
function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, mediaHelper, umbRequestHelper) {
return {
/** sets the image's url, thumbnail and if its a folder */
setImageData: function(img) {
img.isFolder = !mediaHelper.hasFilePropertyType(img);
if(!img.isFolder){
img.thumbnail = mediaHelper.resolveFile(img, true);
img.image = mediaHelper.resolveFile(img, false);
}
},
@@ -184,7 +198,7 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, um
//in this case, a single image will not fit into the row so we need to crop/center
// width the full width and the min display height
if (imgs.length > 1 && imageRowHeight.imgCount === 1) {
if (imageRowHeight.imgCount === 1) {
sizes.push({
width: targetWidth,
//ensure that the height is rounded
@@ -238,15 +252,22 @@ function umbPhotoFolderHelper($compile, $log, $timeout, $filter, imageHelper, um
},
/** Creates the image grid with calculated widths/heights for images to fill the grid nicely */
buildGrid: function(images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin) {
buildGrid: function(images, maxRowWidth, maxRowHeight, startingIndex, minDisplayHeight, idealImgPerRow, margin,imagesOnly) {
var rows = [];
var imagesProcessed = 0;
//first fill in all of the original image sizes and URLs
for (var i = startingIndex; i < images.length; i++) {
this.setImageUrl(images[i]);
this.setOriginalSize(images[i], maxRowHeight);
var item = images[i];
this.setImageData(item);
this.setOriginalSize(item, maxRowHeight);
if(imagesOnly && !item.isFolder && !item.thumbnail){
images.splice(i, 1);
i--;
}
}
while ((imagesProcessed + startingIndex) < images.length) {
@@ -376,7 +397,7 @@ function updateChecker($http, umbRequestHelper) {
umbRequestHelper.getApiUrl(
"updateCheckApiBaseUrl",
"GetCheck")),
'Failed to retreive update status');
'Failed to retrieve update status');
}
};
}
@@ -506,13 +527,13 @@ function umbDataFormatter() {
//we know the current property matches an alias, now we need to determine which membership provider property it was for
// by looking at the key
switch (foundAlias[0]) {
case "umbracoLockPropertyTypeAlias":
case "umbracoMemberLockedOut":
saveModel.isLockedOut = prop.value.toString() === "1" ? true : false;
break;
case "umbracoApprovePropertyTypeAlias":
case "umbracoMemberApproved":
saveModel.isApproved = prop.value.toString() === "1" ? true : false;
break;
case "umbracoCommentPropertyTypeAlias":
case "umbracoMemberComments":
saveModel.comments = prop.value;
break;
}

View File

@@ -1,6 +1,6 @@
/** Executed when the application starts, binds to events and set global state */
app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies',
function (userService, $log, $rootScope, $location, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies) {
app.run(['userService', '$log', '$rootScope', '$location', 'navigationService', 'appState', 'editorState', 'fileManager', 'assetsService', 'eventsService', '$cookies', '$templateCache',
function (userService, $log, $rootScope, $location, navigationService, appState, editorState, fileManager, assetsService, eventsService, $cookies, $templateCache) {
//This sets the default jquery ajax headers to include our csrf token, we
@@ -47,6 +47,15 @@ app.run(['userService', '$log', '$rootScope', '$location', 'navigationService',
$location.path(rejection.path).search(rejection.search);
});
/** For debug mode, always clear template cache to cut down on
dev frustration and chrome cache on templates */
if(Umbraco.Sys.ServerVariables.isDebuggingEnabled){
$rootScope.$on('$viewContentLoaded', function() {
$templateCache.removeAll();
});
}
/* this will initialize the navigation service once the application has started */
navigationService.init();

View File

@@ -0,0 +1,22 @@
yepnope({
load: [
'lib/jquery/jquery-2.0.3.min.js',
/* 1.1.5 */
'lib/angular/1.1.5/angular.min.js',
'lib/angular/1.1.5/angular-cookies.min.js',
'lib/angular/1.1.5/angular-mobile.min.js',
'lib/angular/1.1.5/angular-mocks.js',
'lib/angular/1.1.5/angular-sanitize.min.js',
'lib/underscore/underscore.js',
'js/umbraco.installer.js'
],
complete: function () {
jQuery(document).ready(function () {
angular.bootstrap(document, ['ngSanitize', 'umbraco.install']);
});
}
});

View File

@@ -0,0 +1,43 @@
angular.module("umbraco.install").controller("Umbraco.InstallerController",
function ($scope, installerService) {
//TODO: Decouple the service from the controller - the controller should be responsible
// for the model (state) and the service should be responsible for helping the controller,
// the controller should be passing the model into it's methods for manipulation and not hold
// state. We should not be assigning properties from a service to a controller's scope.
// see: https://github.com/umbraco/Umbraco-CMS/commit/b86ef0d7ac83f699aee35d807f7f7ebb6dd0ed2c#commitcomment-5721204
$scope.stepIndex = 0;
//comment this out if you just want to see tips
installerService.init();
//uncomment this to see tips
//installerService.switchToFeedback();
$scope.installer = installerService.status;
$scope.forward = function () {
installerService.forward();
};
$scope.backward = function () {
installerService.backward();
};
$scope.install = function () {
installerService.install();
};
$scope.gotoStep = function (step) {
installerService.gotoNamedStep(step);
};
$scope.restart = function () {
installerService.gotoStep(0);
};
});
//this ensure that we start with a clean slate on every install and upgrade
angular.module("umbraco.install").run(function ($templateCache) {
$templateCache.removeAll();
});

View File

@@ -0,0 +1,24 @@
<!doctype html>
<html lang="en">
<head>
<base href="/belle/" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Install Umbraco</title>
<link rel="stylesheet" href="assets/css/installer.css" />
</head>
<body ng-class="{touch:touchDevice, installing:installer.current.installing}" ng-controller="Umbraco.InstallerController" id="umbracoInstallPageBody">
<img src="assets/img/application/logo_white.png" id="logo" />
<div id="installer" class="absolute-center clearfix" ng-if="installer.current">
<div ng-include="installer.current.view"></div>
</div>
<div id="overlay"></div>
<script src="lib/yepnope/yepnope.min.js"></script>
<script src="js/install.loader.js"></script>
</body>
</html>

View File

@@ -0,0 +1,337 @@
angular.module("umbraco.install").factory('installerService', function($rootScope, $q, $timeout, $http, $location, $log){
var _status = {
index: 0,
current: undefined,
steps: undefined,
loading: true,
progress: "100%"
};
var factTimer = undefined;
var _installerModel = {
installId: undefined,
instructions: {
}
};
//add to umbraco installer facts here
var facts = ['Umbraco helped millions of people watch a man jump from the edge of space',
'Over 250.000 websites are currently powered by Umbraco',
'On an average day, more than 1.000 people download Umbraco',
'<a target="_blank" href="http://umbraco.tv">umbraco.tv</a> is the premier source of Umbraco video tutorials to get you started',
'You can find the world\'s friendliest CMS community at <a target="_blank" href="http://our.umbraco.org">our.umbraco.org</a>',
'You can become a certified Umbraco developer by attending one of the official courses',
'Umbraco works really well on tablets',
'You have 100% control over your markup and design when crafting a website in Umbraco',
'Umbraco is the best of both worlds: 100% free and open source, and backed by a professional and profitable company',
"There's a pretty big chance, you've visited a website powered by Umbraco today",
"'Umbraco-spotting' is the game of spotting big brands running Umbraco",
"At least 2 people have the Umbraco logo tattooed on them",
"'Umbraco' is the danish name for an allen key",
"Umbraco has been around since 2005, that's a looong time in IT",
"More than 400 people from all over the world meet each year in Denmark in June for our annual conference <a target='_blank' href='http://codegarden14.com'>CodeGarden</a>",
"While you install Umbraco someone else on the other side of the planet probably does it too",
"You can extend Umbraco without modifying the source code and using either JavaScript or C#"
];
/**
Returns the description for the step at a given index based on the order of the serverOrder of steps
Since they don't execute on the server in the order that they are displayed in the UI.
*/
function getDescriptionForStepAtIndex(steps, index) {
var sorted = _.sortBy(steps, "serverOrder");
if (sorted[index]) {
return sorted[index].description;
}
return null;
}
/* Returns the description for the given step name */
function getDescriptionForStepName(steps, name) {
var found = _.find(steps, function(i) {
return i.name == name;
});
return (found) ? found.description : null;
}
//calculates the offset of the progressbar on the installaer
function calculateProgress(steps, next) {
var sorted = _.sortBy(steps, "serverOrder");
var pct = "100%";
for (var i = sorted.length - 1; i >= 0; i--) {
if(sorted[i].name == next){
pct = Math.floor((i+1) / steps.length * 100) + "%";
break;
}
}
return pct;
}
//helpful defaults for the view loading
function resolveView(view){
if(view.indexOf(".html") < 0){
view = view + ".html";
}
if(view.indexOf("/") < 0){
view = "views/install/" + view;
}
return view;
}
/** Have put this here because we are not referencing our other modules */
function safeApply (scope, fn) {
if (scope.$$phase || scope.$root.$$phase) {
if (angular.isFunction(fn)) {
fn();
}
}
else {
if (angular.isFunction(fn)) {
scope.$apply(fn);
}
else {
scope.$apply();
}
}
}
var service = {
status : _status,
//loads the needed steps and sets the intial state
init : function(){
service.status.loading = true;
if(!_status.all){
service.getSteps().then(function(response){
service.status.steps = response.data.steps;
service.status.index = 0;
_installerModel.installId = response.data.installId;
service.findNextStep();
$timeout(function(){
service.status.loading = false;
service.status.configuring = true;
}, 2000);
});
}
},
//loads available packages from our.umbraco.org
getPackages : function(){
return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + "GetPackages");
},
getSteps : function(){
return $http.get(Umbraco.Sys.ServerVariables.installApiBaseUrl + "GetSetup");
},
gotoStep : function(index){
var step = service.status.steps[index];
step.view = resolveView(step.view);
if(!step.model){
step.model = {};
}
service.status.index = index;
service.status.current = step;
service.retrieveCurrentStep();
},
gotoNamedStep : function(stepName){
var step = _.find(service.status.steps, function(s, index){
if (s.view && s.name === stepName) {
service.status.index = index;
return true;
}
return false;
});
step.view = resolveView(step.view);
if(!step.model){
step.model = {};
}
service.retrieveCurrentStep();
service.status.current = step;
},
/**
Finds the next step containing a view. If one is found it stores it as the current step
and retreives the step information and returns it, otherwise returns null .
*/
findNextStep : function(){
var step = _.find(service.status.steps, function(s, index){
if(s.view && index >= service.status.index){
service.status.index = index;
return true;
}
return false;
});
if (step) {
if (step.view.indexOf(".html") < 0) {
step.view = step.view + ".html";
}
if (step.view.indexOf("/") < 0) {
step.view = "views/install/" + step.view;
}
if (!step.model) {
step.model = {};
}
service.status.current = step;
service.retrieveCurrentStep();
//returns the next found step
return step;
}
else {
//there are no more steps found containing a view so return null
return null;
}
},
storeCurrentStep : function(){
_installerModel.instructions[service.status.current.name] = service.status.current.model;
},
retrieveCurrentStep : function(){
if(_installerModel.instructions[service.status.current.name]){
service.status.current.model = _installerModel.instructions[service.status.current.name];
}
},
/** Moves the installer forward to the next view, if there are not more views than the installation will commence */
forward : function(){
service.storeCurrentStep();
service.status.index++;
var found = service.findNextStep();
if (!found) {
//no more steps were found so start the installation process
service.install();
}
},
backwards : function(){
service.storeCurrentStep();
service.gotoStep(service.status.index--);
},
install : function(){
service.storeCurrentStep();
service.switchToFeedback();
service.status.feedback = getDescriptionForStepAtIndex(service.status.steps, 0);
service.status.progress = 0;
function processInstallStep() {
$http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostPerformInstall", _installerModel)
.success(function(data, status, headers, config) {
if (!data.complete) {
//progress feedback
service.status.progress = calculateProgress(service.status.steps, data.nextStep);
if (data.view) {
//set the current view and model to whatever the process returns, the view is responsible for retriggering install();
var v = resolveView(data.view);
service.status.current = { view: v, model: data.model };
//turn off loading bar and feedback
service.switchToConfiguration();
}
else {
var desc = getDescriptionForStepName(service.status.steps, data.nextStep);
if (desc) {
service.status.feedback = desc;
}
processInstallStep();
}
}
else {
service.complete();
}
}).error(function(data, status, headers, config) {
//need to handle 500's separately, this will happen if something goes wrong outside
// of the installer (like app startup events or something) and these will get returned as text/html
// not as json. If this happens we can't actually load in external views since they will YSOD as well!
// so we need to display this in our own internal way
if (status >= 500 && status < 600) {
service.status.current = { view: "ysod", model: null };
var ysod = data;
//we need to manually write the html to the iframe - the html contains full html markup
$timeout(function () {
document.getElementById('ysod').contentDocument.write(ysod);
}, 500);
}
else {
//this is where we handle installer error
var v = data.view ? resolveView(data.view) : resolveView("error");
var model = data.model ? data.model : data;
service.status.current = { view: v, model: model };
}
service.switchToConfiguration();
});
}
processInstallStep();
},
randomFact: function () {
safeApply($rootScope, function() {
service.status.fact = facts[_.random(facts.length - 1)];
});
},
switchToFeedback : function(){
service.status.current = undefined;
service.status.loading = true;
service.status.configuring = false;
//initial fact
service.randomFact();
//timed facts
factTimer = window.setInterval(function(){
service.randomFact();
},6000);
},
switchToConfiguration : function(){
service.status.loading = false;
service.status.configuring = true;
service.status.feedback = undefined;
service.status.fact = undefined;
if(factTimer){
clearInterval(factTimer);
}
},
complete : function(){
service.status.progress = "100%";
service.status.done = true;
service.status.feedback = "Redirecting you to Umbraco, please wait";
service.status.loading = false;
if(factTimer){
clearInterval(factTimer);
}
$timeout(function(){
window.location.href = Umbraco.Sys.ServerVariables.umbracoBaseUrl;
}, 1500);
}
};
return service;
});

View File

@@ -0,0 +1,13 @@
<div>
<h1>Continue Umbraco Installation</h1>
<p>
You see this screen because your Umbraco installation did not complete correctly.
</p>
<p>
Simply click <strong>continue</strong> below to be guided through the rest of the installation process.
</p>
<p>
<button class="btn btn-success" ng-click="install()">Continue</button>
</p>
</div>

View File

@@ -0,0 +1,35 @@
angular.module("umbraco.install").controller("Umbraco.Installer.DataBaseController", function($scope, $http, installerService){
$scope.checking = false;
$scope.dbs = [
{name: 'Microsoft SQL Server Compact (SQL CE)', id: 0},
{name: 'Microsoft SQL Server', id: 1},
{name: 'MySQL', id: 2},
{name: 'Custom connection string', id: -1}];
if(installerService.status.current.model.dbType === undefined){
installerService.status.current.model.dbType = 0;
}
$scope.validateAndForward = function(){
if(!$scope.checking && this.myForm.$valid){
$scope.checking = true;
var model = installerService.status.current.model;
$http.post(Umbraco.Sys.ServerVariables.installApiBaseUrl + "PostValidateDatabaseConnection",
model).then(function(response){
if(response.data === "true"){
installerService.forward();
}else{
$scope.invalidDbDns = true;
}
$scope.checking = false;
}, function(){
$scope.invalidDbDns = true;
$scope.checking = false;
});
}
};
});

View File

@@ -0,0 +1,121 @@
<div ng-controller="Umbraco.Installer.DataBaseController">
<h1>Configure your database</h1>
<p>
Enter connection and authentication details for the database you want to install Umbraco on
</p>
<form name="myForm" class="form-horizontal" novalidate ng-submit="validateAndForward();">
<div class="control-group">
<legend>What type of database do you use?</legend>
<label class="control-label" for="dbType">Database type</label>
<div class="controls">
<select name="dbType" ng-options="db.id as db.name for db in dbs" ng-model="installer.current.model.dbType" required>
</select>
</div>
</div>
<div class="controls" ng-if="installer.current.model.dbType == 0">
<p>Great!, no need to configure anything then, you simply click the <strong>continue</strong> button below to continue to the next step</p>
</div>
<div ng-if="installer.current.model.dbType < 0">
<legend>What is the exact connectionstring we should use?</legend>
<div class="control-group">
<label class="control-label" for="server">Connection string</label>
<div class="controls">
<textarea class="input-block-level" required ng-model="installer.current.model.connectionString" rows="5">
</textarea>
<small class="inline-help">Enter a valid database connection string.</small>
</div>
</div>
</div>
<div ng-if="installer.current.model.dbType > 0">
<div class="row">
<legend>Where do we find your database?</legend>
<div class="span6">
<div class="control-group">
<label class="control-label" for="server">Server</label>
<div class="controls">
<input type="text" name="server" placeholder="127.0.0.1/SQLEXPRESS" required ng-model="installer.current.model.server" />
<small class="inline-help">Enter server domain or IP</small>
</div>
</div>
</div>
<div class="span6">
<div class="control-group">
<label class="control-label" for="databaseName">Database name</label>
<div class="controls">
<input type="text" name="installer.current.model.databaseName"
placeholder="umbraco-cms"
required ng-model="installer.current.model.databaseName" />
<small class="inline-help">Enter the name of the database</small>
</div>
</div>
</div>
</div>
<div class="row">
<legend>What credentials are used to access the database?</legend>
<div class="span6">
<div class="control-group" ng-if="!installer.current.model.integratedAuth">
<label class="control-label" for="login">Login</label>
<div class="controls">
<input type="text" name="login"
placeholder="databaseuser"
required ng-model="installer.current.model.login" />
<small class="inline-help">Enter the database user name</small>
</div>
</div>
</div>
<div class="span6">
<div class="control-group" ng-if="!installer.current.model.integratedAuth">
<label class="control-label" for="password">Password</label>
<div class="controls">
<input type="password" name="password"
placeholder="umbraco-cms"
required ng-model="installer.current.model.password" />
<small class="inline-help">Enter the database password</small>
</div>
</div>
</div>
<div class="span12 control-group" ng-if="installer.current.model.dbType == 1">
<div class="controls">
<label class="checkbox" for="integratedAuth">
<input type="checkbox" name="integratedAuth"
placeholder="umbraco-cms"
ng-model="installer.current.model.integratedAuth" /> Use integrated authentication</label>
</div>
</div>
</div>
</div>
<div class="row">
<div class="control-group" ng-class="{disabled:myForm.$invalid}">
<div class="controls">
<input type="submit" ng-disabled="myForm.$invalid || checking"
value="Continue" class="btn btn-success" />
<span class="inline-help" ng-if="checking" ng-animate="'fade'">
Validating your database connection...
</span>
<span class="inline-help error" ng-if="invalidDbDns" ng-animate="'fade'">
Could not connect to database
</span>
</div>
</div>
</div>
</form>
</div>

View File

@@ -0,0 +1,9 @@
<div class="error">
<h1>Error during installation</h1>
<p class="message">{{installer.current.model.message}}</p>
<p><small>See the log for full details (logs can typically be found in the App_Data\Logs folder).</small></p>
<button class="btn btn-success" ng-click="restart()">Go back</button>
</div>

View File

@@ -0,0 +1,26 @@
<div>
<h1>Your permission settings are not ready for umbraco</h1>
<p>
In order to run umbraco, you'll need to update your permission settings.
Detailed information about the correct file & folder permissions for Umbraco can be found
<a href="http://our.umbraco.org/documentation/Installation/permissions"><strong>here</strong></a>.
</p>
<p>
The following report list the permissions that are currently failing. Once the permissions are fixed press the 'Go back' button to restart the installation.
</p>
<ul class="permissions-report">
<li ng-repeat="(category, items) in installer.current.model.errors">
<h4>{{category}}</h4>
<ul>
<li ng-repeat="item in items">
{{item}}
</li>
</ul>
</li>
</ul>
<p>
<button class="btn btn-success" ng-click="restart()">Go back</button>
</p>
</div>

View File

@@ -0,0 +1,12 @@
angular.module("umbraco.install").controller("Umbraco.Installer.PackagesController", function ($scope, installerService) {
installerService.getPackages().then(function (response) {
$scope.packages = response.data;
});
$scope.setPackageAndContinue = function (pckId) {
installerService.status.current.model = pckId;
installerService.forward();
};
});

View File

@@ -0,0 +1,20 @@
<div ng-controller="Umbraco.Installer.PackagesController">
<h1>Install a starter website</h1>
<p>
Installing a starter website helps you learn how Umbraco works, and gives you a solid
and simple foundation to build on top of.
</p>
<ul class="thumbnails">
<li class="span3" ng-repeat="pck in packages">
<a href ng-click="setPackageAndContinue(pck.id)" class="thumbnail">
<img ng-src="http://our.umbraco.org{{pck.thumbnail}}" alt="{{pck.name}}">
</a>
</li>
</ul>
<a href ng-click="setPackageAndContinue('00000000-0000-0000-0000-000000000000')" class="btn btn-link">
No thanks, I do not want to install a starter website
</a>
</div>

View File

@@ -0,0 +1,13 @@
<div>
<h1>Upgrading Umbraco</h1>
<p>
Welcome to the Umbraco installer. You see this screen because your Umbraco installation needs a quick upgrade of its database and files, which will ensure your website is kept as fast, secure and up to date as possible.
</p>
<p>
Simply click <strong>continue</strong> below to be guided through the rest of the upgrade
</p>
<p>
<button class="btn btn-success" ng-click="install()">Continue</button>
</p>
</div>

View File

@@ -0,0 +1,26 @@
angular.module("umbraco.install").controller("Umbraco.Install.UserController", function($scope, installerService) {
$scope.passwordPattern = /.*/;
$scope.installer.current.model.subscribeToNewsLetter = true;
if ($scope.installer.current.model.minNonAlphaNumericLength > 0) {
var exp = "";
for (var i = 0; i < $scope.installer.current.model.minNonAlphaNumericLength; i++) {
exp += ".*[\\W].*";
}
//replace duplicates
exp = exp.replace(".*.*", ".*");
$scope.passwordPattern = new RegExp(exp);
}
$scope.validateAndInstall = function(){
installerService.install();
};
$scope.validateAndForward = function(){
if(this.myForm.$valid){
installerService.forward();
}
};
});

View File

@@ -0,0 +1,69 @@
<div ng-controller="Umbraco.Install.UserController">
<h1>Install Umbraco 7</h1>
<p>Enter your name, email and password to install Umbraco 7 with its default settings, alternatively you can customize your installation</p>
<form name="myForm" class="form-horizontal" novalidate ng-submit="validateAndInstall();">
<div class="row">
<div class="span8">
<div class="pull-right">
<div class="control-group">
<label class="control-label" for="name">Name</label>
<div class="controls">
<input type="text" name="name" placeholder="Full name" required
ng-model="installer.current.model.name" />
</div>
</div>
<div class="control-group">
<label class="control-label" for="email">Email</label>
<div class="controls">
<input type="email" name="email" placeholder="you@example.com" required ng-model="installer.current.model.email" />
<small class="inline-help">Your email will be used as your login</small>
</div>
</div>
<div class="control-group">
<label class="control-label" for="password">Password</label>
<div class="controls">
<!-- why isn't this masked: http://www.nngroup.com/articles/stop-password-masking/ -->
<input type="text" name="installer.current.model.password"
ng-minlength="{{installer.current.model.minCharLength}}"
ng-pattern="passwordPattern"
autocorrect="off"
autocapitalize="off"
required
ng-model="installer.current.model.password" />
<small class="inline-help">At least {{installer.current.model.minCharLength}} characters long</small>
<small ng-if="installer.current.model.minNonAlphaNumericLength > 0" class="inline-help">
At least {{installer.current.model.minNonAlphaNumericLength}} symbol{{installer.current.model.minNonAlphaNumericLength > 1 ? 's' : ''}}
</small>
</div>
</div>
<div class="control-group">
<div class="controls">
<label class="checkbox" for="subscribeToNewsLetter">
<input type="checkbox" name="subscribeToNewsLetter"
ng-model="installer.current.model.subscribeToNewsLetter" /> Keep me updated on Umbraco Versions, Security Bulletins and Community News</label>
</div>
</div>
<div class="control-group" ng-class="{disabled:myForm.$invalid}">
<div class="controls">
<input type="submit" ng-disabled="myForm.$invalid" value="Install" class="btn btn-success" />
<a href class="btn btn-link" ng-disabled="myForm.$invalid" ng-click="validateAndForward()">Customize</a>
</div>
</div>
</div>
</div>
</div>
</form>
</div>

View File

@@ -0,0 +1,22 @@
<div>
<h1>Major version upgrade from {{installer.current.model.currentVersion}} to {{installer.current.model.newVersion}}</h1>
<h2>There were {{installer.current.model.errors.length}} issues detected</h2>
<p>
The following compatibility issues were found. If you continue all non-compatible property editors will be converted to a Readonly/Label.
You will be able to change the property editor to a compatible type manually by editing the data type after installation.
</p>
<p>
Otherwise if you choose not to proceed you will need to fix the errors listed below.
Refer to v{{installer.current.model.newVersion}} upgrade instructions for full details.
</p>
<ul class="upgrade-report">
<li ng-repeat="item in installer.current.model.errors">
{{item}}
</li>
</ul>
<p>
<button class="btn btn-success" ng-click="forward()">Continue</button>
</p>
</div>

View File

@@ -62,6 +62,16 @@
color: @infoText;
}
.alert-form {
background-color: @grayLighter;
border: 1px solid @gray;
color: @gray;
}
.alert-form h4 {
color: @gray;
}
// Block alerts
// -------------------------

View File

@@ -1,21 +1,23 @@
// Animations
// -------------------------
.fade-hide, .fade-show {
.fade-hide, .fade-show , .fade-leave, .fade-enter {
-webkit-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.3s;
-moz-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.3s;
-o-transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.3s;
transition: all cubic-bezier(0.250, 0.460, 0.450, 0.940) 0.3s;
}
.fade-hide {
.fade-enter {
opacity: 1;
}
.fade-hide.fade-hide-active {
opacity: 0;
}
.fade-show {
.fade-leave {
opacity: 0;
}
.fade-show.fade-show-active {
opacity: 1;
}
@@ -63,6 +65,7 @@
display: block;
position: relative;
}
@-webkit-keyframes leave {
to {
opacity: 0;

View File

@@ -48,6 +48,7 @@
@import "../../lib/bootstrap/less/modals.less";
@import "../../lib/bootstrap/less/tooltip.less";
@import "../../lib/bootstrap/less/popovers.less";
@import "tipmenu.less";
// Components: Misc
@import "../../lib/bootstrap/less/thumbnails.less";

View File

@@ -72,6 +72,7 @@
}
// Button Sizes
// --------------------------------------------------
@@ -134,6 +135,23 @@ input[type="button"] {
}
}
// Round button
.btn-round{
font-size: 24px;
color: @gray;
background: @white;
line-height: 32px;
text-align: center;
-moz-border-radius: 15px;
border-radius: 15px;
height: 32px;
width: 32px;
overflow: hidden;
text-decoration: none;
display: inline-block;
z-index: 6666;
}
// Alternate buttons

View File

@@ -156,7 +156,7 @@ body {
#speechbubble {
z-index: 1000;
position: absolute;
bottom: 20px;
bottom: 40px;
left: 0;
right: 0;
border-bottom: none;

Some files were not shown because too many files have changed in this diff Show More