Merge pull request #8809 from umbraco/v8/bugfix/8801-resolvers-should-be-registred-for-specific-clipboard-entry-types

V8.7RC: Clipboard resolvers should target specific entry types
This commit is contained in:
Warren Buckley
2020-09-07 14:43:36 +01:00
committed by GitHub
6 changed files with 158 additions and 126 deletions

View File

@@ -49,6 +49,7 @@
} else if(k === "settingsUdi") {
replaceUdi(obj, k, propValue.settingsData);
} else {
// lets crawl through all properties of layout to make sure get captured all `contentUdi` and `settingsUdi` properties.
var propType = typeof obj[k];
if(propType === "object" || propType === "array") {
replaceUdisOfObject(obj[k], propValue)
@@ -56,22 +57,15 @@
}
}
}
function replaceElementTypeBlockListUDIsResolver(obj, propClearingMethod) {
replaceRawBlockListUDIsResolver(obj.value, propClearingMethod);
}
function replaceBlockListUDIsResolver(obj, propClearingMethod) {
clipboardService.registerPastePropertyResolver(replaceElementTypeBlockListUDIsResolver, clipboardService.TYPES.ELEMENT_TYPE);
if (typeof obj === "object") {
// 'obj' can both be a property object or the raw value of a inner property.
var value = obj;
// if we got a property object from a ContentTypeModel we need to look at the value. We check for value and editor to, sort of, ensure this is the case.
if(obj.value !== undefined && obj.editor !== undefined) {
value = obj.value;
// If value isnt a object, lets break out.
if(typeof obj.value !== "object") {
return;
}
}
function replaceRawBlockListUDIsResolver(value, propClearingMethod) {
if (typeof value === "object") {
// we got an object, and it has these three props then we are most likely dealing with a Block Editor.
if ((value.layout !== undefined && value.contentData !== undefined && value.settingsData !== undefined)) {
@@ -82,7 +76,7 @@
if(value.contentData.length > 0) {
value.contentData.forEach((item) => {
for (var k in item) {
propClearingMethod(item[k]);
propClearingMethod(item[k], clipboardService.TYPES.RAW);
}
});
}
@@ -90,7 +84,7 @@
if(value.settingsData.length > 0) {
value.settingsData.forEach((item) => {
for (var k in item) {
propClearingMethod(item[k]);
propClearingMethod(item[k], clipboardService.TYPES.RAW);
}
});
}
@@ -99,7 +93,7 @@
}
}
clipboardService.registerPastePropertyResolver(replaceBlockListUDIsResolver)
clipboardService.registerPastePropertyResolver(replaceRawBlockListUDIsResolver, clipboardService.TYPES.RAW);
}]);

View File

