From 3196561ad80d0755222e7faa3b1c9f1d151250d0 Mon Sep 17 00:00:00 2001 From: leekelleher Date: Thu, 16 May 2024 11:56:42 +0100 Subject: [PATCH 1/7] Adds `preview.js` to replace the legacy `umbraco.websitepreview.min.js` script. Updates the content's `Id` with `Key`. --- .../wwwroot/umbraco/website/preview.js | 151 ++++++++++++++++++ .../Configuration/Models/ContentSettings.cs | 137 +--------------- .../Views/UmbracoViewPage.cs | 2 +- .../Extensions/HtmlHelperRenderExtensions.cs | 2 +- 4 files changed, 156 insertions(+), 136 deletions(-) create mode 100644 src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js new file mode 100644 index 000000000000..ce27d1ef05c7 --- /dev/null +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js @@ -0,0 +1,151 @@ +(() => { + "use strict"; + + // Quick exit if within an iframe. + if (window.self !== window.top) return; + + class UmbWebsitePreviewElement extends HTMLElement { + static observedAttributes = ["path", "unique", "url"]; + + #path; + #unique; + #url; + + constructor() { + super(); + } + + connectedCallback() { + this.#render(); + } + + attributeChangedCallback(name, oldValue, newValue) { + switch (name) { + case "path": + this.#path = newValue; + break; + + case "unique": + this.#unique = newValue; + break; + + case "url": + this.#url = newValue; + break; + + default: + break; + } + } + + #render() { + const shadow = this.attachShadow({ mode: "open" }); + + const wrapper = document.createElement("div"); + wrapper.id = "umbracoPreviewBadge"; + wrapper.className = "umbraco-preview-badge"; + + const title = document.createElement("span"); + title.className = "umbraco-preview-badge__header"; + title.textContent = "Preview mode"; + + wrapper.appendChild(title); + + const btnOpen = document.createElement('a'); + btnOpen.classList.add('umbraco-preview-badge__a', 'open'); + btnOpen.title = 'Open preview in BackOffice'; + btnOpen.href = `${this.#path}/preview/?id=${this.#unique}`; + btnOpen.innerHTML = '…'; + + wrapper.appendChild(btnOpen); + + const btnExit = document.createElement("a"); + btnExit.classList.add("umbraco-preview-badge__a", "end"); + btnExit.title = "End preview mode"; + btnExit.href = `${this.#path}/preview/end/?redir=${this.#url}`; + btnExit.innerHTML = ` + + Click to end preview mode + + + + +`; + + wrapper.appendChild(btnExit); + + const style = document.createElement("style"); + + style.textContent = ` +.umbraco-preview-badge { + position: fixed; + bottom: 0; + display: inline-flex; + background: rgba(27, 38, 79, 0.9); + color: #fff; + font-size: 12px; + z-index: 99999999; + justify-content: center; + align-items: center; + box-shadow: 0 5px 10px rgba(0, 0, 0, .2), 0 1px 2px rgba(0, 0, 0, .2); + line-height: 1; + pointer-events:none; + left: 50%; + transform: translate(-50%, 40px); + animation: umbraco-preview-badge--effect 10s 1.2s ease both; + border-radius: 3px 3px 0 0; +} +@keyframes umbraco-preview-badge--effect { + 0% { transform: translate(-50%, 40px); animation-timing-function: ease-out; } + 1.5% { transform: translate(-50%, -20px); animation-timing-function: ease-in; } + 5.0% { transform: translate(-50%, -8px); animation-timing-function: ease-in; } + 7.5% { transform: translate(-50%, -4px); animation-timing-function: ease-in; } + 9.2% { transform: translate(-50%, -2px); animation-timing-function: ease-in; } + 3.5%, 6.5%, 8.5% { transform: translate(-50%, 0); animation-timing-function: ease-out; } + 9.7% { transform: translate(-50%, 0); animation-timing-function: ease-out; } + 10.0% { transform: translate(-50%, 0); } + 60% { transform: translate(-50%, 0); animation-timing-function: ease-out; } + 61.5% { transform: translate(-50%, -20px); animation-timing-function: ease-in; } + 65.0% { transform: translate(-50%, -8px); animation-timing-function: ease-in; } + 67.5% { transform: translate(-50%, -4px); animation-timing-function: ease-in; } + 69.2% { transform: translate(-50%, -2px); animation-timing-function: ease-in; } + 63.5%, 66.5%, 68.5% { transform: translate(-50%, 0); animation-timing-function: ease-out; } + 69.7% { transform: translate(-50%, 0); animation-timing-function: ease-out; } + 70.0% { transform: translate(-50%, 0); } + 100.0% { transform: translate(-50%, 0); } +} +.umbraco-preview-badge__header { + padding: 1em; + font-weight: bold; + pointer-events:none; +} +.umbraco-preview-badge__a { + width: 3em; + padding: 1em; + display: flex; + flex-shrink: 0; + justify-content: center; + align-items: center; + align-self: stretch; + color:white; + text-decoration:none; + font-weight: bold; + border-left: 1px solid hsla(0,0%,100%,.25); + pointer-events:all; +} +.umbraco-preview-badge__a svg { + width: 1em; + height:1em; +} +.umbraco-preview-badge__a:hover { + background: #202d5e; +} + `; + + shadow.appendChild(style); + shadow.appendChild(wrapper); + } + } + + window.customElements.define("umb-website-preview", UmbWebsitePreviewElement); +})(); diff --git a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs index 3f7b23a0956c..3d10a6b2675a 100644 --- a/src/Umbraco.Core/Configuration/Models/ContentSettings.cs +++ b/src/Umbraco.Core/Configuration/Models/ContentSettings.cs @@ -13,140 +13,9 @@ public class ContentSettings { internal const bool StaticResolveUrlsFromTextString = false; - internal const string StaticDefaultPreviewBadge = - @" -
- Preview mode - - … - - - Click to end preview mode - -
- - "; + internal const string StaticDefaultPreviewBadge = @" + +"; internal const string StaticDisallowedUploadFiles = "ashx,aspx,ascx,config,cshtml,vbhtml,asmx,air,axd,xamlx"; internal const bool StaticShowDeprecatedPropertyEditors = false; diff --git a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs index 086a0b0c81a5..f851acab3b34 100644 --- a/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs +++ b/src/Umbraco.Web.Common/Views/UmbracoViewPage.cs @@ -142,7 +142,7 @@ public void WriteUmbracoContent(TagHelperOutput tagHelperOutput) ContentSettings.PreviewBadge, HostingEnvironment.ToAbsolute(GlobalSettings.UmbracoPath), Context.Request.GetEncodedUrl(), - UmbracoContext.PublishedRequest?.PublishedContent?.Id); + UmbracoContext.PublishedRequest?.PublishedContent?.Key); } else { diff --git a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs index d14f0bded3d2..3b3a95df2d21 100644 --- a/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web.Website/Extensions/HtmlHelperRenderExtensions.cs @@ -87,7 +87,7 @@ public static IHtmlContent PreviewBadge( contentSettings.PreviewBadge, hostingEnvironment.ToAbsolute(globalSettings.UmbracoPath), WebUtility.UrlEncode(httpContextAccessor.GetRequiredHttpContext().Request.Path), - umbracoContext.PublishedRequest?.PublishedContent?.Id); + umbracoContext.PublishedRequest?.PublishedContent?.Key); return new HtmlString(htmlBadge); } From 5873fd59fb33de51b88a172ca86c84e5f6acfda9 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 16 May 2024 17:15:26 +0200 Subject: [PATCH 2/7] allow any protected route to render the backoffice --- src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs b/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs index 397bba707806..a854be29f060 100644 --- a/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs +++ b/src/Umbraco.Cms.Api.Management/Routing/BackOfficeAreaRoutes.cs @@ -88,6 +88,6 @@ private void MapMinimalBackOffice(IEndpointRouteBuilder endpoints) Controller = ControllerExtensions.GetControllerName(), Action = nameof(BackOfficeDefaultController.Index), }, - constraints: new { slug = @"^(section.*|preview|upgrade|install|oauth_complete|logout|error)$" }); + constraints: new { slug = @"^(section|preview|upgrade|install|oauth_complete|logout|error).*$" }); } } From bd16233a079ec4e642c2ff0d1087c819ec4f2a03 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Thu, 16 May 2024 17:16:02 +0200 Subject: [PATCH 3/7] optimise component so it doesn't need to observe its attributes and use the popover API to show it on top of the content --- .../wwwroot/umbraco/website/preview.js | 58 ++++++------------- 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js index ce27d1ef05c7..a0034d31302d 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js @@ -1,49 +1,25 @@ -(() => { +!(() => { "use strict"; // Quick exit if within an iframe. if (window.self !== window.top) return; class UmbWebsitePreviewElement extends HTMLElement { - static observedAttributes = ["path", "unique", "url"]; - - #path; - #unique; - #url; - - constructor() { - super(); - } - connectedCallback() { this.#render(); } - attributeChangedCallback(name, oldValue, newValue) { - switch (name) { - case "path": - this.#path = newValue; - break; - - case "unique": - this.#unique = newValue; - break; - - case "url": - this.#url = newValue; - break; - - default: - break; - } - } - #render() { + const path = this.getAttribute("path") ?? '/umbraco'; + const unique = this.getAttribute("unique") ?? ''; + const url = this.getAttribute("url") ?? ''; + const shadow = this.attachShadow({ mode: "open" }); const wrapper = document.createElement("div"); wrapper.id = "umbracoPreviewBadge"; wrapper.className = "umbraco-preview-badge"; + wrapper.popover = "manual"; const title = document.createElement("span"); title.className = "umbraco-preview-badge__header"; @@ -54,7 +30,7 @@ const btnOpen = document.createElement('a'); btnOpen.classList.add('umbraco-preview-badge__a', 'open'); btnOpen.title = 'Open preview in BackOffice'; - btnOpen.href = `${this.#path}/preview/?id=${this.#unique}`; + btnOpen.href = `${path}/preview/?id=${unique}`; btnOpen.innerHTML = '…'; wrapper.appendChild(btnOpen); @@ -62,7 +38,7 @@ const btnExit = document.createElement("a"); btnExit.classList.add("umbraco-preview-badge__a", "end"); btnExit.title = "End preview mode"; - btnExit.href = `${this.#path}/preview/end/?redir=${this.#url}`; + btnExit.href = `${path}/preview/end/?redir=${url}`; btnExit.innerHTML = ` Click to end preview mode @@ -78,25 +54,27 @@ style.textContent = ` .umbraco-preview-badge { - position: fixed; - bottom: 0; display: inline-flex; background: rgba(27, 38, 79, 0.9); color: #fff; - font-size: 12px; - z-index: 99999999; + font-size: 87.5%; justify-content: center; align-items: center; box-shadow: 0 5px 10px rgba(0, 0, 0, .2), 0 1px 2px rgba(0, 0, 0, .2); line-height: 1; pointer-events:none; - left: 50%; - transform: translate(-50%, 40px); animation: umbraco-preview-badge--effect 10s 1.2s ease both; border-radius: 3px 3px 0 0; } +.umbraco-preview-badge[popover] { + inset: unset; + position: fixed; + bottom: 0; + left: 50%; + transform: translate(-50%, 55px); +} @keyframes umbraco-preview-badge--effect { - 0% { transform: translate(-50%, 40px); animation-timing-function: ease-out; } + 0% { transform: translate(-50%, 55px); animation-timing-function: ease-out; } 1.5% { transform: translate(-50%, -20px); animation-timing-function: ease-in; } 5.0% { transform: translate(-50%, -8px); animation-timing-function: ease-in; } 7.5% { transform: translate(-50%, -4px); animation-timing-function: ease-in; } @@ -105,7 +83,7 @@ 9.7% { transform: translate(-50%, 0); animation-timing-function: ease-out; } 10.0% { transform: translate(-50%, 0); } 60% { transform: translate(-50%, 0); animation-timing-function: ease-out; } - 61.5% { transform: translate(-50%, -20px); animation-timing-function: ease-in; } + 61.5% { transform: translate(-50%, -24px); animation-timing-function: ease-in; } 65.0% { transform: translate(-50%, -8px); animation-timing-function: ease-in; } 67.5% { transform: translate(-50%, -4px); animation-timing-function: ease-in; } 69.2% { transform: translate(-50%, -2px); animation-timing-function: ease-in; } From f2efc8523663934c6df0beeff4b16b1a11e9e0b1 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 21 May 2024 10:16:16 +0200 Subject: [PATCH 4/7] handle case where the culture could be set to "invariant" - we just want to set the "lang" attribute to the default ui language --- .../umbraco/UmbracoBackOffice/Index.cshtml | 8 +++++--- .../umbraco/UmbracoLogin/Index.cshtml | 3 +-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Index.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Index.cshtml index 0ec32181ee82..8d46aad3f181 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Index.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoBackOffice/Index.cshtml @@ -1,5 +1,6 @@ -@using System.Globalization +@using Microsoft.Extensions.Options @using Umbraco.Cms.Api.Management.Extensions +@using Umbraco.Cms.Core.Configuration.Models @using Umbraco.Cms.Core.Manifest @using Umbraco.Cms.Core.Serialization @using Umbraco.Cms.Web.Common.Hosting @@ -7,6 +8,7 @@ @inject IBackOfficePathGenerator BackOfficePathGenerator @inject IPackageManifestService PackageManifestService @inject IJsonSerializer JsonSerializer +@inject IOptions GlobalSettings @{ var backOfficePath = BackOfficePathGenerator.BackOfficePath; @@ -14,7 +16,7 @@ } - + @@ -30,7 +32,7 @@ - + diff --git a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml index 86ba0a9df71c..731ae9f81dd0 100644 --- a/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml +++ b/src/Umbraco.Cms.StaticAssets/umbraco/UmbracoLogin/Index.cshtml @@ -1,4 +1,3 @@ -@using System.Globalization @using Microsoft.AspNetCore.Mvc.TagHelpers @using Microsoft.Extensions.Options; @using Umbraco.Cms.Api.Management.Extensions @@ -35,7 +34,7 @@ @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers - + From 5d3f38f58c80a59a705c0974eb2f2e76ef6832db Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 21 May 2024 10:24:08 +0200 Subject: [PATCH 5/7] convert 'end preview' into an api request and reset the style of the button --- .../wwwroot/umbraco/website/preview.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js index a0034d31302d..daf84661e7f1 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js @@ -9,10 +9,17 @@ this.#render(); } + async #endPreview() { + await fetch(`/umbraco/management/api/v1/preview`, { + method: "DELETE" + }); + + window.location.href = this.getAttribute("url") ?? '/'; + } + #render() { const path = this.getAttribute("path") ?? '/umbraco'; const unique = this.getAttribute("unique") ?? ''; - const url = this.getAttribute("url") ?? ''; const shadow = this.attachShadow({ mode: "open" }); @@ -35,10 +42,10 @@ wrapper.appendChild(btnOpen); - const btnExit = document.createElement("a"); + const btnExit = document.createElement("button"); + btnExit.type = "button"; btnExit.classList.add("umbraco-preview-badge__a", "end"); btnExit.title = "End preview mode"; - btnExit.href = `${path}/preview/end/?redir=${url}`; btnExit.innerHTML = ` Click to end preview mode @@ -47,6 +54,7 @@ `; + btnExit.onclick = () => this.#endPreview(); wrapper.appendChild(btnExit); @@ -98,6 +106,7 @@ pointer-events:none; } .umbraco-preview-badge__a { + background: inherit; width: 3em; padding: 1em; display: flex; @@ -108,15 +117,17 @@ color:white; text-decoration:none; font-weight: bold; + border: 0; border-left: 1px solid hsla(0,0%,100%,.25); pointer-events:all; + cursor: pointer; } .umbraco-preview-badge__a svg { width: 1em; height:1em; } .umbraco-preview-badge__a:hover { - background: #202d5e; + background: #202d5e00; } `; From 39838b3bdbd6d38f230886f6418a3c2e6c333fac Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 21 May 2024 10:29:50 +0200 Subject: [PATCH 6/7] minimize function --- .../wwwroot/umbraco/website/preview.js | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js index daf84661e7f1..32b6feaa6abf 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js @@ -46,7 +46,21 @@ btnExit.type = "button"; btnExit.classList.add("umbraco-preview-badge__a", "end"); btnExit.title = "End preview mode"; - btnExit.innerHTML = ` + btnExit.innerHTML = this.exitIcon; + btnExit.onclick = () => this.#endPreview(); + + wrapper.appendChild(btnExit); + + const style = document.createElement("style"); + + style.textContent = this.styles; + + shadow.appendChild(style); + shadow.appendChild(wrapper); + } + + get exitIcon() { + return ` Click to end preview mode @@ -54,13 +68,10 @@ `; - btnExit.onclick = () => this.#endPreview(); - - wrapper.appendChild(btnExit); - - const style = document.createElement("style"); + } - style.textContent = ` + get styles() { + return ` .umbraco-preview-badge { display: inline-flex; background: rgba(27, 38, 79, 0.9); @@ -130,9 +141,6 @@ background: #202d5e00; } `; - - shadow.appendChild(style); - shadow.appendChild(wrapper); } } From 29220a9ca68c54874e06d50a520244c355a8e194 Mon Sep 17 00:00:00 2001 From: Jacob Overgaard <752371+iOvergaard@users.noreply.github.com> Date: Tue, 21 May 2024 10:37:52 +0200 Subject: [PATCH 7/7] move static text into constants --- .../wwwroot/umbraco/website/preview.js | 134 +++++++++--------- 1 file changed, 65 insertions(+), 69 deletions(-) diff --git a/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js index 32b6feaa6abf..2ca6a841076e 100644 --- a/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js +++ b/src/Umbraco.Cms.StaticAssets/wwwroot/umbraco/website/preview.js @@ -4,74 +4,7 @@ // Quick exit if within an iframe. if (window.self !== window.top) return; - class UmbWebsitePreviewElement extends HTMLElement { - connectedCallback() { - this.#render(); - } - - async #endPreview() { - await fetch(`/umbraco/management/api/v1/preview`, { - method: "DELETE" - }); - - window.location.href = this.getAttribute("url") ?? '/'; - } - - #render() { - const path = this.getAttribute("path") ?? '/umbraco'; - const unique = this.getAttribute("unique") ?? ''; - - const shadow = this.attachShadow({ mode: "open" }); - - const wrapper = document.createElement("div"); - wrapper.id = "umbracoPreviewBadge"; - wrapper.className = "umbraco-preview-badge"; - wrapper.popover = "manual"; - - const title = document.createElement("span"); - title.className = "umbraco-preview-badge__header"; - title.textContent = "Preview mode"; - - wrapper.appendChild(title); - - const btnOpen = document.createElement('a'); - btnOpen.classList.add('umbraco-preview-badge__a', 'open'); - btnOpen.title = 'Open preview in BackOffice'; - btnOpen.href = `${path}/preview/?id=${unique}`; - btnOpen.innerHTML = '…'; - - wrapper.appendChild(btnOpen); - - const btnExit = document.createElement("button"); - btnExit.type = "button"; - btnExit.classList.add("umbraco-preview-badge__a", "end"); - btnExit.title = "End preview mode"; - btnExit.innerHTML = this.exitIcon; - btnExit.onclick = () => this.#endPreview(); - - wrapper.appendChild(btnExit); - - const style = document.createElement("style"); - - style.textContent = this.styles; - - shadow.appendChild(style); - shadow.appendChild(wrapper); - } - - get exitIcon() { - return ` - - Click to end preview mode - - - - -`; - } - - get styles() { - return ` + const styles = ` .umbraco-preview-badge { display: inline-flex; background: rgba(27, 38, 79, 0.9); @@ -140,7 +73,70 @@ .umbraco-preview-badge__a:hover { background: #202d5e00; } - `; + `; + + const exitIcon = ` + + Click to end preview mode + + + + +`; + + class UmbWebsitePreviewElement extends HTMLElement { + connectedCallback() { + this.#render(); + } + + async #endPreview() { + await fetch(`/umbraco/management/api/v1/preview`, { + method: "DELETE" + }); + + window.location.href = this.getAttribute("url") ?? '/'; + } + + #render() { + const path = this.getAttribute("path") ?? '/umbraco'; + const unique = this.getAttribute("unique") ?? ''; + + const shadow = this.attachShadow({ mode: "open" }); + + const wrapper = document.createElement("div"); + wrapper.id = "umbracoPreviewBadge"; + wrapper.className = "umbraco-preview-badge"; + wrapper.popover = "manual"; + + const title = document.createElement("span"); + title.className = "umbraco-preview-badge__header"; + title.textContent = "Preview mode"; + + wrapper.appendChild(title); + + const btnOpen = document.createElement('a'); + btnOpen.classList.add('umbraco-preview-badge__a', 'open'); + btnOpen.title = 'Open preview in BackOffice'; + btnOpen.href = `${path}/preview/?id=${unique}`; + btnOpen.innerHTML = '…'; + + wrapper.appendChild(btnOpen); + + const btnExit = document.createElement("button"); + btnExit.type = "button"; + btnExit.classList.add("umbraco-preview-badge__a", "end"); + btnExit.title = "End preview mode"; + btnExit.innerHTML = exitIcon; + btnExit.onclick = () => this.#endPreview(); + + wrapper.appendChild(btnExit); + + const style = document.createElement("style"); + + style.textContent = styles; + + shadow.appendChild(style); + shadow.appendChild(wrapper); } }