diff --git a/src/Umbraco.Core/Constants-Web.cs b/src/Umbraco.Core/Constants-Web.cs
index 64216ba571..0e956cfd6a 100644
--- a/src/Umbraco.Core/Constants-Web.cs
+++ b/src/Umbraco.Core/Constants-Web.cs
@@ -17,6 +17,10 @@
/// The preview cookie name
///
public const string PreviewCookieName = "UMB_PREVIEW";
+ ///
+ /// Client-side cookie that determins wether the user has accepted to be in Preview Mode when visiting the website.
+ ///
+ public const string AcceptPreviewCookieName = "UMB-WEBSITE-PREVIEW-ACCEPT";
public const string InstallerCookieName = "umb_installId";
}
diff --git a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js
index 5ff8dd3633..412d0c9cc5 100644
--- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js
@@ -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) {
@@ -89,7 +89,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;
}
@@ -100,7 +100,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" },
@@ -136,17 +136,24 @@ 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);
-
- win.on("blur", windowBlurHandler);
+ function windowVisibilityHandler(e) {
+ // When tab is visible again:
+ if(document.hidden === false) {
+ checkPreviewState();
+ }
+ }
+ document.addEventListener("visibilitychange", windowVisibilityHandler);
$scope.$on("$destroy", function () {
- win.off("blur", handleBlwindowBlurHandlerur);
+ window.removeEventListener("blur", windowBlurHandler);
+ window.removeEventListener("visibilitychange", windowVisibilityHandler);
});
@@ -162,6 +169,167 @@ 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?
+
+ // 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 = `
Preview content?
+ You have exited preview mode, do you want to continue previewing this content?
`;
+ con.appendChild(modal);
+
+ var continueButton = document.createElement("button");
+ continueButton.type = "button";
+ continueButton.className = "umbraco-preview-dialog__continue";
+ continueButton.innerHTML = "Preview content";
+ 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
+ }
+ })
+ }
+ function getPageURL() {
+ var culture = $location.search().culture || getParameterByName("culture");
+ var relativeUrl = "/" + $scope.pageId;
+ if (culture) {
+ relativeUrl += '?culture=' + culture;
+ }
+ return relativeUrl;
+ }
+
/*****************************************************************************/
/* Preview devices */
/*****************************************************************************/
@@ -171,18 +339,21 @@ 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);
+ window.top.location.href = "../preview/end?redir=" + encodeURIComponent(getPageURL());
};
$scope.onFrameLoaded = function (iframe) {
@@ -239,8 +410,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() {
diff --git a/src/Umbraco.Web.UI.Client/src/websitepreview/websitepreview.js b/src/Umbraco.Web.UI.Client/src/websitepreview/websitepreview.js
index be5c989f68..cb76b47bb7 100644
--- a/src/Umbraco.Web.UI.Client/src/websitepreview/websitepreview.js
+++ b/src/Umbraco.Web.UI.Client/src/websitepreview/websitepreview.js
@@ -30,20 +30,31 @@
return c.substring(name.length, c.length);
}
}
- return "";
+ return null;
}
function exitPreviewMode() {
window.top.location.href = scriptElement.getAttribute("data-umbraco-path")+"/preview/end?redir=" + encodeURIComponent(window.location.pathname+window.location.search);
}
- function continuePreviewMode() {
- setCookie("UMB-WEBSITE-PREVIEW-ACCEPT", "true", 5);
+ function continuePreviewMode(minutsToExpire) {
+ setCookie("UMB-WEBSITE-PREVIEW-ACCEPT", "true", minutsToExpire || 2);
}
var user = getCookie("UMB-WEBSITE-PREVIEW-ACCEPT");
if (user != "true") {
+ // 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;
@@ -68,6 +79,7 @@
max-width: 540px;
font-family: Lato,Helvetica Neue,Helvetica,Arial,sans-serif;
font-size: 15px;
+ line-height: 1.5;
}
.umbraco-preview-dialog__headline {
@@ -75,19 +87,22 @@
font-size: 22px;
color: #1b264f;
margin-top:10px;
- margin-bottom: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;
@@ -109,37 +124,37 @@
var bodyEl = document.getElementsByTagName("BODY")[0];
- var fragment = document.createDocumentFragment();
+ var fragment = document.createElement("div");
+ var shadowRoot = fragment.attachShadow({ mode: 'open' });
var style = document.createElement("style");
style.innerHTML = modelStyles;
- fragment.appendChild(style);
+ shadowRoot.appendChild(style);
var con = document.createElement("div");
con.className = "umbraco-preview-dialog";
- fragment.appendChild(con);
+ shadowRoot.appendChild(con);
var modal = document.createElement("div");
modal.className = "umbraco-preview-dialog__modal";
- modal.innerHTML = `Preview mode
- Do you want to continue viewing latest saved version?
`;
+ modal.innerHTML = `View published version?
+ You are in Preview Mode, do you want exit in order to view the published version of your website?
`;
con.appendChild(modal);
var continueButton = document.createElement("button");
continueButton.type = "button";
continueButton.className = "umbraco-preview-dialog__continue";
- continueButton.innerHTML = "Continue";
- continueButton.addEventListener("click", function() {
- bodyEl.removeChild(style);
- bodyEl.removeChild(con);
- continuePreviewMode();
- });
+ continueButton.innerHTML = "View published version";
+ continueButton.addEventListener("click", exitPreviewMode);
modal.appendChild(continueButton);
var exitButton = document.createElement("button");
exitButton.type = "button";
- exitButton.innerHTML = "Exit preview mode";
- exitButton.addEventListener("click", exitPreviewMode);
+ exitButton.innerHTML = "Stay in preview mode";
+ exitButton.addEventListener("click", function() {
+ bodyEl.removeChild(fragment);
+ continuePreviewMode(5);
+ });
modal.appendChild(exitButton);
bodyEl.appendChild(fragment);
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml
index 4b7bcaee87..8b7d871ba4 100644
--- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml
+++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml
@@ -65,6 +65,9 @@
}
+
+
diff --git a/src/Umbraco.Web.UI/config/umbracoSettings.config b/src/Umbraco.Web.UI/config/umbracoSettings.config
index 2287464d07..086210e5a5 100644
--- a/src/Umbraco.Web.UI/config/umbracoSettings.config
+++ b/src/Umbraco.Web.UI/config/umbracoSettings.config
@@ -64,7 +64,7 @@
pointer-events:none;
left: 50%;
transform: translate(-50%, 40px);
- animation: umbraco-preview-badge--effect 10s 100ms ease both;
+ animation: umbraco-preview-badge--effect 10s 1.2s ease both;
border-radius: 3px 3px 0 0;
}}
@keyframes umbraco-preview-badge--effect {{
diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs
index f148655acd..c57968af06 100644
--- a/src/Umbraco.Web/Editors/PreviewController.cs
+++ b/src/Umbraco.Web/Editors/PreviewController.cs
@@ -101,11 +101,7 @@ namespace Umbraco.Web.Editors
[UmbracoAuthorize]
public ActionResult Frame(int id, string culture)
{
- var user = _umbracoContextAccessor.UmbracoContext.Security.CurrentUser;
-
- var previewToken = _publishedSnapshotService.EnterPreview(user, id);
-
- Response.Cookies.Set(new HttpCookie(Constants.Web.PreviewCookieName, previewToken));
+ EnterPreview(id);
// use a numeric url because content may not be in cache and so .Url would fail
var query = culture.IsNullOrWhiteSpace() ? string.Empty : $"?culture={culture}";
@@ -113,7 +109,16 @@ namespace Umbraco.Web.Editors
return null;
}
+ public ActionResult EnterPreview(int id)
+ {
+ var user = _umbracoContextAccessor.UmbracoContext.Security.CurrentUser;
+ var previewToken = _publishedSnapshotService.EnterPreview(user, id);
+
+ Response.Cookies.Set(new HttpCookie(Constants.Web.PreviewCookieName, previewToken));
+
+ return null;
+ }
public ActionResult End(string redir = null)
{
var previewToken = Request.GetPreviewCookieValue();
@@ -121,6 +126,8 @@ namespace Umbraco.Web.Editors
service.ExitPreview(previewToken);
System.Web.HttpContext.Current.ExpireCookie(Constants.Web.PreviewCookieName);
+ // Expire Client-side cookie that determins wether the user has accepted to be in Preview Mode when visiting the website.
+ System.Web.HttpContext.Current.ExpireCookie(Constants.Web.AcceptPreviewCookieName);
if (Uri.IsWellFormedUriString(redir, UriKind.Relative)
&& redir.StartsWith("//") == false