Merge branch v8/dev into v8/bugfix/805-migrate-from-7

This commit is contained in:
Stephan
2019-05-16 13:58:32 +02:00
40 changed files with 995 additions and 609 deletions
+1
View File
@@ -161,3 +161,4 @@ build/temp/
# eof
/src/Umbraco.Web.UI.Client/TESTS-*.xml
/src/ApiDocs/api/*
+44
View File
@@ -0,0 +1,44 @@
$uenv=build/build.ps1 -get
$src = "$($uenv.SolutionRoot)\src"
$tmp = $uenv.BuildTemp
$out = $uenv.BuildOutput
$DocFxJson = "$src\ApiDocs\docfx.json"
$DocFxSiteOutput = "$tmp\_site\*.*"
################ Do the UI docs
$uenv.CompileBelle()
"Moving to Umbraco.Web.UI.Client folder"
cd .\src\Umbraco.Web.UI.Client
"Generating the docs and waiting before executing the next commands"
& gulp docs | Out-Null
# change baseUrl
$BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/"
$IndexPath = "./docs/api/index.html"
(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath
# zip it
& $uenv.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Client\docs\api\*.*"
################ Do the c# docs
# Build the solution in debug mode
$SolutionPath = Join-Path -Path $src -ChildPath "umbraco.sln"
#$uenv.CompileUmbraco()
#restore nuget packages
$uenv.RestoreNuGet()
# run DocFx
$DocFx = $uenv.BuildEnv.DocFx
Write-Host "$DocFxJson"
& $DocFx metadata $DocFxJson
& $DocFx build $DocFxJson
# zip it
& $uenv.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput
+54 -1
View File
@@ -11,6 +11,11 @@
[Alias("loc")]
[switch] $local = $false,
# enable docfx
[Parameter(Mandatory=$false)]
[Alias("doc")]
[switch] $docfx = $false,
# keep the build directories, don't clear them
[Parameter(Mandatory=$false)]
[Alias("c")]
@@ -31,7 +36,7 @@
$ubuild = &"$PSScriptRoot\build-bootstrap.ps1"
if (-not $?) { return }
$ubuild.Boot($PSScriptRoot,
@{ Local = $local; },
@{ Local = $local; WithDocFx = $docfx },
@{ Continue = $continue })
if ($ubuild.OnError()) { return }
@@ -424,6 +429,53 @@
Write-Host "Prepare Azure Gallery"
$this.CopyFile("$($this.SolutionRoot)\build\Azure\azuregalleryrelease.ps1", $this.BuildOutput)
})
$ubuild.DefineMethod("PrepareCSharpDocs",
{
Write-Host "Prepare C# Documentation"
$src = "$($this.SolutionRoot)\src"
$tmp = $this.BuildTemp
$out = $this.BuildOutput
$DocFxJson = Join-Path -Path $src "\ApiDocs\docfx.json"
$DocFxSiteOutput = Join-Path -Path $tmp "\_site\*.*"
#restore nuget packages
$this.RestoreNuGet()
# run DocFx
$DocFx = $this.BuildEnv.DocFx
& $DocFx metadata $DocFxJson
& $DocFx build $DocFxJson
# zip it
& $this.BuildEnv.Zip a -tzip -r "$out\csharp-docs.zip" $DocFxSiteOutput
})
$ubuild.DefineMethod("PrepareAngularDocs",
{
Write-Host "Prepare Angular Documentation"
$src = "$($this.SolutionRoot)\src"
$out = $this.BuildOutput
$this.CompileBelle()
"Moving to Umbraco.Web.UI.Client folder"
cd .\src\Umbraco.Web.UI.Client
"Generating the docs and waiting before executing the next commands"
& gulp docs | Out-Null
# change baseUrl
$BaseUrl = "https://our.umbraco.com/apidocs/v8/ui/"
$IndexPath = "./docs/api/index.html"
(Get-Content $IndexPath).replace('location.href.replace(rUrl, indexFile)', "`'" + $BaseUrl + "`'") | Set-Content $IndexPath
# zip it
& $this.BuildEnv.Zip a -tzip -r "$out\ui-docs.zip" "$src\Umbraco.Web.UI.Client\docs\api\*.*"
})
$ubuild.DefineMethod("Build",
{
@@ -456,6 +508,7 @@
if ($this.OnError()) { return }
$this.PostPackageHook()
if ($this.OnError()) { return }
Write-Host "Done"
})
+10 -24
View File
@@ -2,21 +2,20 @@
"metadata": [
{
"src": [
{
{
"src": "../",
"files": [
"Umbraco.Core/Umbraco.Core.csproj",
"Umbraco.Web/Umbraco.Web.csproj"
],
"exclude": [
"**/obj/**",
"**/bin/**",
"_site/**"
],
"cwd": "../src"
"**/bin/**"
]
}
],
"dest": "../apidocs/api",
"filter": "../apidocs/docfx.filter.yml"
"dest": "api",
"filter": "docfx.filter.yml"
}
],
"build": {
@@ -35,19 +34,7 @@
"*.md"
],
"exclude": [
"obj/**",
"_site/**"
]
}
],
"resource": [
{
"files": [
"images/**"
],
"exclude": [
"obj/**",
"_site/**"
"obj/**"
]
}
],
@@ -57,8 +44,7 @@
"**.md"
],
"exclude": [
"obj/**",
"_site/**"
"obj/**"
]
}
],
@@ -67,9 +53,9 @@
"_enableSearch": true,
"_disableContribution": false
},
"dest": "_site",
"dest": "../../build.tmp/_site",
"template": [
"default", "umbracotemplate"
]
}
}
}
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -77,6 +77,7 @@
"lodash": "4.17.11",
"marked": "^0.5.2",
"merge-stream": "1.0.1",
"run-sequence": "2.2.1"
"run-sequence": "2.2.1",
"caniuse-lite": "^1.0.30000966"
}
}
@@ -1,7 +1,7 @@
(function () {
"use strict";
function AppHeaderDirective(eventsService, appState, userService) {
function AppHeaderDirective(eventsService, appState, userService, focusService) {
function link(scope, el, attr, ctrl) {
@@ -54,7 +54,9 @@
}
});
}));
scope.rememberFocus = focusService.rememberFocus;
scope.searchClick = function() {
var showSearch = appState.getSearchState("show");
appState.setSearchState("show", !showSearch);
@@ -101,4 +103,4 @@
angular.module("umbraco.directives").directive("umbAppHeader", AppHeaderDirective);
})();
})();
@@ -13,7 +13,7 @@
}
};
function umbSearchController($timeout, backdropService, searchService) {
function umbSearchController($timeout, backdropService, searchService, focusService) {
var vm = this;
@@ -25,6 +25,9 @@
vm.handleKeyUp = handleKeyUp;
vm.closeSearch = closeSearch;
vm.focusSearch = focusSearch;
//we need to capture the focus before this element is initialized.
vm.focusBeforeOpening = focusService.getLastKnownFocus();
function onInit() {
vm.searchQuery = "";
@@ -70,6 +73,10 @@
* @param {object} event
*/
function handleKeyUp(event) {
event.stopPropagation();
event.preventDefault();
// esc
if(event.keyCode === 27) {
closeSearch();
@@ -80,6 +87,9 @@
* Used to proxy a callback
*/
function closeSearch() {
if(vm.focusBeforeOpening) {
vm.focusBeforeOpening.focus();
}
if(vm.onClose) {
vm.onClose();
}
@@ -115,4 +125,4 @@
angular.module('umbraco.directives').component('umbSearch', umbSearch);
})();
})();
@@ -4,7 +4,7 @@
**/
angular.module("umbraco.directives")
.directive('hotkey', function($window, keyboardService, $log) {
.directive('hotkey', function($window, keyboardService, $log, focusService) {
return function(scope, el, attrs) {
@@ -28,7 +28,9 @@ angular.module("umbraco.directives")
}
keyboardService.bind(keyCombo, function() {
focusService.rememberFocus();
var element = $(el);
var activeElementType = document.activeElement.tagName;
var clickableElements = ["A", "BUTTON"];
@@ -295,7 +295,8 @@ Opens an overlay to show a custom YSOD. </br>
scope.closeOverLay();
});
}
event.stopPropagation();
event.preventDefault();
}
@@ -511,6 +512,7 @@ Opens an overlay to show a custom YSOD. </br>
model: "=",
view: "=",
position: "@",
size: "=?",
parentScope: "=?"
},
link: link
@@ -41,6 +41,7 @@
vm.removeTag = removeTag;
vm.showPrompt = showPrompt;
vm.hidePrompt = hidePrompt;
vm.onKeyUpOnTag = onKeyUpOnTag;
vm.htmlId = "t" + String.CreateGuid();
vm.isLoading = true;
@@ -272,6 +273,12 @@
vm.promptIsVisible = "-1";
}
function onKeyUpOnTag(tag, $event) {
if ($event.keyCode === 8 || $event.keyCode === 46) {
removeTag(tag);
}
}
// helper method to remove current tags
function removeCurrentTagsFromSuggestions(suggestions) {
return $.grep(suggestions, function (suggestion) {
@@ -3,7 +3,7 @@
function () {
var link = function ($scope) {
// Clone the model because some property editors
// do weird things like updating and config values
// so we want to ensure we start from a fresh every
@@ -12,10 +12,10 @@
$scope.nodeContext = $scope.model;
// Find the selected tab
var selectedTab = $scope.model.tabs[0];
var selectedTab = $scope.model.variants[0].tabs[0];
if ($scope.tabAlias) {
angular.forEach($scope.model.tabs, function (tab) {
angular.forEach($scope.model.variants[0].tabs, function (tab) {
if (tab.alias.toLowerCase() === $scope.tabAlias.toLowerCase()) {
selectedTab = tab;
return;
@@ -31,9 +31,9 @@
// Tell inner controls we are submitting
$scope.$broadcast("formSubmitting", { scope: $scope });
// Sync the values back
angular.forEach($scope.ngModel.tabs, function (tab) {
angular.forEach($scope.ngModel.variants[0].tabs, function (tab) {
if (tab.alias.toLowerCase() === selectedTab.alias.toLowerCase()) {
var localPropsMap = selectedTab.properties.reduce(function (map, obj) {
@@ -94,4 +94,4 @@
// },
// link: link
// }
//});
//});
@@ -0,0 +1,31 @@
/**
* @ngdoc filter
* @name umbraco.filters.filter:truncate
* @namespace truncateFilter
*
* param {any} wordwise if true, the string will be cut after last fully displayed word.
* param {any} max max length of the outputtet string
* param {any} tail option tail, defaults to: ' ...'
*
* @description
* Limits the length of a string, if a cut happens only the string will be appended with three dots to indicate that more is available.
*/
angular.module("umbraco.filters").filter('truncate',
function () {
return function (value, wordwise, max, tail) {
if (!value) return '';
max = parseInt(max, 10);
if (!max) return value;
if (value.length <= max) return value;
value = value.substr(0, max);
if (wordwise) {
var lastspace = value.lastIndexOf(' ');
if (lastspace != -1) {
value = value.substr(0, lastspace);
}
}
return value + (tail || (wordwise ? ' …' : '…'));
};
}
);
@@ -0,0 +1,203 @@
/**
* @ngdoc service
* @name umbraco.services.clipboardService
*
* @requires notificationsService
* @requires eventsService
*
* @description
* Service to handle clipboard in general across the application. Responsible for handling the data both storing and retrive.
* The service has a set way for defining a data-set by a entryType and alias, which later will be used to retrive the posible entries for a paste scenario.
*
*/
function clipboardService(notificationsService, eventsService, localStorageService) {
var STORAGE_KEY = "umbClipboardService";
var retriveStorage = function() {
if (localStorageService.isSupported === false) {
return null;
}
var dataJSON;
var dataString = localStorageService.get(STORAGE_KEY);
if (dataString != null) {
dataJSON = JSON.parse(dataString);
}
if(dataJSON == null) {
dataJSON = new Object();
}
if(dataJSON.entries === undefined) {
dataJSON.entries = [];
}
return dataJSON;
}
var saveStorage = function(storage) {
var storageString = JSON.stringify(storage);
try {
var storageJSON = JSON.parse(storageString);
localStorageService.set(STORAGE_KEY, storageString);
eventsService.emit("clipboardService.storageUpdate");
return true;
} catch(e) {
return false;
}
return false;
}
var service = {};
/**
* @ngdoc method
* @name umbraco.services.clipboardService#copy
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to storing, example: 'elementType', 'contentNode'
* @param {string} alias A string defining the alias of the data to store, example: 'product'
* @param {object} data A object containing the properties to be saved.
*
* @description
* Saves a single JS-object with a type and alias to the clipboard.
*/
service.copy = function(type, alias, data) {
var storage = retriveStorage();
var shallowCloneData = Object.assign({}, data);// Notice only a shallow copy, since we dont need to deep copy. (that will happen when storing the data)
delete shallowCloneData.key;
delete shallowCloneData.$$hashKey;
var key = data.key || data.$$hashKey || console.error("missing unique key for this content");
// remove previous copies of this entry:
storage.entries = storage.entries.filter(
(entry) => {
return entry.unique !== key;
}
);
var entry = {unique:key, type:type, alias:alias, data:shallowCloneData};
storage.entries.push(entry);
if (saveStorage(storage) === true) {
notificationsService.success("Clipboard", "Copied to clipboard.");
} else {
notificationsService.success("Clipboard", "Couldnt copy this data to clipboard.");
}
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#supported
* @methodOf umbraco.services.clipboardService
*
* @description
* Determins wether the current browser is able to performe its actions.
*/
service.isSupported = function() {
return localStorageService.isSupported;
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#hasEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data test for.
* @param {string} aliases A array of strings providing the alias of the data you want to test for.
*
* @description
* Determines whether the current clipboard has entries that match a given type and one of the aliases.
*/
service.hasEntriesOfType = function(type, aliases) {
if(service.retriveEntriesOfType(type, aliases).length > 0) {
return true;
}
return false;
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#retriveEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to recive.
* @param {string} aliases A array of strings providing the alias of the data you want to recive.
*
* @description
* Returns an array of entries matching the given type and one of the provided aliases.
*/
service.retriveEntriesOfType = function(type, aliases) {
var storage = retriveStorage();
// Find entries that are fulfilling the criteria for this nodeType and nodeTypesAliases.
var filteretEntries = storage.entries.filter(
(entry) => {
return (entry.type === type && aliases.filter(alias => alias === entry.alias).length > 0);
}
);
return filteretEntries;
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#retriveEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to recive.
* @param {string} aliases A array of strings providing the alias of the data you want to recive.
*
* @description
* Returns an array of data of entries matching the given type and one of the provided aliases.
*/
service.retriveDataOfType = function(type, aliases) {
return service.retriveEntriesOfType(type, aliases).map((x) => x.data);
};
/**
* @ngdoc method
* @name umbraco.services.supportsCopy#retriveEntriesOfType
* @methodOf umbraco.services.clipboardService
*
* @param {string} type A string defining the type of data to remove.
* @param {string} aliases A array of strings providing the alias of the data you want to remove.
*
* @description
* Removes entries matching the given type and one of the provided aliases.
*/
service.clearEntriesOfType = function(type, aliases) {
var storage = retriveStorage();
// Find entries that are NOT fulfilling the criteria for this nodeType and nodeTypesAliases.
var filteretEntries = storage.entries.filter(
(entry) => {
return !(entry.type === type && aliases.filter(alias => alias === entry.alias).length > 0);
}
);
storage.entries = filteretEntries;
saveStorage(storage);
};
return service;
}
@@ -164,9 +164,22 @@ When building a custom infinite editor view you can use the same components as a
"use strict";
function editorService(eventsService, keyboardService, $timeout) {
let editorsKeyboardShorcuts = [];
var editors = [];
var isEnabled = true;
// events for backdrop
eventsService.on("appState.backdrop", function (name, args) {
if (args.show === true) {
blur();
} else {
focus();
}
});
/**
* @ngdoc method
@@ -191,7 +204,43 @@ When building a custom infinite editor view you can use the same components as a
function getNumberOfEditors() {
return editors.length;
};
/**
* @ngdoc method
* @name umbraco.services.editorService#blur
* @methodOf umbraco.services.editorService
*
* @description
* Method to tell editors that they are begin blurred.
*/
function blur() {
/* keyboard shortcuts will be overwritten by the new infinite editor
so we need to store the shortcuts for the current editor so they can be rebound
when the infinite editor closes
*/
unbindKeyboardShortcuts();
isEnabled = false;
}
/**
* @ngdoc method
* @name umbraco.services.editorService#blur
* @methodOf umbraco.services.editorService
*
* @description
* Method to tell editors that they are gaining focus again.
*/
function focus() {
if(isEnabled === false) {
/* keyboard shortcuts will be overwritten by the new infinite editor
so we need to store the shortcuts for the current editor so they can be rebound
when the infinite editor closes
*/
rebindKeyboardShortcuts();
isEnabled = true;
}
}
/**
* @ngdoc method
* @name umbraco.services.editorService#open
@@ -0,0 +1,78 @@
/**
@ngdoc service
* @name umbraco.services.focusService
*
* @description
* <b>Added in Umbraco 8.1</b>. Application-wide service for focus related stuff.
*
*/
(function () {
"use strict";
function focusService() {
var currentFocus = null;
var lastKnownFocus = null;
function focusInApp(e) {
currentFocus = e.target;
}
document.addEventListener('focusin', focusInApp);
var service = {
/**
* @ngdoc function
* @name umbraco.services.focusService#getLastKnownFocus
* @methodOf umbraco.services.focusService
* @function
*
* @description
* Gives the element that was set to be remembered, the directive using this should store the value of this to make sure that its not changed white using that directive.
* This variable is avaiable for directives that are not able to figure out the focused element on init, and there this service will help remembering it untill the directive is initialized.
*
*/
getLastKnownFocus: function() {
return lastKnownFocus;
},
/**
* @ngdoc function
* @name umbraco.services.focusService#rememberFocus
* @methodOf umbraco.services.focusService
* @function
*
* @description
* Call this before a new focus is begin set, to be able to return to the focus before a given scenario.
*
*/
rememberFocus: function() {
lastKnownFocus = currentFocus;
},
/**
* @ngdoc function
* @name umbraco.services.focusService#setLastKnownFocus
* @methodOf umbraco.services.focusService
* @function
*
* @description
* Overwrite the element remembered as the last known element in focus.
*
*/
setLastKnownFocus: function(element) {
lastKnownFocus = element;
}
};
return service;
}
angular.module("umbraco.services").factory("focusService", focusService);
})();
@@ -27,6 +27,11 @@
overlay.position = "center";
}
// set the default overlay size to small
if(!overlay.size) {
overlay.size = "small";
}
// use a default empty view if nothing is set
if(!overlay.view) {
overlay.view = "views/common/overlays/default/default.html";
@@ -72,4 +77,4 @@
angular.module("umbraco.services").factory("overlayService", overlayService);
})();
})();
@@ -8,7 +8,9 @@
* The main application controller
*
*/
function MainController($scope, $location, appState, treeService, notificationsService, userService, historyService, updateChecker, assetsService, eventsService, tmhDynamicLocale, localStorageService, editorService, overlayService) {
function MainController($scope, $location, appState, treeService, notificationsService,
userService, historyService, updateChecker, assetsService, eventsService,
tmhDynamicLocale, localStorageService, editorService, overlayService, focusService) {
//the null is important because we do an explicit bool check on this in the view
$scope.authenticated = null;
@@ -25,6 +27,7 @@ function MainController($scope, $location, appState, treeService, notificationsS
function handleFirstTab(evt) {
if (evt.keyCode === 9) {
$scope.tabbingActive = true;
$scope.$digest();
window.removeEventListener('keydown', handleFirstTab);
window.addEventListener('mousedown', disableTabbingActive);
}
@@ -32,6 +35,7 @@ function MainController($scope, $location, appState, treeService, notificationsS
function disableTabbingActive(evt) {
$scope.tabbingActive = false;
$scope.$digest();
window.removeEventListener('mousedown', disableTabbingActive);
window.addEventListener("keydown", handleFirstTab);
}
@@ -130,6 +130,7 @@
@import "components/tooltip/umb-tooltip.less";
@import "components/tooltip/umb-tooltip-list.less";
@import "components/overlays/umb-overlay-backdrop.less";
@import "components/overlays/umb-itempicker.less";
@import "components/umb-grid.less";
@import "components/umb-empty-state.less";
@import "components/umb-property-editor.less";
@@ -6,6 +6,7 @@
position: relative;
padding: 5px 10px 5px 10px;
background: white;
width: 100%;
.title{padding: 12px; color: @gray-3; border-bottom: 1px solid @gray-8; font-weight: 400; font-size: 16px; text-transform: none; margin: 0 -10px 10px -10px;}
@@ -84,63 +85,73 @@
padding: 0;
margin: 0 auto;
list-style: none;
width: 100%;
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
}
.umb-card-grid li {
padding: 5px;
overflow: hidden;
font-size: 12px;
text-align: center;
width: 100px;
box-sizing: border-box;
position: relative;
width: 100px;
}
.umb-card-grid li.-four-in-row {
.umb-card-grid.-four-in-row li {
flex: 0 0 25%;
max-width: 25%;
}
.umb-card-grid li.-three-in-row {
.umb-card-grid.-three-in-row li {
flex: 0 0 33.33%;
max-width:33.33%;
}
.umb-card-grid .umb-card-grid-item {
position: relative;
display: block;
width: 100%;
height: 100%;
//height: 100%;
padding-top: 100%;
border-radius: 3px;
padding-bottom: 5px;
transition: background-color 120ms;
> span {
position: absolute;
top: 10px;
bottom: 10px;
left: 10px;
right: 10px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
background-color: transparent;
}
}
.umb-card-grid .umb-card-grid-item:hover,
.umb-card-grid .umb-card-grid-item:focus,
.umb-card-grid .umb-card-grid-item:hover > *,
.umb-card-grid .umb-card-grid-item:focus > * {
background: @ui-option-hover;
.umb-card-grid .umb-card-grid-item:hover,
.umb-card-grid .umb-card-grid-item:focus {
background-color: @ui-option-hover;
color: @ui-option-type-hover;
cursor: pointer;
outline: none;
border-radius: 3px;
}
.umb-card-grid a {
color: @ui-option-type;
text-decoration: none;
}
color: @ui-option-type;
text-decoration: none;
}
.umb-card-grid i {
font-size: 30px;
line-height: 50px;
display: block;
color: @ui-option-type;
}
font-size: 30px;
line-height: 20px;
margin-bottom: 10px;
display: block;
}
.umb-card-grid .umb-card-grid-item__loading {
position: absolute;
@@ -5,51 +5,72 @@
z-index: @zindexUmbOverlay;
animation: fadeIn 0.2s;
box-shadow: 0 10px 50px rgba(0,0,0,0.1), 0 6px 20px rgba(0,0,0,0.16);
text-align: left;
}
.umb-overlay__form {
display: flex;
flex-wrap: nowrap;
flex-direction: column;
height: 100%;
}
.umb-overlay .umb-overlay-header {
//background: @gray-10;
border-bottom: 1px solid @purple-l3;
//background: @blueExtraDark;
//color:@u-white;
padding: 10px;
margin-top: 0;
flex-grow: 0;
flex-shrink: 0;
padding: 20px 30px 0;
}
.umb-overlay .umb-overlay__title {
.umb-overlay__section-header {
width: 100%;
margin-top:30px;
margin-bottom: 10px;
h5 {
display: inline;
}
button {
display: inline;
float: right;
background-color: transparent;
border:none;
&:hover {
color: @ui-option-type-hover;
}
}
}
.umb-overlay__title {
font-size: @fontSizeLarge;
color: @black;
font-weight: bold;
margin: 7px 0;
}
.umb-overlay .umb-overlay__subtitle {
.umb-overlay__subtitle {
font-size: @fontSizeSmall;
color: @gray-3;
margin: 0;
}
.umb-overlay .umb-overlay-container {
.umb-overlay-container {
flex-grow: 1;
flex-shrink: 1;
flex-basis: auto;
overflow-y: auto;
overflow-x: hidden;
position: relative;
height: auto;
padding: 0px 30px;
margin-bottom: 10px;
max-height: calc(100vh - 170px);
overflow-y: auto;
}
.umb-overlay .umb-overlay-drawer {
.umb-overlay-drawer {
flex-grow: 0;
flex-shrink: 0;
flex-basis: 31px;
@@ -60,16 +81,16 @@
border-top: 1px solid @purple-l3;
}
.umb-overlay .umb-overlay-drawer.-auto-height {
.umb-overlay-drawer.-auto-height {
flex-basis: auto;
}
.umb-overlay .umb-overlay-drawer .umb-overlay-drawer__align-right {
.umb-overlay-drawer .umb-overlay-drawer__align-right {
display: flex;
justify-content: flex-end;
}
.umb-overlay .umb-overlay-drawer .umb-overlay-drawer-content .dropdown-menu {
.umb-overlay-drawer .umb-overlay-drawer-content .dropdown-menu {
right: 0;
left: auto;
}
@@ -89,46 +110,44 @@
.umb-overlay.umb-overlay-center .umb-overlay-header {
border: none;
background: transparent;
padding: 20px 20px 0 20px;
padding: 30px 30px 0;
}
.umb-overlay.umb-overlay-center .umb-overlay__form {
max-height: 80vh;
}
.umb-overlay.umb-overlay-center .umb-overlay-container {
padding: 20px;
}
.umb-overlay.umb-overlay-center .umb-overlay-drawer {
border: none;
background: transparent;
padding: 0 20px 20px 20px;
padding: 0 30px 20px;
}
/* ---------- OVERLAY TARGET ---------- */
.umb-overlay.umb-overlay-target {
width: 400px;
height: 400px;
max-height: 100vh;
box-sizing: border-box;
border-radius: @baseBorderRadius;
/* default:
&.umb-overlay--small {
width: 400px;
}
*/
&.umb-overlay--medium {
width: 480px;
}
}
.umb-overlay.umb-overlay-target .umb-overlay-header {
border: none;
background: transparent;
padding: 20px 20px 0 20px;
text-align: center;
}
.umb-overlay.umb-overlay-target .umb-overlay-container {
padding: 20px;
}
.umb-overlay.umb-overlay-target .umb-overlay-drawer {
border: none;
background: transparent;
padding: 0 20px 20px 20px;
padding: 0 30px 20px;
}
/* ---------- OVERLAY RIGHT ---------- */
@@ -143,14 +162,9 @@
.umb-overlay.umb-overlay-right .umb-overlay-header {
flex-basis: 100px;
padding: 20px;
box-sizing: border-box;
}
.umb-overlay.umb-overlay-right .umb-overlay-container {
padding: 20px;
}
// reset the top position to 0 because we are in a asbolute container and want to
// overlay to go all the way to the top
.umb-editors .umb-overlay.umb-overlay-right {
@@ -175,14 +189,10 @@
.umb-overlay.umb-overlay-left .umb-overlay-header {
flex-basis: 100px;
padding: 20px;
padding: 30px 30px 0;
box-sizing: border-box;
}
.umb-overlay.umb-overlay-left .umb-overlay-container {
padding: 20px;
}
@media (max-width: 767px) {
.umb-overlay.umb-overlay-left {
margin-left: 61px;
@@ -0,0 +1,6 @@
.umb-itempicker .form-search {
margin-top:10px;
}
.umb-card-grid {
margin-top: 10px;
}
@@ -30,23 +30,13 @@
}
&:checked ~ .umb-form-check__state .umb-form-check__check {
border-color: @ui-option-type;
background-color: @ui-option-type;
}
&:checked:hover ~ .umb-form-check__state .umb-form-check__check {
&::before {
background: @ui-option-type-hover;
}
}
&:focus:checked ~ .umb-form-check .umb-form-check__check,
&:focus ~ .umb-form-check__state .umb-form-check__check {
border-color: @inputBorderFocus;
.tabbing-active & {
outline: 2px solid @inputBorderTabFocus;
}
}
&:checked ~ .umb-form-check__state {
.umb-form-check__check {
// This only happens if the state has a radiobutton modifier
@@ -72,6 +62,19 @@
}
}
}
.tabbing-active &.umb-form-check--radiobutton &__input:focus ~ .umb-form-check__state .umb-form-check__check {
//outline: 2px solid @inputBorderTabFocus;
border: 2px solid @inputBorderTabFocus;
margin: -1px;
}
.tabbing-active &.umb-form-check--checkbox &__input:focus ~ .umb-form-check__state .umb-form-check__check {
outline: 2px solid @inputBorderTabFocus;
}
.tabbing-active &.umb-form-check--checkbox &__input:checked:focus ~ .umb-form-check__state .umb-form-check__check {
border-color: white;
}
&__state {
display: flex;
@@ -87,23 +87,11 @@
.umb-nested-content__icons {
opacity: 0;
transition: opacity .15s ease-in-out;
transition: opacity 120ms ease-in-out;
position: absolute;
right: 0px;
top: 2px;
background-color: @white;
right: 8px;
top: 4px;
padding: 5px;
&:before {
content: ' ';
position: absolute;
display: block;
width: 30px;
left: -30px;
top: 0;
bottom: 0;
background: linear-gradient(90deg, rgba(255,255,255,0), white);
}
}
.umb-nested-content__item--active > .umb-nested-content__header-bar {
@@ -130,41 +118,22 @@
.umb-nested-content__icon,
.umb-nested-content__icon.umb-nested-content__icon--disabled:hover {
.umb-nested-content__icon {
display: inline-block;
padding: 4px 6px;
padding: 4px;
margin: 2px;
cursor: pointer;
background: @white;
border: 1px solid @gray-7;
border-radius: 200px;
text-decoration: none !important;
color: @ui-option-type;
}
.umb-nested-content__icon.umb-nested-content__icon--disabled:hover {
cursor: default;
}
.umb-nested-content__icon:hover,
.umb-nested-content__icon--active
{
color: @white;
background: @blueMid;
border-color: @blueMid;
.umb-nested-content__icon:hover {
color: @ui-option-type-hover;
text-decoration: none;
}
.umb-nested-content__icon .icon,
.umb-nested-content__icon.umb-nested-content__icon--disabled:hover .icon {
.umb-nested-content__icon .icon {
display: block;
font-size: 16px !important;
color: @gray-3;
}
.umb-nested-content__icon:hover .icon,
.umb-nested-content__icon--active .icon {
color: @white;
}
.umb-nested-content__icon--disabled {
@@ -223,16 +192,6 @@
display: none !important;
}
.umb-nested-content__help-text {
display: inline-block;
padding: 10px 20px 10px 20px;
clear: both;
font-size: 14px;
color: @gray-3;
background: @gray-10;
border-radius: 15px;
}
.umb-nested-content__doctypepicker table input,
.umb-nested-content__doctypepicker table select {
width: 100%;
@@ -118,7 +118,6 @@ h5.-black {
.umb-control-group {
border-bottom: 1px solid @gray-11;
padding-bottom: 20px;
margin-bottom: 15px !important;
}
.umb-control-group.-no-border {
@@ -852,9 +852,10 @@
//
// Tags
// --------------------------------------------------
.umb-tags{
.umb-tags {
border: @inputBorder solid 1px;
padding: 10px;
padding: 5px;
min-height: 54px;
font-size: 13px;
text-shadow: none;
box-sizing: border-box;
@@ -862,17 +863,19 @@
.tag {
cursor: default;
margin: 7px;
padding: 10px;
background: @blueDark;
margin: 10px;
padding: 10px 15px;
background: @blueExtraDark;
position: relative;
.icon-trash{
cursor: pointer;
padding: 2px;
font-size: 12px;
user-select: all;
.icon-trash {
position: relative;
right: -6px;
cursor: pointer;
padding-left: 2px;
font-size: 15px;
right: -5px;
bottom: -1px;
}
.umb_confirm-action__overlay.-left{
@@ -882,10 +885,19 @@
}
}
input{
input {
border: none;
background: @white;
}
.twitter-typeahead {
margin: 10px;
margin-top: 16px;
vertical-align: top;
input {
padding-left: 0;
}
}
}
//
@@ -31,8 +31,8 @@
padding: 8px 0;
background-color: @white;
border: 1px solid @gray-8;
border-radius: 8px;
box-shadow: 0 5px 10px rgba(0,0,0,.2);
border-radius: 3px;
box-shadow: 0 3px 6px rgba(0,0,0,.16);
}
.tt-suggestion {
@@ -44,7 +44,7 @@
.tt-suggestion.tt-cursor {
color: @white;
background-color: @turquoise;
background-color: @ui-selected-type-hover;
}
.tt-suggestion p {
@@ -33,24 +33,25 @@
<umb-load-indicator ng-if="vm.loading"></umb-load-indicator>
<!-- TABS -->
<div ng-if="vm.showTabs">
<umb-tabs-nav
ng-if="vm.tabs"
tabs="vm.tabs"
<umb-tabs-nav
ng-if="vm.tabs"
tabs="vm.tabs"
on-tab-change="vm.onTabChange(tab)">
</umb-tabs-nav>
<umb-tab-content ng-repeat="tab in vm.tabs" tab="tab" ng-if="tab.active">
<div ng-if="tab.alias==='Default'">
<div ng-repeat="(key,value) in tab.typesAndEditors">
<h5>{{key}}</h5>
<ul class="umb-card-grid" ng-mouseleave="vm.hideDetailsOverlay()">
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="systemDataType in value | orderBy:'name'"
data-element="editor-{{systemDataType.name}}"
ng-mouseover="vm.showDetailsOverlay(systemDataType)"
ng-click="vm.pickEditor(systemDataType)"
class="-four-in-row">
ng-click="vm.pickEditor(systemDataType)">
<a class="umb-card-grid-item" href="" title="{{ systemDataType.name }}">
<i class="{{ systemDataType.icon }}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{ systemDataType.name }}
<span>
<i class="{{ systemDataType.icon }}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{ systemDataType.name }}
</span>
</a>
</li>
</ul>
@@ -59,18 +60,19 @@
<div ng-if="tab.alias==='Reuse'">
<div ng-repeat="(key,value) in tab.userConfigured">
<h5>{{key}}</h5>
<ul class="umb-card-grid" ng-mouseleave="vm.hideDetailsOverlay()">
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="dataType in value | orderBy:'name'"
data-element="editor-{{dataType.name}}"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)"
class="-four-in-row">
ng-click="vm.pickDataType(dataType)">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
</span>
</a>
</li>
</ul>
@@ -84,17 +86,18 @@
<div ng-repeat="result in vm.filterResult.userConfigured">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<ul class="umb-card-grid" ng-mouseleave="vm.hideDetailsOverlay()">
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="dataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(dataType)"
ng-click="vm.pickDataType(dataType)"
class="-four-in-row">
ng-click="vm.pickDataType(dataType)">
<div ng-if="dataType.loading" class="umb-card-grid-item__loading">
<div class="umb-button__progress"></div>
</div>
<a class="umb-card-grid-item" href="" title="{{ dataType.name }}">
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
<span>
<i class="{{ dataType.icon }}" ng-class="{'icon-autofill': dataType.icon == null}"></i>
{{ dataType.name }}
</span>
</a>
</li>
</ul>
@@ -104,14 +107,15 @@
<div ng-repeat="result in vm.filterResult.typesAndEditors">
<div ng-if="result.dataTypes.length > 0">
<h5>{{result.group}}</h5>
<ul class="umb-card-grid" ng-mouseleave="vm.hideDetailsOverlay()">
<ul class="umb-card-grid -four-in-row" ng-mouseleave="vm.hideDetailsOverlay()">
<li ng-repeat="systemDataType in result.dataTypes | orderBy:'name'"
ng-mouseover="vm.showDetailsOverlay(systemDataType)"
ng-click="vm.pickEditor(systemDataType)"
class="-four-in-row">
ng-click="vm.pickEditor(systemDataType)">
<a class="umb-card-grid-item" href="" title="{{ systemDataType.name }}">
<i class="{{ systemDataType.icon }}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{ systemDataType.name }}
<span>
<i class="{{ systemDataType.icon }}" ng-class="{'icon-autofill': systemDataType.icon == null}"></i>
{{ systemDataType.name }}
</span>
</a>
</li>
</ul>
@@ -23,14 +23,15 @@
umb-auto-focus
no-dirty-check />
</div>
<ul class="umb-card-grid">
<ul class="umb-card-grid -three-in-row">
<li ng-repeat="availableItem in model.availableItems | compareArrays:model.selectedItems:'alias' | orderBy:'name' | filter:searchTerm"
ng-click="vm.selectItem(availableItem)"
class="-three-in-row">
ng-click="vm.selectItem(availableItem)">
<a class="umb-card-grid-item" href="" title="{{ availableItem.name }}">
<i class="{{ availableItem.icon }}"></i>
{{ availableItem.name }}
<span>
<i class="{{ availableItem.icon }}"></i>
{{ availableItem.name }}
</span>
</a>
</li>
</ul>
@@ -29,13 +29,14 @@
no-dirty-check />
</div>
<ul class="umb-card-grid">
<ul class="umb-card-grid -three-in-row">
<li ng-repeat="availableItem in macros | orderBy:'name' | filter:searchTerm"
ng-click="selectMacro(availableItem)"
class="-three-in-row">
ng-click="selectMacro(availableItem)">
<a class="umb-card-grid-item" href="" title="{{ availableItem.name }}">
<i class="icon-settings-alt"></i>
{{ availableItem.name }}
<span>
<i class="icon-settings-alt"></i>
{{ availableItem.name }}
</span>
</a>
</li>
</ul>
@@ -1,4 +1,4 @@
<div ng-controller="Umbraco.Overlays.ItemPickerOverlay">
<div ng-controller="Umbraco.Overlays.ItemPickerOverlay" class="umb-itempicker">
<div class="form-search" ng-hide="model.filter === false" style="margin-bottom: 15px;">
<i class="icon-search"></i>
@@ -11,13 +11,37 @@
no-dirty-check />
</div>
<ul class="umb-card-grid">
<div class="umb-overlay__section-header" ng-if="(model.pasteItems | filter:searchTerm).length > 0">
<h5><localize key="content_createFromClipboard">Paste from clipboard</localize></h5>
<button ng-if="model.clickClearPaste" ng-click="model.clickClearPaste($event)" alt="Clear clipboard for entries accepted in this context.">
<i class="icon-trash"></i>
</button>
</div>
<ul class="umb-card-grid" ng-class="{'-three-in-row': model.availableItems.length < 7, '-four-in-row': model.availableItems.length >= 7}">
<li ng-repeat="pasteItem in model.pasteItems | filter:searchTerm"
ng-click="model.clickPasteItem(pasteItem)">
<a class="umb-card-grid-item" href="" title="{{ pasteItem.name }}">
<span>
<i class="{{ pasteItem.icon }}"></i>
{{ pasteItem.name | truncate:true:36 }}
</span>
</a>
</li>
</ul>
<div class="umb-overlay__section-header" ng-if="model.pasteItems.length > 0 && (model.availableItems | filter:searchTerm).length > 0">
<h5><localize key="content_createEmpty">Create new</localize></h5>
</div>
<ul class="umb-card-grid" ng-class="{'-three-in-row': model.availableItems.length < 7, '-four-in-row': model.availableItems.length >= 7}">
<li ng-repeat="availableItem in model.availableItems | compareArrays:model.selectedItems:'alias' | orderBy:model.orderBy | filter:searchTerm"
ng-click="selectItem(availableItem)"
class="-three-in-row">
ng-click="selectItem(availableItem)">
<a class="umb-card-grid-item" href="" title="{{ availableItem.name }}">
<i class="{{ availableItem.icon }}"></i>
{{ availableItem.name }}
<span>
<i class="{{ availableItem.icon }}"></i>
{{ availableItem.name }}
</span>
</a>
</li>
</ul>
@@ -1,13 +1,14 @@
<div ng-controller="Umbraco.Overlays.MediaTypePickerController">
<ul class="umb-card-grid">
<ul class="umb-card-grid -three-in-row">
<li
ng-repeat="mediatype in model.acceptedMediatypes | orderBy:'name'"
ng-click="select(mediatype)"
class="-three-in-row">
ng-click="select(mediatype)">
<a class="umb-card-grid-item" href="" title="{{mediatype.name}}">
<i class="{{ mediatype.icon }}"></i>
{{ mediatype.name }}
<span>
<i class="{{ mediatype.icon }}"></i>
{{ mediatype.name }}
</span>
</a>
</li>
</ul>
@@ -1,7 +1,7 @@
<div>
<div class="umb-app-header">
<umb-sections
ng-if="authenticated"
sections="sections">
@@ -10,7 +10,7 @@
<div class="flex items-center">
<ul class="umb-app-header__actions">
<li data-element="global-search" class="umb-app-header__action">
<a href="#" hotkey="ctrl+space" ng-click="searchClick()" prevent-default style="font-size: 20px;">
<a href="#" hotkey="ctrl+space" ng-click="searchClick()" ng-mousedown="rememberFocus()" prevent-default style="font-size: 20px;">
<i class="umb-app-header__action-icon icon-search"></i>
</a>
</li>
@@ -44,4 +44,3 @@
</umb-overlay>
</div>
@@ -1,4 +1,4 @@
<div data-element="overlay" class="umb-overlay umb-overlay-{{position}}" on-outside-click="outSideClick()">
<div data-element="overlay" class="umb-overlay umb-overlay-{{position}} umb-overlay--{{size}}" on-outside-click="outSideClick()">
<ng-form class="umb-overlay__form" name="overlayForm" novalidate val-form-manager>
<div data-element="overlay-header" class="umb-overlay-header">
@@ -8,7 +8,7 @@
<input type="hidden" name="tagCount" ng-model="vm.viewModel.length" val-property-validator="vm.validateMandatory" />
<span ng-repeat="tag in vm.viewModel track by $index" class="label label-primary tag">
<span ng-repeat="tag in vm.viewModel track by $index" class="label label-primary tag" ng-keyup="vm.onKeyUpOnTag(tag, $event)" tabindex="0">
<span ng-bind-html="tag"></span>
<i class="icon-trash" ng-click="vm.showPrompt($index, tag)" localize="title" title="@buttons_deleteTag"></i>
@@ -12,8 +12,7 @@
ncAlias: "",
ncTabAlias: "",
nameTemplate: ""
}
);
});
}
$scope.remove = function (index) {
@@ -58,7 +57,7 @@
ncResources.getContentTypes().then(function (docTypes) {
$scope.model.docTypes = docTypes;
// Populate document type tab dictionary
docTypes.forEach(function (value) {
$scope.docTypeTabs[value.alias] = value.tabs;
@@ -74,7 +73,6 @@
return docType.alias === c.ncAlias;
});
});
}
if (!$scope.model.value) {
@@ -93,10 +91,17 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
"contentResource",
"localizationService",
"iconHelper",
function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper) {
"clipboardService",
"eventsService",
function ($scope, $interpolate, $filter, $timeout, contentResource, localizationService, iconHelper, clipboardService, eventsService) {
var inited = false;
var contentTypeAliases = [];
_.each($scope.model.config.contentTypes, function (contentType) {
contentTypeAliases.push(contentType.ncAlias);
});
_.each($scope.model.config.contentTypes, function (contentType) {
contentType.nameExp = !!contentType.nameTemplate
@@ -122,8 +127,9 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
$scope.hasContentTypes = $scope.model.config.contentTypes.length > 0;
$scope.labels = {};
localizationService.localizeMany(["grid_insertControl"]).then(function(data) {
$scope.labels.docTypePickerTitle = data[0];
localizationService.localizeMany(["grid_addElement", "content_createEmpty"]).then(function(data) {
$scope.labels.grid_addElement = data[0];
$scope.labels.content_createEmpty = data[1];
});
// helper to force the current form into the dirty state
@@ -136,7 +142,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
$scope.addNode = function (alias) {
var scaffold = $scope.getScaffold(alias);
var newNode = initNode(scaffold, null);
var newNode = createNode(scaffold, null);
$scope.currentNode = newNode;
$scope.setDirty();
@@ -148,14 +154,18 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
$scope.overlayMenu = {
title: $scope.labels.docTypePickerTitle,
show: false,
style: {},
filter: $scope.scaffolds.length > 15 ? true : false,
filter: $scope.scaffolds.length > 12 ? true : false,
orderBy: "$index",
view: "itempicker",
event: $event,
submit: function(model) {
clickPasteItem: function(item) {
$scope.pasteFromClipboard(item.data);
$scope.overlayMenu.show = false;
$scope.overlayMenu = null;
},
submit: function(model) {
if(model && model.selectedItem) {
$scope.addNode(model.selectedItem.alias);
}
@@ -181,13 +191,35 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
if ($scope.overlayMenu.availableItems.length === 0) {
return;
}
if ($scope.overlayMenu.availableItems.length === 1) {
$scope.overlayMenu.size = $scope.overlayMenu.availableItems.length > 6 ? "medium" : "small";
$scope.overlayMenu.pasteItems = [];
var availableNodesForPaste = clipboardService.retriveDataOfType("elementType", contentTypeAliases);
_.each(availableNodesForPaste, function (node) {
$scope.overlayMenu.pasteItems.push({
alias: node.contentTypeAlias,
name: node.name, //contentTypeName
data: node,
icon: iconHelper.convertFromLegacyIcon(node.icon)
});
});
$scope.overlayMenu.title = $scope.overlayMenu.pasteItems.length > 0 ? $scope.labels.grid_addElement : $scope.labels.content_createEmpty;
$scope.overlayMenu.clickClearPaste = function($event) {
$event.stopPropagation();
$event.preventDefault();
clipboardService.clearEntriesOfType("elementType", contentTypeAliases);
$scope.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually.
};
if ($scope.overlayMenu.availableItems.length === 1 && $scope.overlayMenu.pasteItems.length === 0) {
// only one scaffold type - no need to display the picker
$scope.addNode($scope.scaffolds[0].contentTypeAlias);
return;
}
$scope.overlayMenu.show = true;
};
@@ -201,19 +233,20 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
$scope.deleteNode = function (idx) {
if ($scope.nodes.length > $scope.model.config.minItems) {
if ($scope.model.config.confirmDeletes && $scope.model.config.confirmDeletes === 1) {
localizationService.localize("content_nestedContentDeleteItem").then(function (value) {
if (confirm(value)) {
$scope.nodes.splice(idx, 1);
$scope.setDirty();
updateModel();
}
});
} else {
$scope.nodes.splice(idx, 1);
$scope.setDirty();
updateModel();
}
$scope.nodes.splice(idx, 1);
$scope.setDirty();
updateModel();
}
};
$scope.requestDeleteNode = function (idx) {
if ($scope.model.config.confirmDeletes === true) {
localizationService.localize("content_nestedContentDeleteItem").then(function (value) {
if (confirm(value)) {
$scope.deleteNode(idx);
}
});
} else {
$scope.deleteNode(idx);
}
};
@@ -247,20 +280,22 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
if ($scope.nodes[idx].name !== name) {
$scope.nodes[idx].name = name;
}
return name;
};
$scope.getIcon = function (idx) {
var scaffold = $scope.getScaffold($scope.model.value[idx].ncContentTypeAlias);
return scaffold && scaffold.icon ? iconHelper.convertFromLegacyIcon(scaffold.icon) : "icon-folder";
}
$scope.sortableOptions = {
axis: "y",
cursor: "move",
handle: ".umb-nested-content__icon--move",
handle:'.umb-nested-content__header-bar',
distance: 10,
opacity: 0.7,
tolerance: "pointer",
scroll: true,
start: function (ev, ui) {
updateModel();
// Yea, yea, we shouldn't modify the dom, sue me
@@ -298,7 +333,40 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
return contentType.ncAlias === alias;
});
}
$scope.showCopy = clipboardService.isSupported();
$scope.showPaste = false;
$scope.clickCopy = function($event, node) {
syncCurrentNode();
clipboardService.copy("elementType", node.contentTypeAlias, node);
$event.stopPropagation();
}
$scope.pasteFromClipboard = function(newNode) {
if (newNode === undefined) {
return;
}
// generate a new key.
newNode.key = String.CreateGuid();
$scope.nodes.push(newNode);
//updateModel();// done by setting current node...
$scope.currentNode = newNode;
}
function checkAbilityToPasteContent() {
$scope.showPaste = clipboardService.hasEntriesOfType("elementType", contentTypeAliases);
}
eventsService.on("clipboardService.storageUpdate", checkAbilityToPasteContent);
var notSupported = [
"Umbraco.Tags",
"Umbraco.UploadField",
@@ -317,9 +385,9 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
var tab = _.find(tabs, function (tab) {
return tab.id !== 0 && (tab.alias.toLowerCase() === contentType.ncTabAlias.toLowerCase() || contentType.ncTabAlias === "");
});
scaffold.tabs = [];
scaffold.variants[0].tabs = [];
if (tab) {
scaffold.tabs.push(tab);
scaffold.variants[0].tabs.push(tab);
angular.forEach(tab.properties,
function (property) {
@@ -348,7 +416,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
if ($scope.model.config.contentTypes.length === scaffoldsLoaded) {
// Because we're loading the scaffolds async one at a time, we need to
// sort them explicitly according to the sort order defined by the data type.
var contentTypeAliases = [];
contentTypeAliases = [];
_.each($scope.model.config.contentTypes, function (contentType) {
contentTypeAliases.push(contentType.ncAlias);
});
@@ -365,7 +433,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
// No such scaffold - the content type might have been deleted. We need to skip it.
continue;
}
initNode(scaffold, item);
createNode(scaffold, item);
}
}
@@ -382,64 +450,78 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
}
inited = true;
checkAbilityToPasteContent();
}
}
var initNode = function (scaffold, item) {
function createNode(scaffold, fromNcEntry) {
var node = angular.copy(scaffold);
node.key = item && item.key ? item.key : UUID.generate();
node.ncContentTypeAlias = scaffold.contentTypeAlias;
for (var t = 0; t < node.tabs.length; t++) {
var tab = node.tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
prop.propertyAlias = prop.alias;
prop.alias = $scope.model.alias + "___" + prop.alias;
// Force validation to occur server side as this is the
// only way we can have consistency between mandatory and
// regex validation messages. Not ideal, but it works.
prop.validation = {
mandatory: false,
pattern: ""
};
if (item) {
if (item[prop.propertyAlias]) {
prop.value = item[prop.propertyAlias];
node.key = fromNcEntry && fromNcEntry.key ? fromNcEntry.key : String.CreateGuid();
for (var v = 0; v < node.variants.length; v++) {
var variant = node.variants[v];
for (var t = 0; t < variant.tabs.length; t++) {
var tab = variant.tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
prop.propertyAlias = prop.alias;
prop.alias = $scope.model.alias + "___" + prop.alias;
// Force validation to occur server side as this is the
// only way we can have consistency between mandatory and
// regex validation messages. Not ideal, but it works.
prop.validation = {
mandatory: false,
pattern: ""
};
if (fromNcEntry && fromNcEntry[prop.propertyAlias]) {
prop.value = fromNcEntry[prop.propertyAlias];
}
}
}
}
$scope.nodes.push(node);
return node;
}
var updateModel = function () {
function convertNodeIntoNCEntry(node) {
var obj = {
key: node.key,
name: node.name,
ncContentTypeAlias: node.contentTypeAlias
};
for (var t = 0; t < node.variants[0].tabs.length; t++) {
var tab = node.variants[0].tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
if (typeof prop.value !== "function") {
obj[prop.propertyAlias] = prop.value;
}
}
}
return obj;
}
function syncCurrentNode() {
if ($scope.realCurrentNode) {
$scope.$broadcast("ncSyncVal", { key: $scope.realCurrentNode.key });
}
}
function updateModel() {
syncCurrentNode();
if (inited) {
var newValues = [];
for (var i = 0; i < $scope.nodes.length; i++) {
var node = $scope.nodes[i];
var newValue = {
key: node.key,
name: node.name,
ncContentTypeAlias: node.ncContentTypeAlias
};
for (var t = 0; t < node.tabs.length; t++) {
var tab = node.tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
if (typeof prop.value !== "function") {
newValue[prop.propertyAlias] = prop.value;
}
}
}
newValues.push(newValue);
newValues.push(convertNodeIntoNCEntry($scope.nodes[i]));
}
$scope.model.value = newValues;
}
@@ -457,23 +539,7 @@ angular.module("umbraco").controller("Umbraco.PropertyEditors.NestedContent.Prop
$scope.$on("$destroy", function () {
unsubscribe();
});
// TODO: Move this into a shared location?
var UUID = (function () {
var self = {};
var lut = []; for (var i = 0; i < 256; i++) { lut[i] = (i < 16 ? "0" : "") + (i).toString(16); }
self.generate = function () {
var d0 = Math.random() * 0xffffffff | 0;
var d1 = Math.random() * 0xffffffff | 0;
var d2 = Math.random() * 0xffffffff | 0;
var d3 = Math.random() * 0xffffffff | 0;
return lut[d0 & 0xff] + lut[d0 >> 8 & 0xff] + lut[d0 >> 16 & 0xff] + lut[d0 >> 24 & 0xff] + "-" +
lut[d1 & 0xff] + lut[d1 >> 8 & 0xff] + "-" + lut[d1 >> 16 & 0x0f | 0x40] + lut[d1 >> 24 & 0xff] + "-" +
lut[d2 & 0x3f | 0x80] + lut[d2 >> 8 & 0xff] + "-" + lut[d2 >> 16 & 0xff] + lut[d2 >> 24 & 0xff] +
lut[d3 & 0xff] + lut[d3 >> 8 & 0xff] + lut[d3 >> 16 & 0xff] + lut[d3 >> 24 & 0xff];
}
return self;
})();
}
]);
@@ -3,29 +3,26 @@
ng-class="{'umb-nested-content--narrow':!wideMode, 'umb-nested-content--wide':wideMode}">
<ng-form>
<div class="umb-nested-content__items" ng-hide="nodes.length == 0" ui-sortable="sortableOptions" ng-model="nodes">
<div class="umb-nested-content__items" ng-hide="nodes.length === 0" ui-sortable="sortableOptions" ng-model="nodes">
<div class="umb-nested-content__item" ng-repeat="node in nodes" ng-class="{ 'umb-nested-content__item--active' : $parent.realCurrentNode.key == node.key, 'umb-nested-content__item--single' : $parent.singleMode }">
<div class="umb-nested-content__item" ng-repeat="node in nodes" ng-class="{ 'umb-nested-content__item--active' : $parent.realCurrentNode.key === node.key, 'umb-nested-content__item--single' : $parent.singleMode }">
<div class="umb-nested-content__header-bar" ng-click="$parent.editNode($index)" ng-hide="$parent.singleMode">
<div class="umb-nested-content__heading"><i ng-if="showIcons" class="icon" ng-class="$parent.getIcon($index)"></i><span class="umb-nested-content__item-name" ng-bind="$parent.getName($index)"></span></div>
<div class="umb-nested-content__icons">
<a class="umb-nested-content__icon umb-nested-content__icon--edit" localize="title" title="general_edit" ng-class="{ 'umb-nested-content__icon--active' : $parent.realCurrentNode.id == node.id }" ng-click="$parent.editNode($index); $event.stopPropagation();" ng-show="$parent.maxItems > 1" prevent-default>
<i class="icon icon-edit"></i>
<a class="umb-nested-content__icon umb-nested-content__icon--copy" title="{{copyIconTitle}}" ng-click="clickCopy($event, node);" ng-if="showCopy" prevent-default>
<i class="icon icon-documents"></i>
</a>
<a class="umb-nested-content__icon umb-nested-content__icon--move" localize="title" title="actions_move" ng-click="$event.stopPropagation();" ng-show="$parent.nodes.length > 1" prevent-default>
<i class="icon icon-navigation"></i>
</a>
<a class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-class="{ 'umb-nested-content__icon--disabled': $parent.nodes.length <= $parent.minItems }" ng-click="$parent.deleteNode($index); $event.stopPropagation();" prevent-default>
<a class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-class="{ 'umb-nested-content__icon--disabled': $parent.nodes.length <= $parent.minItems }" ng-click="$parent.requestDeleteNode($index); $event.stopPropagation();" prevent-default>
<i class="icon icon-trash"></i>
</a>
</div>
</div>
<div class="umb-nested-content__content" ng-if="$parent.realCurrentNode.key == node.key && !$parent.sorting">
<div class="umb-nested-content__content" ng-if="$parent.realCurrentNode.key === node.key && !$parent.sorting">
<umb-nested-content-editor ng-model="node" tab-alias="ncTabAlias" />
</div>
</div>
@@ -49,6 +46,7 @@
<umb-overlay
ng-if="overlayMenu.show"
position="target"
size="overlayMenu.size"
view="overlayMenu.view"
model="overlayMenu">
</umb-overlay>
@@ -284,7 +284,9 @@
<key alias="readyToPublish">Klar til at udgive?</key>
<key alias="readyToSave">Klar til at gemme?</key>
<key alias="sendForApproval">Send til godkendelse</key>
<key alias="schedulePublishHelp">Vælg dato og klokkeslæt for at udgive og/eller afpublicere indholdet.</key>
<key alias="schedulePublishHelp">Vælg dato og klokkeslæt for at udgive og/eller afpublicere indholdet.</key>
<key alias="createEmpty">Opret ny</key>
<key alias="createFromClipboard">Indsæt fra udklipsmappen</key>
</area>
<area alias="blueprints">
<key alias="createBlueprintFrom">Opret en ny indholdsskabelon fra '%0%'</key>
@@ -293,7 +293,9 @@
<key alias="readyToPublish">Ready to Publish?</key>
<key alias="readyToSave">Ready to Save?</key>
<key alias="sendForApproval">Send for approval</key>
<key alias="schedulePublishHelp">Select the date and time to publish and/or unpublish the content item.</key>
<key alias="schedulePublishHelp">Select the date and time to publish and/or unpublish the content item.</key>
<key alias="createEmpty">Create new</key>
<key alias="createFromClipboard">Paste from clipboard</key>
</area>
<area alias="blueprints">
<key alias="createBlueprintFrom">Create a new Content Template from '%0%'</key>
@@ -297,7 +297,9 @@
<key alias="readyToPublish">Ready to Publish?</key>
<key alias="readyToSave">Ready to Save?</key>
<key alias="sendForApproval">Send for approval</key>
<key alias="schedulePublishHelp">Select the date and time to publish and/or unpublish the content item.</key>
<key alias="schedulePublishHelp">Select the date and time to publish and/or unpublish the content item.</key>
<key alias="createEmpty">Create new</key>
<key alias="createFromClipboard">Paste from clipboard</key>
</area>
<area alias="blueprints">
<key alias="createBlueprintFrom">Create a new Content Template from '%0%'</key>
@@ -1408,7 +1410,7 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="deleteUserSuccess">User %0% was deleted</key>
<key alias="resendInviteHeader">Invite user</key>
<key alias="resendInviteSuccess">Invitation has been re-sent to %0%</key>
<key alias="contentReqCulturePublishError">Cannot publish the document since the required '%0%' is not published</key>
<key alias="contentReqCulturePublishError">Cannot publish the document since the required '%0%' is not published</key>
<key alias="contentCultureValidationError">Validation failed for language '%0%'</key>
<key alias="documentTypeExportedSuccess">Document type was exported to file</key>
<key alias="documentTypeExportedError">An error occurred while exporting the document type</key>