Merge branch 'v8/dev' into v8/contrib

# Conflicts:
#	src/Umbraco.Examine/ContentIndexPopulator.cs
This commit is contained in:
Sebastiaan Janssen
2020-11-04 11:32:20 +01:00
44 changed files with 1507 additions and 529 deletions

View File

@@ -30,6 +30,7 @@ module.exports = {
// js files for backoffice
// processed in the js task
js: {
websitepreview: { files: "./src/websitepreview/**/*.js", out: "umbraco.websitepreview.js" },
preview: { files: "./src/preview/**/*.js", out: "umbraco.preview.js" },
installer: { files: "./src/installer/**/*.js", out: "umbraco.installer.js" },
filters: { files: "./src/common/filters/**/*.js", out: "umbraco.filters.js" },
@@ -56,7 +57,7 @@ module.exports = {
],
out: "umbraco.directives.js"
}
},
//selectors for copying all views into the build

View File

@@ -164,7 +164,7 @@
function inviteSavePassword() {
if (formHelper.submitForm({ scope: $scope })) {
if (formHelper.submitForm({ scope: $scope, formCtrl: vm.inviteUserPasswordForm })) {
vm.invitedUserPasswordModel.buttonState = "busy";
@@ -172,7 +172,7 @@
.then(function (data) {
//success
formHelper.resetForm({ scope: $scope });
formHelper.resetForm({ scope: $scope, formCtrl: vm.inviteUserPasswordForm });
vm.invitedUserPasswordModel.buttonState = "success";
//set the user and set them as logged in
vm.invitedUser = data;
@@ -181,7 +181,7 @@
vm.inviteStep = 2;
}, function (err) {
formHelper.resetForm({ scope: $scope, hasErrors: true });
formHelper.resetForm({ scope: $scope, hasErrors: true, formCtrl: vm.inviteUserPasswordForm });
formHelper.handleError(err);
vm.invitedUserPasswordModel.buttonState = "error";
});

View File

@@ -57,14 +57,32 @@
}
}
}
function replaceElementTypeBlockListUDIsResolver(obj, propClearingMethod) {
replaceRawBlockListUDIsResolver(obj.value, propClearingMethod);
function removeBlockReferences(obj) {
for (var k in obj) {
if(k === "contentUdi") {
delete obj[k];
} else if(k === "settingsUdi") {
delete obj[k];
} 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 != null && (propType === "object" || propType === "array")) {
removeBlockReferences(obj[k])
}
}
}
}
clipboardService.registerPastePropertyResolver(replaceElementTypeBlockListUDIsResolver, clipboardService.TYPES.ELEMENT_TYPE);
function elementTypeBlockResolver(obj, propPasteResolverMethod) {
// we could filter for specific Property Editor Aliases, but as the Block Editor structure can be used by many Property Editor we do not in this code know a good way to detect that this is a Block Editor and will therefor leave it to the value structure to determin this.
rawBlockResolver(obj.value, propPasteResolverMethod);
}
clipboardService.registerPastePropertyResolver(elementTypeBlockResolver, clipboardService.TYPES.ELEMENT_TYPE);
function replaceRawBlockListUDIsResolver(value, propClearingMethod) {
function rawBlockResolver(value, propPasteResolverMethod) {
if (value != null && typeof value === "object") {
// we got an object, and it has these three props then we are most likely dealing with a Block Editor.
@@ -72,19 +90,19 @@
replaceUdisOfObject(value.layout, value);
// replace UDIs for inner properties of this Block Editors content data.
// run resolvers for inner properties of this Blocks content data.
if(value.contentData.length > 0) {
value.contentData.forEach((item) => {
for (var k in item) {
propClearingMethod(item[k], clipboardService.TYPES.RAW);
propPasteResolverMethod(item[k], clipboardService.TYPES.RAW);
}
});
}
// replace UDIs for inner properties of this Block Editors settings data.
// run resolvers for inner properties of this Blocks settings data.
if(value.settingsData.length > 0) {
value.settingsData.forEach((item) => {
for (var k in item) {
propClearingMethod(item[k], clipboardService.TYPES.RAW);
propPasteResolverMethod(item[k], clipboardService.TYPES.RAW);
}
});
}
@@ -93,7 +111,29 @@
}
}
clipboardService.registerPastePropertyResolver(replaceRawBlockListUDIsResolver, clipboardService.TYPES.RAW);
clipboardService.registerPastePropertyResolver(rawBlockResolver, clipboardService.TYPES.RAW);
function provideNewUdisForBlockResolver(block, propPasteResolverMethod) {
if(block.layout) {
// We do not support layout child blocks currently, these should be stripped out as we only will be copying a single entry.
removeBlockReferences(block.layout);
}
if(block.data) {
// Make new UDI for content-element
block.data.udi = block.layout.contentUdi = udiService.create("element");
}
if(block.settingsData) {
// Make new UDI for settings-element
block.settingsData.udi = block.layout.settingsUdi = udiService.create("element");
}
}
clipboardService.registerPastePropertyResolver(provideNewUdisForBlockResolver, clipboardService.TYPES.BLOCK);
}]);