@@ -744,7 +744,7 @@
*/
createFromElementType: function (elementTypeDataModel) {
elementTypeDataModel = clipboardService.parseContentForPaste(elementTypeDataModel);
elementTypeDataModel = clipboardService.parseContentForPaste(elementTypeDataModel, clipboardService.TYPES.ELEMENT_TYPE);
var contentElementTypeKey = elementTypeDataModel.contentTypeKey;

View File

@@ -13,8 +13,28 @@
function clipboardService(notificationsService, eventsService, localStorageService, iconHelper) {
var clearPropertyResolvers = [];
var pastePropertyResolvers = [];
const TYPES = {};
TYPES.ELEMENT_TYPE = "elementType";
TYPES.RAW = "raw";
var clearPropertyResolvers = {};
var pastePropertyResolvers = {};
var clipboardTypeResolvers = {};
clipboardTypeResolvers[TYPES.ELEMENT_TYPE] = function(data, propMethod) {
for (var t = 0; t < data.variants[0].tabs.length; t++) {
var tab = data.variants[0].tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
propMethod(prop, TYPES.ELEMENT_TYPE);
}
}
}
clipboardTypeResolvers[TYPES.RAW] = function(data, propMethod) {
for (var p = 0; p < data.length; p++) {
propMethod(data[p], TYPES.RAW);
}
}
var STORAGE_KEY = "umbClipboardService";
@@ -58,28 +78,29 @@ function clipboardService(notificationsService, eventsService, localStorageServi
}
function clearPropertyForStorage(prop) {
function resolvePropertyForStorage(prop, type) {
for (var i=0; i<clearPropertyResolvers.length; i++) {
clearPropertyResolvers[i](prop, clearPropertyForStorage);
type = type || "raw";
var resolvers = clearPropertyResolvers[type];
for (var i=0; i<resolvers.length; i++) {
resolvers[i](prop, resolvePropertyForStorage);
}
}
var prepareEntryForStorage = function(entryData, firstLevelClearupMethod) {
var prepareEntryForStorage = function(type, entryData, firstLevelClearupMethod) {
var cloneData = Utilities.copy(entryData);
if (firstLevelClearupMethod != undefined) {
firstLevelClearupMethod(cloneData);
}
// remove keys from sub-entries
for (var t = 0; t < cloneData.variants[0].tabs.length; t++) {
var tab = cloneData.variants[0].tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
clearPropertyForStorage(prop);
}
var typeResolver = clipboardTypeResolvers[type];
if(typeResolver) {
typeResolver(cloneData, resolvePropertyForStorage);
} else {
console.warn("Umbraco.service.clipboardService has no type resolver for '" + type + "'.");
}
return cloneData;
@@ -98,9 +119,13 @@ function clipboardService(notificationsService, eventsService, localStorageServi
function resolvePropertyForPaste(prop) {
for (var i=0; i<pastePropertyResolvers.length; i++) {
pastePropertyResolvers[i](prop, resolvePropertyForPaste);
function resolvePropertyForPaste(prop, type) {
type = type || "raw";
var resolvers = pastePropertyResolvers[type];
for (var i=0; i<resolvers.length; i++) {
resolvers[i](prop, resolvePropertyForPaste);
}
}
@@ -108,6 +133,12 @@ function clipboardService(notificationsService, eventsService, localStorageServi
var service = {};
/**
* Default types to store in clipboard.
*/
service.TYPES = TYPES;
/**
* @ngdoc method
* @name umbraco.services.clipboardService#parseContentForPaste
@@ -119,18 +150,14 @@ function clipboardService(notificationsService, eventsService, localStorageServi
* Executed registered property resolvers for inner properties, to be done on pasting a clipbaord content entry.
*
*/
service.parseContentForPaste = function(contentEntryData) {
service.parseContentForPaste = function(pasteEntryData, type) {
var cloneData = Utilities.copy(pasteEntryData);
var cloneData = Utilities.copy(contentEntryData);
// remove keys from sub-entries
for (var t = 0; t < cloneData.variants[0].tabs.length; t++) {
var tab = cloneData.variants[0].tabs[t];
for (var p = 0; p < tab.properties.length; p++) {
var prop = tab.properties[p];
resolvePropertyForPaste(prop);
}
var typeResolver = clipboardTypeResolvers[type];
if(typeResolver) {
typeResolver(cloneData, resolvePropertyForPaste);
} else {
console.warn("Umbraco.service.clipboardService has no type resolver for '" + type + "'.");
}
return cloneData;
@@ -143,14 +170,15 @@ function clipboardService(notificationsService, eventsService, localStorageServi
* @methodOf umbraco.services.clipboardService
*
* @param {string} function A method executed for every property and inner properties copied.
* @param {string} string A string representing the property type format for this resolver to execute for.
*
* @description
* Executed for all properties including inner properties when performing a copy action.
* Executed for all properties of given type when performing a copy action.
*
* @deprecated Incorrect spelling please use 'registerClearPropertyResolver'
*/
service.registrerClearPropertyResolver = function(resolver) {
this.registerClearPropertyResolver(resolver);
service.registrerClearPropertyResolver = function(resolver, type) {
this.registerClearPropertyResolver(resolver, type);
};
/**
@@ -158,13 +186,18 @@ function clipboardService(notificationsService, eventsService, localStorageServi
* @name umbraco.services.clipboardService#registerClearPropertyResolver
* @methodOf umbraco.services.clipboardService
*
* @param {string} function A method executed for every property and inner properties copied. Notice the method both needs to deal with retriving a property object {alias:..editor:..value:... ,...} and the raw property value as if the property is an inner property of a nested property.
* @param {method} function A method executed for every property and inner properties copied. Notice the method both needs to deal with retriving a property object {alias:..editor:..value:... ,...} and the raw property value as if the property is an inner property of a nested property.
* @param {string} string A string representing the property type format for this resolver to execute for.
*
* @description
* Executed for all properties including inner properties when performing a copy action.
* Executed for all properties of given type when performing a copy action.
*/
service.registerClearPropertyResolver = function(resolver) {
clearPropertyResolvers.push(resolver);
service.registerClearPropertyResolver = function(resolver, type) {
type = type || "raw";
if(!clearPropertyResolvers[type]) {
clearPropertyResolvers[type] = [];
}
clearPropertyResolvers[type].push(resolver);
};
/**
@@ -172,13 +205,37 @@ function clipboardService(notificationsService, eventsService, localStorageServi
* @name umbraco.services.clipboardService#registerPastePropertyResolver
* @methodOf umbraco.services.clipboardService
*
* @param {string} function A method executed for every property and inner properties pasted. Notice the method both needs to deal with retriving a property object {alias:..editor:..value:... ,...} and the raw property value as if the property is an inner property of a nested property.
* @param {method} function A method executed for every property and inner properties copied. Notice the method both needs to deal with retriving a property object {alias:..editor:..value:... ,...} and the raw property value as if the property is an inner property of a nested property.
* @param {string} string A string representing the property type format for this resolver to execute for.
*
* @description
* Executed for all properties of given type when performing a paste action.
*/
service.registerPastePropertyResolver = function(resolver, type) {
type = type || "raw";
if(!pastePropertyResolvers[type]) {
pastePropertyResolvers[type] = [];
}
pastePropertyResolvers[type].push(resolver);
};
/**
* @ngdoc method
* @name umbraco.services.clipboardService#registrerTypeResolvers
* @methodOf umbraco.services.clipboardService
*
* @param {method} function A method for to execute resolvers for each properties of the specific type.
* @param {string} string A string representing the content type format for this resolver to execute for.
*
* @description
* Executed for all properties including inner properties when performing a paste action.
*/
service.registerPastePropertyResolver = function(resolver) {
pastePropertyResolvers.push(resolver);
service.registrerTypeResolvers = function(resolver, type) {
if(!clipboardTypeResolvers[type]) {
clipboardTypeResolvers[type] = [];
}
clipboardTypeResolvers[type].push(resolver);
};
@@ -212,7 +269,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi
}
);
var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data, firstLevelClearupMethod), label:displayLabel, icon:displayIcon, date:Date.now()};
var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(type, data, firstLevelClearupMethod), label:displayLabel, icon:displayIcon, date:Date.now()};
storage.entries.push(entry);
if (saveStorage(storage) === true) {
@@ -242,10 +299,14 @@ function clipboardService(notificationsService, eventsService, localStorageServi
*/
service.copyArray = function(type, aliases, datas, displayLabel, displayIcon, uniqueKey, firstLevelClearupMethod) {
if (type === "elementTypeArray") {
type = "elementType";
}
var storage = retriveStorage();
// Clean up each entry
var copiedDatas = datas.map(data => prepareEntryForStorage(data, firstLevelClearupMethod));
var copiedDatas = datas.map(data => prepareEntryForStorage(type, data, firstLevelClearupMethod));
// remove previous copies of this entry:
storage.entries = storage.entries.filter(

View File

@@ -188,25 +188,29 @@ ng-form.ng-invalid-val-server-match-settings > .umb-block-list__block > .umb-blo
> .__plus {
position: absolute;
pointer-events: none; // lets stop avoiding the mouse values in JS move event.
width: 24px;
height: 24px;
padding: 0;
border-radius: 3em;
border: 2px solid @blueMid;
display: flex;
justify-content: center;
align-items: center;
pointer-events: none; // lets stop avoiding the mouse values in JS move event.
box-sizing: border-box;
width: 28px;
height: 28px;
margin-left: -16px - 8px;
margin-top: -16px;
padding: 0;
border-radius: 3em;
border: 2px solid @blueMid;
color: @blueMid;
line-height: 22px;
font-size: 20px;
font-weight: 800;
background-color: rgba(255, 255, 255, .96);
box-shadow: 0 0 0 2px rgba(255, 255, 255, .96);
transform: scale(0) translate(-80%, -50%);
transform: scale(0);
transition: transform 240ms ease-in;
animation: umb-block-list__block--create-button_after 800ms ease-in-out infinite;
animation: umb-block-list__block--create-button__plus 400ms ease-in-out infinite;
@keyframes umb-block-list__block--create-button_after {
@keyframes umb-block-list__block--create-button__plus {
0% { color: rgba(@blueMid, 0.8); }
50% { color: rgba(@blueMid, 1); }
100% { color: rgba(@blueMid, 0.8); }
@@ -224,7 +228,7 @@ ng-form.ng-invalid-val-server-match-settings > .umb-block-list__block > .umb-blo
transition-duration: 120ms;
> .__plus {
transform: scale(1) translate(-80%, -50%);
transform: scale(1);
transition-timing-function: cubic-bezier(0.175, 0.885, 0.32, 1.275);
}
}

View File

@@ -427,7 +427,7 @@
size: (amountOfAvailableTypes > 8 ? "medium" : "small"),
filter: (amountOfAvailableTypes > 8),
clickPasteItem: function(item, mouseEvent) {
if (item.type === "elementTypeArray") {
if (Array.isArray(item.pasteData)) {
var indexIncrementor = 0;
item.pasteData.forEach(function (entry) {
if (requestPasteFromClipboard(createIndex + indexIncrementor, entry)) {
@@ -469,42 +469,28 @@
};
blockPickerModel.clickClearClipboard = function ($event) {
clipboardService.clearEntriesOfType("elementType", vm.availableContentTypesAliases);
clipboardService.clearEntriesOfType("elementTypeArray", vm.availableContentTypesAliases);
clipboardService.clearEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, vm.availableContentTypesAliases);
};
blockPickerModel.clipboardItems = [];
var singleEntriesForPaste = clipboardService.retriveEntriesOfType("elementType", vm.availableContentTypesAliases);
singleEntriesForPaste.forEach(function (entry) {
blockPickerModel.clipboardItems.push(
{
type: "elementType",
date: entry.date,
pasteData: entry.data,
blockConfigModel: modelObject.getScaffoldFromAlias(entry.alias),
elementTypeModel: {
name: entry.label,
icon: entry.icon
}
var entriesForPaste = clipboardService.retriveEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, vm.availableContentTypesAliases);
entriesForPaste.forEach(function (entry) {
var pasteEntry = {
type: clipboardService.TYPES.ELEMENT_TYPE,
date: entry.date,
pasteData: entry.data,
elementTypeModel: {
name: entry.label,
icon: entry.icon
}
);
});
var arrayEntriesForPaste = clipboardService.retriveEntriesOfType("elementTypeArray", vm.availableContentTypesAliases);
arrayEntriesForPaste.forEach(function (entry) {
blockPickerModel.clipboardItems.push(
{
type: "elementTypeArray",
date: entry.date,
pasteData: entry.data,
blockConfigModel: {}, // no block configuration for paste items of elementTypeArray.
elementTypeModel: {
name: entry.label,
icon: entry.icon
}
}
);
}
if(Array.isArray(pasteEntry.data) === false) {
pasteEntry.blockConfigModel = modelObject.getScaffoldFromAlias(entry.alias);
} else {
pasteEntry.blockConfigModel = {};
}
blockPickerModel.clipboardItems.push(pasteEntry);
});
blockPickerModel.clipboardItems.sort( (a, b) => {
@@ -534,11 +520,11 @@
}
localizationService.localize("clipboard_labelForArrayOfItemsFrom", [vm.model.label, contentNodeName]).then(function(localizedLabel) {
clipboardService.copyArray("elementTypeArray", aliases, elementTypesToCopy, localizedLabel, "icon-thumbnail-list", vm.model.id);
clipboardService.copyArray(clipboardService.TYPES.ELEMENT_TYPE, aliases, elementTypesToCopy, localizedLabel, "icon-thumbnail-list", vm.model.id);
});
}
function copyBlock(block) {
clipboardService.copy("elementType", block.content.contentTypeAlias, block.content, block.label);
clipboardService.copy(clipboardService.TYPES.ELEMENT_TYPE, block.content.contentTypeAlias, block.content, block.label);
}
function requestPasteFromClipboard(index, pasteEntry) {

View File

@@ -22,18 +22,18 @@
// Loop through all inner properties:
for (var k in obj) {
propClearingMethod(obj[k]);
propClearingMethod(obj[k], clipboardService.TYPES.RAW);
}
}
}
}
clipboardService.registerClearPropertyResolver(clearNestedContentPropertiesForStorage)
clipboardService.registerClearPropertyResolver(clearNestedContentPropertiesForStorage, clipboardService.TYPES.ELEMENT_TYPE)
function clearInnerNestedContentPropertiesForStorage(prop, propClearingMethod) {
// if we got an array, and it has a entry with ncContentTypeAlias this meants that we are dealing with a NestedContent property inside a NestedContent property.
// if we got an array, and it has a entry with ncContentTypeAlias this meants that we are dealing with a NestedContent property data.
if ((Array.isArray(prop) && prop.length > 0 && prop[0].ncContentTypeAlias !== undefined)) {
for (var i = 0; i < prop.length; i++) {
@@ -44,13 +44,13 @@
// Loop through all inner properties:
for (var k in obj) {
propClearingMethod(obj[k]);
propClearingMethod(obj[k], clipboardService.TYPES.RAW);
}
}
}
}
clipboardService.registerClearPropertyResolver(clearInnerNestedContentPropertiesForStorage)
clipboardService.registerClearPropertyResolver(clearInnerNestedContentPropertiesForStorage, clipboardService.TYPES.RAW)
}]);
angular
@@ -128,7 +128,7 @@
}
localizationService.localize("clipboard_labelForArrayOfItemsFrom", [model.label, nodeName]).then(function (data) {
clipboardService.copyArray("elementTypeArray", aliases, vm.nodes, data, "icon-thumbnail-list", model.id, clearNodeForCopy);
clipboardService.copyArray(clipboardService.TYPES.ELEMENT_TYPE, aliases, vm.nodes, data, "icon-thumbnail-list", model.id, clearNodeForCopy);
});
}
@@ -197,7 +197,7 @@
view: "itempicker",
event: $event,
clickPasteItem: function (item) {
if (item.type === "elementTypeArray") {
if (Array.isArray(item.data)) {
_.each(item.data, function (entry) {
pasteFromClipboard(entry);
});
@@ -239,21 +239,9 @@
vm.overlayMenu.pasteItems = [];
var singleEntriesForPaste = clipboardService.retriveEntriesOfType("elementType", contentTypeAliases);
_.each(singleEntriesForPaste, function (entry) {
var entriesForPaste = clipboardService.retriveEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, contentTypeAliases);
_.each(entriesForPaste, function (entry) {
vm.overlayMenu.pasteItems.push({
type: "elementType",
date: entry.date,
name: entry.label,
data: entry.data,
icon: entry.icon
});
});
var arrayEntriesForPaste = clipboardService.retriveEntriesOfType("elementTypeArray", contentTypeAliases);
_.each(arrayEntriesForPaste, function (entry) {
vm.overlayMenu.pasteItems.push({
type: "elementTypeArray",
date: entry.date,
name: entry.label,
data: entry.data,
@@ -271,8 +259,7 @@
vm.overlayMenu.clickClearPaste = function ($event) {
$event.stopPropagation();
$event.preventDefault();
clipboardService.clearEntriesOfType("elementType", contentTypeAliases);
clipboardService.clearEntriesOfType("elementTypeArray", contentTypeAliases);
clipboardService.clearEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, contentTypeAliases);
vm.overlayMenu.pasteItems = [];// This dialog is not connected via the clipboardService events, so we need to update manually.
vm.overlayMenu.hideHeader = false;
};
@@ -463,7 +450,7 @@
syncCurrentNode();
clipboardService.copy("elementType", node.contentTypeAlias, node, null, null, null, clearNodeForCopy);
clipboardService.copy(clipboardService.TYPES.ELEMENT_TYPE, node.contentTypeAlias, node, null, null, null, clearNodeForCopy);
$event.stopPropagation();
}
@@ -474,7 +461,7 @@
return;
}
newNode = clipboardService.parseContentForPaste(newNode);
newNode = clipboardService.parseContentForPaste(newNode, clipboardService.TYPES.ELEMENT_TYPE);
// generate a new key.
newNode.key = String.CreateGuid();
@@ -487,7 +474,7 @@
}
function checkAbilityToPasteContent() {
vm.showPaste = clipboardService.hasEntriesOfType("elementType", contentTypeAliases) || clipboardService.hasEntriesOfType("elementTypeArray", contentTypeAliases);
vm.showPaste = clipboardService.hasEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, contentTypeAliases);
}
eventsService.on("clipboardService.storageUpdate", checkAbilityToPasteContent);