Merge pull request #5353 from umbraco/v8/feature/ux-copy-paste-service-for-nested-content
V8: ClipboardService + implementation for nested content
This commit is contained in:
@@ -512,6 +512,7 @@ Opens an overlay to show a custom YSOD. </br>
|
||||
model: "=",
|
||||
view: "=",
|
||||
position: "@",
|
||||
size: "=?",
|
||||
parentScope: "=?"
|
||||
},
|
||||
link: link
|
||||
|
||||
@@ -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 type {string} umbraco A string defining the type of data to storing, example: 'elementType', 'contentNode'
|
||||
* @param alias {string} umbraco A string defining the alias of the data to store, example: 'product'
|
||||
* @param data {object} umbraco 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 type {string} umbraco A string defining the type of data test for.
|
||||
* @param aliases {string} umbraco 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 type {string} umbraco A string defining the type of data to recive.
|
||||
* @param aliases {string} umbraco 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 type {string} umbraco A string defining the type of data to recive.
|
||||
* @param aliases {string} umbraco 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 type {string} umbraco A string defining the type of data to remove.
|
||||
* @param aliases {string} umbraco 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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
})();
|
||||
})();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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%;
|
||||
|
||||
@@ -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 | 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,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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user