View File

@@ -13,7 +13,7 @@
(function () {
'use strict';
function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService, umbRequestHelper, clipboardService) {
function blockEditorModelObjectFactory($interpolate, $q, udiService, contentResource, localizationService, umbRequestHelper, clipboardService, notificationsService) {
/**
* Simple mapping from property model content entry to editing model,
@@ -773,6 +773,57 @@
return layoutEntry;
},
/**
* @ngdoc method
* @name createFromBlockData
* @methodOf umbraco.services.blockEditorModelObject
* @description Insert data from raw models
* @return {Object | null} Layout entry object, to be inserted at a decired location in the layout object. Or ´null´ if the given ElementType isnt supported by the block configuration.
*/
createFromBlockData: function (blockData) {
blockData = clipboardService.parseContentForPaste(blockData, clipboardService.TYPES.BLOCK);
// As the blockData is a cloned object we can use its layout part for our layout entry.
var layoutEntry = blockData.layout;
if (layoutEntry === null) {
return null;
}
var blockConfiguration;
if (blockData.data) {
// Ensure that we support the alias:
blockConfiguration = this.getBlockConfiguration(blockData.data.contentTypeKey);
if(blockConfiguration === null) {
return null;
}
this.value.contentData.push(blockData.data);
} else {
// We do not have data, this cannot be succesful paste.
return null;
}
if (blockData.settingsData) {
// Ensure that we support the alias:
if(blockConfiguration.settingsElementTypeKey) {
// If we have settings for this Block Configuration, we need to check that they align, if we dont we do not want to fail.
if(blockConfiguration.settingsElementTypeKey === blockData.settingsData.contentTypeKey) {
this.value.settingsData.push(blockData.settingsData);
} else {
notificationsService.error("Clipboard", "Couldn't paste because settings-data is not compatible.");
return null;
}
} else {
// We do not have settings currently, so lets get rid of the settings part and move on with the paste.
delete layoutEntry.settingUdi;
}
}
return layoutEntry;
},
/**

View File

@@ -10,33 +10,67 @@
* 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, iconHelper) {
function clipboardService($window, notificationsService, eventsService, localStorageService, iconHelper) {
const TYPES = {};
TYPES.ELEMENT_TYPE = "elementType";
TYPES.BLOCK = "block";
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];
clipboardTypeResolvers[TYPES.ELEMENT_TYPE] = function(element, propMethod) {
for (var t = 0; t < element.variants[0].tabs.length; t++) {
var tab = element.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.BLOCK] = function (block, propMethod) {
propMethod(block, TYPES.BLOCK);
if(block.data) {
Object.keys(block.data).forEach( key => {
if(key === 'udi' || key === 'contentTypeKey') {
return;
}
propMethod(block.data[key], TYPES.RAW);
});
}
if(block.settingsData) {
Object.keys(block.settingsData).forEach( key => {
if(key === 'udi' || key === 'contentTypeKey') {
return;
}
propMethod(block.settingsData[key], TYPES.RAW);
});
}
/*
// Concept for supporting Block that contains other Blocks.
// Missing clarifications:
// How do we ensure that the inner blocks of a block is supported in the new scenario. Not that likely but still relevant, so considerations should be made.
if(block.references) {
// A Block clipboard entry can contain other Block Clipboard Entries, here we will make sure to resolve those identical to the main entry.
for (var r = 0; r < block.references.length; r++) {
clipboardTypeResolvers[TYPES.BLOCK](block.references[r], propMethod);
}
}
*/
}
clipboardTypeResolvers[TYPES.RAW] = function(data, propMethod) {
for (var p = 0; p < data.length; p++) {
propMethod(data[p], TYPES.RAW);
}
}
var STORAGE_KEY = "umbClipboardService";
var retriveStorage = function() {
@@ -64,7 +98,10 @@ function clipboardService(notificationsService, eventsService, localStorageServi
var storageString = JSON.stringify(storage);
try {
// Check that we can parse the JSON:
var storageJSON = JSON.parse(storageString);
// Store the string:
localStorageService.set(STORAGE_KEY, storageString);
eventsService.emit("clipboardService.storageUpdate");
@@ -82,11 +119,11 @@ function clipboardService(notificationsService, eventsService, localStorageServi
type = type || "raw";
var resolvers = clearPropertyResolvers[type];
for (var i=0; i<resolvers.length; i++) {
resolvers[i](prop, resolvePropertyForStorage);
if (resolvers) {
for (var i=0; i<resolvers.length; i++) {
resolvers[i](prop, resolvePropertyForStorage);
}
}
}
var prepareEntryForStorage = function(type, entryData, firstLevelClearupMethod) {
@@ -123,9 +160,10 @@ function clipboardService(notificationsService, eventsService, localStorageServi
type = type || "raw";
var resolvers = pastePropertyResolvers[type];
for (var i=0; i<resolvers.length; i++) {
resolvers[i](prop, resolvePropertyForPaste);
if (resolvers) {
for (var i=0; i<resolvers.length; i++) {
resolvers[i](prop, resolvePropertyForPaste);
}
}
}
@@ -154,7 +192,7 @@ function clipboardService(notificationsService, eventsService, localStorageServi
var cloneData = Utilities.copy(pasteEntryData);
var typeResolver = clipboardTypeResolvers[type];
if(typeResolver) {
if (typeResolver) {
typeResolver(cloneData, resolvePropertyForPaste);
} else {
console.warn("Umbraco.service.clipboardService has no type resolver for '" + type + "'.");
@@ -427,10 +465,17 @@ function clipboardService(notificationsService, eventsService, localStorageServi
};
var emitClipboardStorageUpdate = _.debounce(function(e) {
eventsService.emit("clipboardService.storageUpdate");
}, 1000);
// Fires if LocalStorage was changed from another tab than this one.
$window.addEventListener("storage", emitClipboardStorageUpdate);
return service;
}
angular.module("umbraco.services").factory("clipboardService", clipboardService);

View File

@@ -14,7 +14,7 @@ function externalLoginInfoService(externalLoginInfo, umbRequestHelper) {
}
function getLoginProviderView(provider) {
if (provider && provider.properties.UmbracoBackOfficeExternalLoginOptions && provider.properties.UmbracoBackOfficeExternalLoginOptions.CustomBackOfficeView) {
if (provider && provider.properties && provider.properties.UmbracoBackOfficeExternalLoginOptions && provider.properties.UmbracoBackOfficeExternalLoginOptions.CustomBackOfficeView) {
return umbRequestHelper.convertVirtualToAbsolutePath(provider.properties.UmbracoBackOfficeExternalLoginOptions.CustomBackOfficeView);
}
return null;
@@ -26,10 +26,10 @@ function externalLoginInfoService(externalLoginInfo, umbRequestHelper) {
*/
function hasDenyLocalLogin(provider) {
if (!provider) {
return _.some(externalLoginInfo.providers, x => x.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin === true);
return _.some(externalLoginInfo.providers, x => x.properties && x.properties.UmbracoBackOfficeExternalLoginOptions && (x.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin === true));
}
else {
return provider.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin;
return provider && provider.properties && provider.properties.UmbracoBackOfficeExternalLoginOptions && (provider.properties.UmbracoBackOfficeExternalLoginOptions.DenyLocalLogin === true);
}
}

View File

@@ -101,6 +101,7 @@
}
&__check {
display: flex;
position: relative;

View File

@@ -19,7 +19,7 @@
yeah so this is a pain, but we must be super specific in targeting the mandatory property labels,
otherwise all properties within a reqired, nested, nested content property will all appear mandatory
*/
> ng-form > .control-group > .umb-el-wrap > .control-header label:after {
.umb-property > ng-form > .control-group > .umb-el-wrap > .control-header label:after {
content: '*';
color: @red;
}

View File

@@ -5,13 +5,13 @@
var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.services'])
.controller("previewController", function ($scope, $window, $location) {
.controller("previewController", function ($scope, $window, $location, $http) {
$scope.currentCulture = { iso: '', title: '...', icon: 'icon-loading' }
var cultures = [];
$scope.tabbingActive = false;
// There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution.
// There are a number of ways to detect when a focus state should be shown when using the tab key and this seems to be the simplest solution.
// For more information about this approach, see https://hackernoon.com/removing-that-ugly-focus-ring-and-keeping-it-too-6c8727fefcd2
function handleFirstTab(evt) {
if (evt.keyCode === 9) {
@@ -110,7 +110,7 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
if (isInit === "true") {
//do not continue, this is the first load of this new window, if this is passed in it means it's been
//initialized by the content editor and then the content editor will actually re-load this window without
//this flag. This is a required trick to get around chrome popup mgr.
//this flag. This is a required trick to get around chrome popup mgr.
return;
}
@@ -121,7 +121,7 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
$scope.valueAreLoaded = false;
$scope.devices = [
{ name: "fullsize", css: "fullsize", icon: "icon-application-window-alt", title: "Browser" },
{ name: "fullsize", css: "fullsize", icon: "icon-application-window-alt", title: "Fit browser" },
{ name: "desktop", css: "desktop shadow", icon: "icon-display", title: "Desktop" },
{ name: "laptop - 1366px", css: "laptop shadow", icon: "icon-laptop", title: "Laptop" },
{ name: "iPad portrait - 768px", css: "iPad-portrait shadow", icon: "icon-ipad", title: "Tablet portrait" },
@@ -157,17 +157,33 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
$scope.windowClickHandler = function () {
closeOthers();
}
function windowBlurHandler() {
closeOthers();
$scope.$digest();
}
window.addEventListener("blur", windowBlurHandler);
var win = $($window);
function windowVisibilityHandler(e) {
win.on("blur", windowBlurHandler);
var amountOfPreviewSessions = localStorage.getItem('UmbPreviewSessionAmount');
// When tab is visible again:
if(document.hidden === false) {
checkPreviewState();
}
}
document.addEventListener("visibilitychange", windowVisibilityHandler);
function beforeUnloadHandler(e) {
endPreviewSession();
}
window.addEventListener("beforeunload", beforeUnloadHandler, false);
$scope.$on("$destroy", function () {
win.off("blur", handleBlwindowBlurHandlerur);
window.removeEventListener("blur", windowBlurHandler);
document.removeEventListener("visibilitychange", windowVisibilityHandler);
window.removeEventListener("beforeunload", beforeUnloadHandler);
});
@@ -183,6 +199,197 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
$scope.pageUrl = "frame?" + query;
}
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(";");
for(var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == " ") {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return null;
}
function setCookie(cname, cvalue, exminutes) {
var d = new Date();
d.setTime(d.getTime() + (exminutes * 60 * 1000));
document.cookie = cname + "=" + cvalue + ";expires="+d.toUTCString() + ";path=/";
}
var hasPreviewDialog = false;
function checkPreviewState() {
if (getCookie("UMB_PREVIEW") === null) {
if(hasPreviewDialog === true) return;
hasPreviewDialog = true;
// Ask to re-enter preview mode?
const localizeVarsFallback = {
"returnToPreviewHeadline": "Preview content?",
"returnToPreviewDescription":"You have ended preview mode, do you want to continue previewing this content?",
"returnToPreviewButton":"Preview"
};
const umbLocalizedVars = Object.assign(localizeVarsFallback, $window.umbLocalizedVars);
// This modal is also used in websitepreview.js
var modelStyles = `
/* Webfont: LatoLatin-Bold */
@font-face {
font-family: 'Lato';
src: url('https://fonts.googleapis.com/css2?family=Lato:wght@700&display=swap');
font-style: normal;
font-weight: 700;
font-display: swap;
text-rendering: optimizeLegibility;
}
.umbraco-preview-dialog {
position: fixed;
display: flex;
align-items: center;
justify-content: center;
z-index: 99999999;
top:0;
bottom:0;
left:0;
right:0;
overflow: auto;
background-color: rgba(0,0,0,0.6);
}
.umbraco-preview-dialog__modal {
background-color: #fff;
border-radius: 6px;
box-shadow: 0 3px 7px rgba(0,0,0,0.3);
margin: auto;
padding: 30px 40px;
width: 100%;
max-width: 540px;
font-family: Lato,Helvetica Neue,Helvetica,Arial,sans-serif;
font-size: 15px;
}
.umbraco-preview-dialog__headline {
font-weight: 700;
font-size: 22px;
color: #1b264f;
margin-top:10px;
margin-bottom:20px;
}
.umbraco-preview-dialog__question {
margin-bottom:30px;
}
.umbraco-preview-dialog__modal > button {
display: inline-block;
cursor: pointer;
padding: 8px 18px;
text-align: center;
vertical-align: middle;
border-radius: 3px;
border:none;
font-family: inherit;
font-weight: 700;
font-size: 15px;
float:right;
margin-left:10px;
color: #1b264f;
background-color: #f6f1ef;
}
.umbraco-preview-dialog__modal > button:hover {
color: #2152a3;
background-color: #f6f1ef;
}
.umbraco-preview-dialog__modal > button.umbraco-preview-dialog__continue {
color: #fff;
background-color: #2bc37c;
}
.umbraco-preview-dialog__modal > button.umbraco-preview-dialog__continue:hover {
background-color: #39d38b;
}
`;
var bodyEl = document.getElementsByTagName("BODY")[0];
var fragment = document.createElement("div");
var shadowRoot = fragment.attachShadow({ mode: 'open' });
var style = document.createElement("style");
style.innerHTML = modelStyles;
shadowRoot.appendChild(style);
var con = document.createElement("div");
con.className = "umbraco-preview-dialog";
shadowRoot.appendChild(con);
var modal = document.createElement("div");
modal.className = "umbraco-preview-dialog__modal";
modal.innerHTML = `<div class="umbraco-preview-dialog__headline">${umbLocalizedVars.returnToPreviewHeadline}</div>
<div class="umbraco-preview-dialog__question">${umbLocalizedVars.returnToPreviewDescription}</div>`;
con.appendChild(modal);
var continueButton = document.createElement("button");
continueButton.type = "button";
continueButton.className = "umbraco-preview-dialog__continue";
continueButton.innerHTML = umbLocalizedVars.returnToPreviewButton;
continueButton.addEventListener("click", () => {
bodyEl.removeChild(fragment);
reenterPreviewMode();
hasPreviewDialog = false;
});
modal.appendChild(continueButton);
bodyEl.appendChild(fragment);
continueButton.focus();
}
}
function reenterPreviewMode() {
//Re-enter Preview Mode
$http({
method: 'POST',
url: '../preview/enterPreview',
params: {
id: $scope.pageId
}
})
startPreviewSession();
}
function getPageURL() {
var culture = $location.search().culture || getParameterByName("culture");
var relativeUrl = "/" + $scope.pageId;
if (culture) {
relativeUrl += '?culture=' + culture;
}
return relativeUrl;
}
function startPreviewSession() {
// lets registrer this preview session.
var amountOfPreviewSessions = Math.max(localStorage.getItem('UmbPreviewSessionAmount') || 0, 0);
amountOfPreviewSessions++;
localStorage.setItem('UmbPreviewSessionAmount', amountOfPreviewSessions);
}
function resetPreviewSessions() {
localStorage.setItem('UmbPreviewSessionAmount', 0);
}
function endPreviewSession() {
var amountOfPreviewSessions = localStorage.getItem('UmbPreviewSessionAmount') || 0;
amountOfPreviewSessions--;
localStorage.setItem('UmbPreviewSessionAmount', amountOfPreviewSessions);
if(amountOfPreviewSessions <= 0) {
// We are good to end preview mode.
navigator.sendBeacon("../preview/end");
}
}
startPreviewSession();
/*****************************************************************************/
/* Preview devices */
/*****************************************************************************/
@@ -192,21 +399,26 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
$scope.previewDevice = device;
};
/*****************************************************************************/
/* Open website in preview mode */
/*****************************************************************************/
$scope.openInBrowser = function () {
setCookie("UMB-WEBSITE-PREVIEW-ACCEPT", "true", 5);
window.open(getPageURL(), "_blank");
};
/*****************************************************************************/
/* Exit Preview */
/*****************************************************************************/
$scope.exitPreview = function () {
var culture = $location.search().culture || getParameterByName("culture");
var relativeUrl = "/" + $scope.pageId;
if (culture) {
relativeUrl += '?culture=' + culture;
}
window.top.location.href = "../preview/end?redir=" + encodeURIComponent(relativeUrl);
resetPreviewSessions();
window.top.location.href = "../preview/end?redir=" + encodeURIComponent(getPageURL());
};
$scope.onFrameLoaded = function (iframe) {
$scope.frameLoaded = true;
configureSignalR(iframe);
@@ -260,8 +472,8 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi
var vm = this;
vm.$postLink = function () {
var resultFrame = $element.find("#resultFrame");
resultFrame.on("load", iframeReady);
var resultFrame = $element.find("#resultFrame").get(0);
resultFrame.addEventListener("load", iframeReady);
};
function iframeReady() {

View File

@@ -10,7 +10,7 @@
<div ng-if="!vm.denyLocalLogin" ng-show="vm.invitedUser != null" class="umb-login-container">
<form name="inviteUserPasswordForm" novalidate="" ng-submit="vm.inviteSavePassword()" val-form-manager>
<form name="vm.inviteUserPasswordForm" novalidate="" ng-submit="vm.inviteSavePassword()" val-form-manager>
<div class="form" ng-if="vm.inviteStep === 1">
<h1 style="margin-bottom: 10px; text-align: left;">Hi, {{vm.invitedUser.name}}</h1>
<p style="line-height: 1.6; margin-bottom: 25px;">
@@ -79,7 +79,7 @@
size="xl"
unknown-char="+"
img-src="{{vm.invitedUser.avatars[3]}}"
img-srcset="{{vm.invitedUser.avatars[4]}} 2x, {{invitedUser.avatars[4]}} 3x">
img-srcset="{{vm.invitedUser.avatars[4]}} 2x, {{vm.invitedUser.avatars[4]}} 3x">
</umb-avatar>
</a>

View File

@@ -1,8 +1,8 @@
<div class="__showcase"
ng-class="{'--error':vm.elementTypeModel === null}"
ng-style="{'background-color':vm.blockConfigModel.backgroundColor, 'background-image': vm.styleBackgroundImage}">
<i ng-if="vm.blockConfigModel.thumbnail == null" class="__icon {{ vm.elementTypeModel ? vm.elementTypeModel.icon : 'icon-block' }}" ng-attr-style="{{'color:'+vm.blockConfigModel.iconColor+' !important'}}" aria-hidden="true"></i>
ng-style="{'background-color': vm.blockConfigModel ? vm.blockConfigModel.backgroundColor : '', 'background-image': vm.styleBackgroundImage}">
<i ng-if="vm.blockConfigModel == null || vm.blockConfigModel.thumbnail == null" class="__icon {{ vm.elementTypeModel ? vm.elementTypeModel.icon : 'icon-block' }}" ng-attr-style="{{'color:'+vm.blockConfigModel.iconColor+' !important'}}" aria-hidden="true"></i>
</div>
<div class="__info" ng-if="vm.elementTypeModel !== null">
<div class="__name" ng-bind="vm.elementTypeModel.name"></div>

View File

@@ -7,12 +7,13 @@ umb-block-card {
background-color: white;
border-radius: @doubleBorderRadius;
box-shadow: 0 1px 2px rgba(0,0,0,.2);
overflow: hidden;
transition: box-shadow 120ms;
cursor: pointer;
.umb-outline();
&:hover {
box-shadow: 0 1px 3px rgba(@ui-action-type-hover, .5);
}
@@ -60,6 +61,9 @@ umb-block-card {
background-size: cover;
background-position: 50% 50%;
background-repeat: no-repeat;
border-top-left-radius: @doubleBorderRadius;
border-top-right-radius: @doubleBorderRadius;
&.--error {
border: 2px solid @errorBackground;
@@ -85,6 +89,8 @@ umb-block-card {
background-color: #fff;
padding-top: 10px;
padding-bottom: 11px;// 10 + 1 to compentiate for the -1 substraction in margin-bottom.
border-bottom-left-radius: @doubleBorderRadius;
border-bottom-right-radius: @doubleBorderRadius;
&.--error {
background-color: @errorBackground;

View File

@@ -9,7 +9,7 @@
controllerAs: "vm",
transclude: true,
bindings: {
blockConfigModel: "<",
blockConfigModel: "<?",
elementTypeModel: "<"
}
});
@@ -35,7 +35,7 @@
}
vm.updateThumbnail = function () {
if (vm.blockConfigModel.thumbnail == null || vm.blockConfigModel.thumbnail === "") {
if (vm.blockConfigModel == null || vm.blockConfigModel.thumbnail == null || vm.blockConfigModel.thumbnail === "") {
vm.styleBackgroundImage = "none";
return;
}

View File

@@ -138,10 +138,10 @@
// We need to ensure that the property model value is an object, this is needed for modelObject to recive a reference and keep that updated.
if (typeof newVal !== 'object' || newVal === null) {// testing if we have null or undefined value or if the value is set to another type than Object.
newVal = {};
vm.model.value = newVal = {};
}
modelObject.update(newVal, $scope);
modelObject.update(vm.model.value, $scope);
onLoaded();
}
@@ -430,12 +430,12 @@
if (Array.isArray(item.pasteData)) {
var indexIncrementor = 0;
item.pasteData.forEach(function (entry) {
if (requestPasteFromClipboard(createIndex + indexIncrementor, entry)) {
if (requestPasteFromClipboard(createIndex + indexIncrementor, entry, item.type)) {
indexIncrementor++;
}
});
} else {
requestPasteFromClipboard(createIndex, item.pasteData);
requestPasteFromClipboard(createIndex, item.pasteData, item.type);
}
if(!(mouseEvent.ctrlKey || mouseEvent.metaKey)) {
blockPickerModel.close();
@@ -470,6 +470,7 @@
blockPickerModel.clickClearClipboard = function ($event) {
clipboardService.clearEntriesOfType(clipboardService.TYPES.ELEMENT_TYPE, vm.availableContentTypesAliases);
clipboardService.clearEntriesOfType(clipboardService.TYPES.BLOCK, vm.availableContentTypesAliases);
};
blockPickerModel.clipboardItems = [];
@@ -485,10 +486,28 @@
icon: entry.icon
}
}
if(Array.isArray(pasteEntry.data) === false) {
pasteEntry.blockConfigModel = modelObject.getScaffoldFromAlias(entry.alias);
} else {
pasteEntry.blockConfigModel = {};
if(Array.isArray(entry.data) === false) {
var scaffold = modelObject.getScaffoldFromAlias(entry.alias);
if(scaffold) {
pasteEntry.blockConfigModel = modelObject.getBlockConfiguration(scaffold.contentTypeKey);
}
}
blockPickerModel.clipboardItems.push(pasteEntry);
});
var entriesForPaste = clipboardService.retriveEntriesOfType(clipboardService.TYPES.BLOCK, vm.availableContentTypesAliases);
entriesForPaste.forEach(function (entry) {
var pasteEntry = {
type: clipboardService.TYPES.BLOCK,
date: entry.date,
pasteData: entry.data,
elementTypeModel: {
name: entry.label,
icon: entry.icon
}
}
if(Array.isArray(entry.data) === false) {
pasteEntry.blockConfigModel = modelObject.getBlockConfiguration(entry.data.data.contentTypeKey);
}
blockPickerModel.clipboardItems.push(pasteEntry);
});
@@ -504,42 +523,67 @@
var requestCopyAllBlocks = function() {
var elementTypesToCopy = vm.layout.filter(entry => entry.$block.config.unsupported !== true).map(entry => entry.$block.content);
var aliases = [];
// list aliases
var aliases = elementTypesToCopy.map(content => content.contentTypeAlias);
var elementTypesToCopy = vm.layout.filter(entry => entry.$block.config.unsupported !== true).map(
(entry) => {
// remove dublicates
aliases.push(entry.$block.content.contentTypeAlias);
// No need to clone the data as its begin handled by the clipboardService.
return {"layout": entry.$block.layout, "data": entry.$block.data, "settingsData":entry.$block.settingsData}
}
);
// remove duplicate aliases
aliases = aliases.filter((item, index) => aliases.indexOf(item) === index);
var contentNodeName = "";
var contentNodeName = "?";
var contentNodeIcon = null;
if(vm.umbVariantContent) {
contentNodeName = vm.umbVariantContent.editor.content.name;
if(vm.umbVariantContentEditors) {
contentNodeIcon = vm.umbVariantContentEditors.content.icon.split(" ")[0];
} else if (vm.umbElementEditorContent) {
contentNodeIcon = vm.umbElementEditorContent.model.documentType.icon.split(" ")[0];
}
} else if (vm.umbElementEditorContent) {
contentNodeName = vm.umbElementEditorContent.model.documentType.name
contentNodeName = vm.umbElementEditorContent.model.documentType.name;
contentNodeIcon = vm.umbElementEditorContent.model.documentType.icon.split(" ")[0];
}
localizationService.localize("clipboard_labelForArrayOfItemsFrom", [vm.model.label, contentNodeName]).then(function(localizedLabel) {
clipboardService.copyArray(clipboardService.TYPES.ELEMENT_TYPE, aliases, elementTypesToCopy, localizedLabel, "icon-thumbnail-list", vm.model.id);
clipboardService.copyArray(clipboardService.TYPES.BLOCK, aliases, elementTypesToCopy, localizedLabel, contentNodeIcon || "icon-thumbnail-list", vm.model.id);
});
}
function copyBlock(block) {
clipboardService.copy(clipboardService.TYPES.ELEMENT_TYPE, block.content.contentTypeAlias, block.content, block.label);
clipboardService.copy(clipboardService.TYPES.BLOCK, block.content.contentTypeAlias, {"layout": block.layout, "data": block.data, "settingsData":block.settingsData}, block.label, block.content.icon, block.content.udi);
}
function requestPasteFromClipboard(index, pasteEntry) {
function requestPasteFromClipboard(index, pasteEntry, pasteType) {
if (pasteEntry === undefined) {
return false;
}
var layoutEntry = modelObject.createFromElementType(pasteEntry);
var layoutEntry;
if (pasteType === clipboardService.TYPES.ELEMENT_TYPE) {
layoutEntry = modelObject.createFromElementType(pasteEntry);
} else if (pasteType === clipboardService.TYPES.BLOCK) {
layoutEntry = modelObject.createFromBlockData(pasteEntry);
} else {
// Not a supported paste type.
return false;
}
if (layoutEntry === null) {
// Pasting did not go well.
return false;
}
// make block model
var blockObject = getBlockObject(layoutEntry);
if (blockObject === null) {
// Initalization of the Block Object didnt go well, therefor we will fail the paste action.
return false;
}
@@ -554,6 +598,7 @@
return true;
}
function requestDeleteBlock(block) {
localizationService.localizeMany(["general_delete", "blockEditor_confirmDeleteBlockMessage", "contentTypeEditor_yesDelete"]).then(function (data) {
const overlay = {

View File

@@ -0,0 +1,227 @@
/*********************************************************************************************************/
/* website preview */
/*********************************************************************************************************/
(function() {
if ( window.location !== window.parent.location ) {
//we are in an iFrame, so lets skip the dialog.
return;
}
const scriptElement = document.currentScript;
function setCookie(cname, cvalue, exminutes) {
var d = new Date();
d.setTime(d.getTime() + (exminutes * 60 * 1000));
document.cookie = cname + "=" + cvalue + ";expires="+d.toUTCString() + ";path=/";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(";");
for(var i = 0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == " ") {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return null;
}
function beforeUnloadHandler(e) {
endPreviewSession();
}
window.addEventListener("beforeunload", beforeUnloadHandler, false);
function startPreviewSession() {
// lets registrer this preview session.
var amountOfPreviewSessions = Math.max(localStorage.getItem('UmbPreviewSessionAmount') || 0, 0);
amountOfPreviewSessions++;
localStorage.setItem('UmbPreviewSessionAmount', amountOfPreviewSessions);
}
function resetPreviewSessions() {
localStorage.setItem('UmbPreviewSessionAmount', 0);
}
function endPreviewSession() {
var amountOfPreviewSessions = localStorage.getItem('UmbPreviewSessionAmount') || 0;
amountOfPreviewSessions--;
localStorage.setItem('UmbPreviewSessionAmount', amountOfPreviewSessions);
if(amountOfPreviewSessions <= 0) {
// We are good to secretly end preview mode.
navigator.sendBeacon(scriptElement.getAttribute("data-umbraco-path")+"/preview/end");
}
}
startPreviewSession();
function endPreviewMode() {
resetPreviewSessions();
window.top.location.href = scriptElement.getAttribute("data-umbraco-path")+"/preview/end?redir=" + encodeURIComponent(window.location.pathname+window.location.search);
}
function continuePreviewMode(minutsToExpire) {
setCookie("UMB-WEBSITE-PREVIEW-ACCEPT", "true", minutsToExpire || 4);
}
var user = getCookie("UMB-WEBSITE-PREVIEW-ACCEPT");
if (user != "true") {
askToViewPublishedVersion();
} else {
continuePreviewMode();
}
function askToViewPublishedVersion() {
scriptElement.getAttribute("data-umbraco-path");
const request = new XMLHttpRequest();
request.open("GET", scriptElement.getAttribute("data-umbraco-path") + "/LocalizedText");
request.send();
request.onreadystatechange = (e) => {
if (request.readyState == 4 && request.status == 200) {
const jsonLocalization = JSON.parse(request.responseText);
createAskUserAboutVersionDialog(jsonLocalization);
}
}
}
function createAskUserAboutVersionDialog(jsonLocalization) {
const localizeVarsFallback = {
"viewPublishedContentHeadline": "Preview content?",
"viewPublishedContentDescription":"You have ended preview mode, do you want to continue previewing this content?",
"viewPublishedContentAcceptButton":"You have ended preview mode, do you want to continue previewing this content?",
"viewPublishedContentDeclineButton":"Preview"
};
const umbLocalizedVars = jsonLocalization || {};
umbLocalizedVars.preview = Object.assign(localizeVarsFallback, jsonLocalization.preview);
// This modal is also used in preview.js
var modelStyles = `
/* Webfont: LatoLatin-Bold */
@font-face {
font-family: 'Lato';
src: url('https://fonts.googleapis.com/css2?family=Lato:wght@700&display=swap');
font-style: normal;
font-weight: 700;
font-display: swap;
text-rendering: optimizeLegibility;
}
.umbraco-preview-dialog {
position: fixed;
display: flex;
align-items: center;
justify-content: center;
z-index: 99999999;
top:0;
bottom:0;
left:0;
right:0;
overflow: auto;
background-color: rgba(0,0,0,0.6);
}
.umbraco-preview-dialog__modal {
background-color: #fff;
border-radius: 6px;
box-shadow: 0 3px 7px rgba(0,0,0,0.3);
margin: auto;
padding: 30px 40px;
width: 100%;
max-width: 540px;
font-family: Lato,Helvetica Neue,Helvetica,Arial,sans-serif;
font-size: 15px;
line-height: 1.5;
}
.umbraco-preview-dialog__headline {
font-weight: 700;
font-size: 22px;
color: #1b264f;
margin-top:10px;
margin-bottom:20px;
}
.umbraco-preview-dialog__question {
margin-bottom:30px;
}
.umbraco-preview-dialog__modal > button {
display: inline-block;
cursor: pointer;
padding: 8px 18px;
text-align: center;
vertical-align: middle;
border-radius: 3px;
border:none;
font-family: inherit;
font-weight: 700;
font-size: 15px;
float:right;
margin-left:10px;
color: #1b264f;
background-color: #f6f1ef;
}
.umbraco-preview-dialog__modal > button:hover {
color: #2152a3;
background-color: #f6f1ef;
}
.umbraco-preview-dialog__modal > button.umbraco-preview-dialog__continue {
color: #fff;
background-color: #2bc37c;
}
.umbraco-preview-dialog__modal > button.umbraco-preview-dialog__continue:hover {
background-color: #39d38b;
}
`;
var bodyEl = document.getElementsByTagName("BODY")[0];
var fragment = document.createElement("div");
var shadowRoot = fragment.attachShadow({ mode: 'open' });
var style = document.createElement("style");
style.innerHTML = modelStyles;
shadowRoot.appendChild(style);
var con = document.createElement("div");
con.className = "umbraco-preview-dialog";
shadowRoot.appendChild(con);
var modal = document.createElement("div");
modal.className = "umbraco-preview-dialog__modal";
modal.innerHTML = `<div class="umbraco-preview-dialog__headline">${umbLocalizedVars.preview.viewPublishedContentHeadline}</div>
<div class="umbraco-preview-dialog__question">${umbLocalizedVars.preview.viewPublishedContentDescription}</div>`;
con.appendChild(modal);
var continueButton = document.createElement("button");
continueButton.type = "button";
continueButton.className = "umbraco-preview-dialog__continue";
continueButton.innerHTML = umbLocalizedVars.preview.viewPublishedContentAcceptButton;
continueButton.addEventListener("click", endPreviewMode);
modal.appendChild(continueButton);
var exitButton = document.createElement("button");
exitButton.type = "button";
exitButton.innerHTML = umbLocalizedVars.preview.viewPublishedContentDeclineButton;
exitButton.addEventListener("click", function() {
bodyEl.removeChild(fragment);
continuePreviewMode(5);
});
modal.appendChild(exitButton);
bodyEl.appendChild(fragment);
continueButton.focus();
}
})();