From e6ef924b1b2ed74019380a5142212524f542aac3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Mon, 10 Sep 2018 11:34:31 +1000 Subject: [PATCH 01/18] init commit to getting preview working --- .../src/canvasdesigner.loader.js | 30 ++++---- .../canvasdesigner.controller.js | 2 +- .../components/content/edit.controller.js | 16 ++++- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 3 +- .../Umbraco/Views/Preview/Index.cshtml | 1 - .../Umbraco/dialogs/Preview.aspx | 19 ----- src/Umbraco.Web/Editors/PreviewController.cs | 29 +++++++- src/Umbraco.Web/Umbraco.Web.csproj | 8 --- .../umbraco/dialogs/Preview.aspx | 19 ----- .../umbraco/dialogs/Preview.aspx.cs | 30 -------- .../umbraco/dialogs/Preview.aspx.designer.cs | 69 ------------------- 11 files changed, 60 insertions(+), 166 deletions(-) delete mode 100644 src/Umbraco.Web.UI/Umbraco/dialogs/Preview.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx.cs delete mode 100644 src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx.designer.cs diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js index 8c0ec78025..5476404e6a 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner.loader.js @@ -1,19 +1,19 @@ LazyLoad.js([ - '../lib/jquery/jquery.min.js', - '../lib/angular/1.1.5/angular.min.js', - '../lib/underscore/underscore-min.js', - '../lib/umbraco/Extensions.js', - '../js/app.js', - '../js/umbraco.resources.js', - '../js/umbraco.services.js', - '../js/umbraco.interceptors.js', - '../ServerVariables', - '../lib/signalr/jquery.signalR.js', - '/umbraco/BackOffice/signalr/hubs', - '../js/umbraco.canvasdesigner.js' + '../lib/jquery/jquery.min.js', + '../lib/angular/angular.js', + '../lib/underscore/underscore-min.js', + '../lib/umbraco/Extensions.js', + '../js/app.js', + '../js/umbraco.resources.js', + '../js/umbraco.services.js', + '../js/umbraco.interceptors.js', + '../ServerVariables', + '../lib/signalr/jquery.signalR.js', + '../BackOffice/signalr/hubs', + '../js/umbraco.canvasdesigner.js' ], function () { - jQuery(document).ready(function () { - angular.bootstrap(document, ['Umbraco.canvasdesigner']); - }); + jQuery(document).ready(function () { + angular.bootstrap(document, ['Umbraco.canvasdesigner']); + }); }); diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js index 975d490086..25c616814f 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js @@ -20,7 +20,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['umbraco.resources', 'umbrac $scope.frameLoaded = false; var pageId = $location.search().id; $scope.pageId = pageId; - $scope.pageUrl = "../dialogs/Preview.aspx?id=" + pageId; + $scope.pageUrl = "frame?id=" + pageId; $scope.valueAreLoaded = false; $scope.devices = [ { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index eff1025e60..2b41089722 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -482,8 +482,22 @@ previewWindow.location.href = redirect; } else { - $scope.save().then(function (data) { + var selectedVariant; + if (!$scope.culture) { + selectedVariant = $scope.content.variants[0]; + } + else { + selectedVariant = _.find($scope.content.variants, function (v) { + return v.language.culture === $scope.culture; + }); + } + + //ensure the save flag is set + selectedVariant.save = true; + performSave({ saveMethod: contentResource.publish, action: "save" }).then(function (data) { previewWindow.location.href = redirect; + }, function (err) { + //validation issues .... }); } } diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index bb1be50baf..e277a40319 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -340,7 +340,6 @@ - @@ -600,4 +599,4 @@ - + \ No newline at end of file diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 0b1c3fbe17..5316b448be 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -8,7 +8,6 @@ Umbraco Canvas Designer - diff --git a/src/Umbraco.Web.UI/Umbraco/dialogs/Preview.aspx b/src/Umbraco.Web.UI/Umbraco/dialogs/Preview.aspx deleted file mode 100644 index 0469ae7a61..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/dialogs/Preview.aspx +++ /dev/null @@ -1,19 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoDialog.Master" Inherits="umbraco.presentation.dialogs.Preview" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" Assembly="Umbraco.Web" %> - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index 9c0d46cedb..d2f955c1e8 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -1,10 +1,14 @@ using System; +using System.Web; using System.Web.Mvc; using Umbraco.Core; using Umbraco.Core.Configuration; +using Umbraco.Web.Composing; using Umbraco.Web.Features; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; +using Umbraco.Web.PublishedCache; +using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors { @@ -13,11 +17,15 @@ namespace Umbraco.Web.Editors { private readonly UmbracoFeatures _features; private readonly IGlobalSettings _globalSettings; + private readonly IPublishedSnapshotService _publishedSnapshotService; + private readonly UmbracoContext _umbracoContext; - public PreviewController(UmbracoFeatures features, IGlobalSettings globalSettings) + public PreviewController(UmbracoFeatures features, IGlobalSettings globalSettings, IPublishedSnapshotService publishedSnapshotService, UmbracoContext umbracoContext) { _features = features; _globalSettings = globalSettings; + _publishedSnapshotService = publishedSnapshotService; + _umbracoContext = umbracoContext; } [UmbracoAuthorize(redirectToUmbracoLogin: true)] @@ -41,6 +49,25 @@ namespace Umbraco.Web.Editors return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml", model); } + /// + /// The endpoint that is loaded within the preview iframe + /// + /// + [UmbracoAuthorize] + public ActionResult Frame(int id) + { + var user = _umbracoContext.Security.CurrentUser; + + var previewToken = _publishedSnapshotService.EnterPreview(user, id); + + Response.Cookies.Set(new HttpCookie(Constants.Web.PreviewCookieName, previewToken)); + + // use a numeric url because content may not be in cache and so .Url would fail + Response.Redirect($"../../{id}.aspx", true); + + return null; + } + public ActionResult Editors(string editor) { if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException(nameof(editor)); diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index d01eaa0f64..23251e0bf9 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -1303,13 +1303,6 @@ - - Preview.aspx - ASPXCodeBehind - - - Preview.aspx - insertMasterpageContent.aspx ASPXCodeBehind @@ -1478,7 +1471,6 @@ - ASPXCodeBehind diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx deleted file mode 100644 index 1519e540be..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx +++ /dev/null @@ -1,19 +0,0 @@ -<%@ Page Language="C#" AutoEventWireup="true" MasterPageFile="../masterpages/umbracoDialog.Master" CodeBehind="Preview.aspx.cs" Inherits="umbraco.presentation.dialogs.Preview" %> -<%@ Register TagPrefix="cc1" Namespace="Umbraco.Web._Legacy.Controls" %> - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx.cs deleted file mode 100644 index ea66e32477..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Web; -using Umbraco.Web; -using Umbraco.Core; -using Umbraco.Web.Composing; - -namespace umbraco.presentation.dialogs -{ - public partial class Preview : Umbraco.Web.UI.Pages.UmbracoEnsuredPage - { - public Preview() - { - CurrentApp = Constants.Applications.Content; - } - - protected void Page_Load(object sender, EventArgs e) - { - var user = UmbracoContext.Security.CurrentUser; - var contentId = Request.GetItemAs("id"); - - var publishedSnapshotService = Current.PublishedSnapshotService; - var previewToken = publishedSnapshotService.EnterPreview(user, contentId); - - UmbracoContext.HttpContext.Response.Cookies.Set(new HttpCookie(Constants.Web.PreviewCookieName, previewToken)); - - // use a numeric url because content may not be in cache and so .Url would fail - Response.Redirect($"../../{contentId}.aspx", true); - } - } -} diff --git a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx.designer.cs b/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx.designer.cs deleted file mode 100644 index 7b419b30e1..0000000000 --- a/src/Umbraco.Web/umbraco.presentation/umbraco/dialogs/Preview.aspx.designer.cs +++ /dev/null @@ -1,69 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace umbraco.presentation.dialogs { - - - public partial class Preview { - - /// - /// feedback1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Feedback feedback1; - - /// - /// pane_form control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.Pane pane_form; - - /// - /// PropertyPanel1 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel PropertyPanel1; - - /// - /// docLit control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal docLit; - - /// - /// PropertyPanel2 control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::Umbraco.Web._Legacy.Controls.PropertyPanel PropertyPanel2; - - /// - /// changeSetUrl control. - /// - /// - /// Auto-generated field. - /// To modify move field declaration from designer file to code-behind file. - /// - protected global::System.Web.UI.WebControls.Literal changeSetUrl; - } -} From 003e83f883cb6cc3679b000147e8e4787c40d2f3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 11 Sep 2018 08:54:04 +1000 Subject: [PATCH 02/18] wip on preview --- src/Umbraco.Core/UriExtensions.cs | 4 +- .../canvasdesigner.controller.js | 239 ++++++++++-------- .../components/content/edit.controller.js | 4 +- .../Umbraco/Views/Preview/Index.cshtml | 4 +- src/Umbraco.Web/Editors/PreviewController.cs | 1 + 5 files changed, 144 insertions(+), 108 deletions(-) diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index d018dddfbf..b99fc2695e 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -30,6 +30,7 @@ namespace Umbraco.Core /// These are def back office: /// /Umbraco/RestServices = back office /// /Umbraco/BackOffice = back office + /// /Umbraco/Preview = back office /// If it's not any of the above, and there's no extension then we cannot determine if it's back office or front-end /// so we can only assume that it is not back office. This will occur if people use an UmbracoApiController for the backoffice /// but do not inherit from UmbracoAuthorizedApiController and do not use [IsBackOffice] attribute. @@ -77,7 +78,8 @@ namespace Umbraco.Core //check for special back office paths if (urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/BackOffice/") - || urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/RestServices/")) + || urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/RestServices/") + || urlPath.InvariantStartsWith("/" + globalSettings.GetUmbracoMvcArea() + "/Preview/")) { return true; } diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js index 25c616814f..697599b97d 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js +++ b/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js @@ -5,113 +5,146 @@ var app = angular.module("Umbraco.canvasdesigner", ['umbraco.resources', 'umbraco.services']) -.controller("Umbraco.canvasdesignerController", function ($scope, $http, $window, $timeout, $location, dialogService) { + .controller("Umbraco.canvasdesignerController", function ($scope, $http, $window, $timeout, $location, dialogService) { - var isInit = $location.search().init; - 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. We don't want to double load preview.aspx - //since that will double prepare the preview documents - return; - } + //gets a real query string value + function getParameterByName(name, url) { + if (!url) url = $window.location.href; + name = name.replace(/[\[\]]/g, '\\$&'); + var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, ' ')); + } - $scope.isOpen = false; - $scope.frameLoaded = false; - var pageId = $location.search().id; - $scope.pageId = pageId; - $scope.pageUrl = "frame?id=" + pageId; - $scope.valueAreLoaded = false; - $scope.devices = [ - { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, - { name: "laptop - 1366px", css: "laptop border", icon: "icon-laptop", title: "Laptop" }, - { name: "iPad portrait - 768px", css: "iPad-portrait border", icon: "icon-ipad", title: "Tablet portrait" }, - { name: "iPad landscape - 1024px", css: "iPad-landscape border", icon: "icon-ipad flip", title: "Tablet landscape" }, - { name: "smartphone portrait - 480px", css: "smartphone-portrait border", icon: "icon-iphone", title: "Smartphone portrait" }, - { name: "smartphone landscape - 320px", css: "smartphone-landscape border", icon: "icon-iphone flip", title: "Smartphone landscape" } - ]; - $scope.previewDevice = $scope.devices[0]; - - /*****************************************************************************/ - /* Preview devices */ - /*****************************************************************************/ - - // Set preview device - $scope.updatePreviewDevice = function (device) { - $scope.previewDevice = device; - }; - - /*****************************************************************************/ - /* Exit Preview */ - /*****************************************************************************/ - - $scope.exitPreview = function () { - window.top.location.href = "../endPreview.aspx?redir=%2f" + $scope.pageId; - }; - - - - /*****************************************************************************/ - /* Panel managment */ - /*****************************************************************************/ - - $scope.openPreviewDevice = function () { - $scope.showDevicesPreview = true; - $scope.closeIntelCanvasdesigner(); - } - - /*****************************************************************************/ - /* Call function into the front-end */ - /*****************************************************************************/ - - - var hideUmbracoPreviewBadge = function () { - var iframe = (document.getElementById("resultFrame").contentWindow || document.getElementById("resultFrame").contentDocument); - if(iframe.document.getElementById("umbracoPreviewBadge")) - iframe.document.getElementById("umbracoPreviewBadge").style.display = "none"; - }; - - /*****************************************************************************/ - /* Init */ - /*****************************************************************************/ - - - // signalr hub - var previewHub = $.connection.previewHub; - - previewHub.client.refreshed = function (message, sender) { - console.log("Notified by SignalR preview hub (" + message+ ")."); - - if ($scope.pageId != message) { - console.log("Not a notification for us (" + $scope.pageId + ")."); + var isInit = $location.search().init; + 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. return; } - var resultFrame = document.getElementById("resultFrame"); - var iframe = (resultFrame.contentWindow || resultFrame.contentDocument); - //setTimeout(function() { iframe.location.reload(); }, 1000); - iframe.location.reload(); - }; + var pageId = $location.search().id; - $.connection.hub.start() - .done(function () { console.log("Connected to SignalR preview hub (ID=" + $.connection.hub.id + ")"); }) - .fail(function () { console.log("Could not connect to SignalR preview hub."); }); -}) - - -.directive('iframeIsLoaded', function ($timeout) { - return { - restrict: 'A', - link: function (scope, element, attr) { - element.load(function () { - var iframe = (element.context.contentWindow || element.context.contentDocument); - if(iframe && iframe.document.getElementById("umbracoPreviewBadge")) - iframe.document.getElementById("umbracoPreviewBadge").style.display = "none"; - if (!document.getElementById("resultFrame").contentWindow.refreshLayout) { - scope.frameLoaded = true; - scope.$apply(); - } - }); + //there is no page id query string hash so check if its part of a 'real' query string + //and if so, reload with the query string hash + if (!pageId) { + var queryStringPageId = getParameterByName("id"); + if (queryStringPageId) { + $location.search("id", queryStringPageId); + $window.location.reload(); + return; + } } - }; -}) + + + $scope.isOpen = false; + $scope.frameLoaded = false; + + $scope.pageId = pageId; + $scope.pageUrl = "frame?id=" + pageId; + $scope.valueAreLoaded = false; + $scope.devices = [ + { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, + { name: "laptop - 1366px", css: "laptop border", icon: "icon-laptop", title: "Laptop" }, + { name: "iPad portrait - 768px", css: "iPad-portrait border", icon: "icon-ipad", title: "Tablet portrait" }, + { name: "iPad landscape - 1024px", css: "iPad-landscape border", icon: "icon-ipad flip", title: "Tablet landscape" }, + { name: "smartphone portrait - 480px", css: "smartphone-portrait border", icon: "icon-iphone", title: "Smartphone portrait" }, + { name: "smartphone landscape - 320px", css: "smartphone-landscape border", icon: "icon-iphone flip", title: "Smartphone landscape" } + ]; + $scope.previewDevice = $scope.devices[0]; + + /*****************************************************************************/ + /* Preview devices */ + /*****************************************************************************/ + + // Set preview device + $scope.updatePreviewDevice = function (device) { + $scope.previewDevice = device; + }; + + /*****************************************************************************/ + /* Exit Preview */ + /*****************************************************************************/ + + $scope.exitPreview = function () { + window.top.location.href = "../endPreview.aspx?redir=%2f" + $scope.pageId; + }; + + $scope.onFrameLoaded = function () { + $scope.frameLoaded = true; + } + + /*****************************************************************************/ + /* Panel managment */ + /*****************************************************************************/ + + $scope.openPreviewDevice = function () { + $scope.showDevicesPreview = true; + $scope.closeIntelCanvasdesigner(); + } + + }) + + + .component('previewIFrame', { + + template: "
", + controller: function ($element) { + + var vm = this; + + vm.$onInit = function () { + + ////TODO: Move this to the callback on the controller + + //// signalr hub + //var previewHub = $.connection.previewHub; + + //previewHub.client.refreshed = function (message, sender) { + // console.log("Notified by SignalR preview hub (" + message + ")."); + + // if ($scope.pageId != message) { + // console.log("Not a notification for us (" + $scope.pageId + ")."); + // return; + // } + + // var iframe = ($element.context.contentWindow || $element.context.contentDocument); + // iframe.location.reload(); + //}; + + //$.connection.hub.start() + // .done(function () { console.log("Connected to SignalR preview hub (ID=" + $.connection.hub.id + ")"); }) + // .fail(function () { console.log("Could not connect to SignalR preview hub."); }); + }; + + vm.$postLink = function () { + $element.find("#resultFrame").on("load", function () { + var iframe = $element.find("#resultFrame").get(0); + hideUmbracoPreviewBadge(iframe); + vm.onLoaded(); + scope.$apply(); + }); + }; + + function hideUmbracoPreviewBadge (iframe) { + if (iframe && iframe.document.getElementById("umbracoPreviewBadge")) { + iframe.document.getElementById("umbracoPreviewBadge").style.display = "none"; + } + }; + + }, + controllerAs: "vm", + bindings: { + src: "@", + onLoaded: "&" + } + + }) + + .config(function ($locationProvider) { + $locationProvider.html5Mode(false); //turn html5 mode off + $locationProvider.hashPrefix(''); + }); diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 2b41089722..afc3e45be7 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -471,10 +471,10 @@ // Chromes popup blocker will kick in if a window is opened // without the initial scoped request. This trick will fix that. // - var previewWindow = $window.open('preview/?init=true&id=' + content.id, 'umbpreview'); + var previewWindow = $window.open('preview/#?init=true', 'umbpreview'); // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/#?id=' + content.id; //The user cannot save if they don't have access to do that, in which case we just want to preview //and that's it otherwise they'll get an unauthorized access message diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 5316b448be..21b7d3cbb2 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -18,8 +18,8 @@ @Html.Partial(Model.PreviewExtendedHeaderView) } -
- +
+
diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index d2f955c1e8..f5e9074e7d 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -68,6 +68,7 @@ namespace Umbraco.Web.Editors return null; } + //fixme: not sure we need this anymore since there is no canvas editing - then we can remove that route too public ActionResult Editors(string editor) { if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException(nameof(editor)); From ad1f11f1dac817c916298855b7da1d80d7ad2c31 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 11 Sep 2018 14:17:55 +0200 Subject: [PATCH 03/18] only show save/publish/preview on "Content" content app --- .../components/content/edit.controller.js | 33 +++++++++++++++++-- .../umbvariantcontenteditors.directive.js | 8 +++-- .../src/less/components/editor.less | 4 ++- .../src/views/components/content/edit.html | 7 ++-- 4 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index e5ec9172c6..227cb954af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -27,10 +27,13 @@ function init(content) { + // set first app to active + content.apps[0].active = true; + if (infiniteMode) { createInfiniteModeButtons(content); } else { - createButtons(content); + createButtons(content, content.apps[0]); } editorState.set($scope.content); @@ -135,7 +138,22 @@ } - function createButtons(content) { + /** + * Create the save/publish/preview buttons for the view + * @param {any} content the content node + * @param {any} app the active content app + */ + function createButtons(content, app) { + + // only create the save/publish/preview buttons if the + // content app is "Conent" + if(app && app.alias !== "content") { + $scope.defaultButton = null; + $scope.subButtons = null; + $scope.page.showPreviewButton = false; + return; + } + $scope.page.buttonGroupState = "init"; var buttons = contentEditingHelper.configureContentEditorButtons({ create: $scope.page.isNew, @@ -147,9 +165,10 @@ unPublish: $scope.unPublish } }); - + $scope.defaultButton = buttons.defaultButton; $scope.subButtons = buttons.subButtons; + $scope.page.showPreviewButton = true; } @@ -555,6 +574,14 @@ }); }; + /** + * Call back when a content app changes + * @param {any} app + */ + $scope.appChanged = function(app) { + createButtons($scope.content, app); + }; + function moveNode(node, target) { contentResource.move({ "parentId": target.id, "id": node.id }) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js index 7d6d5dc131..9956bf07df 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js @@ -9,7 +9,8 @@ bindings: { page: "<", content: "<", //TODO: Not sure if this should be = since we are changing the 'active' property of a variant - culture: "<" + culture: "<", + onSelectApp: "&?" }, controllerAs: 'vm', controller: umbVariantContentEditorsController @@ -39,8 +40,6 @@ /** Called when the component initializes */ function onInit() { - // set first app to active - vm.content.apps[0].active = true; prevContentDateUpdated = angular.copy(vm.content.updateDate); setActiveCulture(); } @@ -311,6 +310,9 @@ if(app && app.alias) { activeAppAlias = app.alias; } + if(vm.onSelectApp) { + vm.onSelectApp({"app": app}); + } } } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 2e4f106898..ec58ee0557 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -248,15 +248,17 @@ a.umb-variant-switcher__toggle { height: @editorFooterHeight; padding: 10px 20px; background: @white; - // box-shadow: 0 -1px 3px 0 rgba(0, 0, 0, 0.16); border-top: 1px solid @gray-9; z-index: 1; bottom: 0; + display: flex; + align-items: center; } .umb-editor-footer-content { display: flex; align-items: center; + flex: 1 1 auto; } .umb-editor-footer-content__right-side { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html index f7eaedf3be..ace41d7e94 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/edit.html @@ -9,7 +9,8 @@ + culture="culture" + on-select-app="appChanged(app)"> @@ -37,14 +38,14 @@ - + Date: Tue, 11 Sep 2018 14:43:07 +0200 Subject: [PATCH 04/18] Add empty state if a node has no properties --- .../src/views/components/content/umb-tabbed-content.html | 7 +++++++ src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 1 + 2 files changed, 8 insertions(+) diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html index 408f640c01..b125457ab0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-tabbed-content.html @@ -14,5 +14,12 @@
+ + + + +
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 60337ba3b1..b407f708bc 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -222,6 +222,7 @@ No date chosen Page title This media item has no link + No content can be added for this item test Properties This document is published but is not visible because the parent '%0%' is unpublished This culture is published but is not visible because it is unpublished on parent '%0%' From e8322edc84c479cd298483810a6faedbb5bb7f7e Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 11 Sep 2018 14:47:56 +0200 Subject: [PATCH 05/18] maybe test is not needed --- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index b407f708bc..77e37a439b 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -222,7 +222,7 @@ No date chosen Page title This media item has no link - No content can be added for this item test + No content can be added for this item Properties This document is published but is not visible because the parent '%0%' is unpublished This culture is published but is not visible because it is unpublished on parent '%0%' From 62043f7cf9edd53b26c68d2251398723c30ae077 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 11 Sep 2018 15:33:26 +0200 Subject: [PATCH 06/18] only allow changing the node name if the active content app is "Content". --- .../content/umbvariantcontent.directive.js | 17 +++++++++++ .../umbeditorcontentheader.directive.js | 2 +- .../src/less/components/editor.less | 4 +++ .../content/umb-variant-content.html | 1 + .../editor/umb-editor-content-header.html | 29 +++++++++---------- 5 files changed, 37 insertions(+), 16 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js index 5ad7a9079a..54db6308cf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js @@ -28,12 +28,23 @@ var vm = this; + vm.$onInit = onInit; vm.$postLink = postLink; vm.$onDestroy = onDestroy; vm.selectVariant = selectVariant; vm.openSplitView = openSplitView; vm.selectApp = selectApp; + + function onInit() { + // disable the name field if the active content app is not "Content" + vm.nameDisabled = false; + angular.forEach(vm.editor.content.apps, function(app){ + if(app.active && app.alias !== "content") { + vm.nameDisabled = true; + } + }); + } /** Called when the component has linked all elements, this is when the form controller is available */ function postLink() { @@ -67,6 +78,12 @@ * @param {any} item */ function selectApp(item) { + // disable the name field if the active content app is not "Content" + vm.nameDisabled = false; + if(item && item.alias !== "content") { + vm.nameDisabled = true; + } + // call the callback if any is registered if(vm.onSelectApp) { vm.onSelectApp({"app": item}); } diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js index 3cdcd5b12a..0d78aab0eb 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js @@ -97,7 +97,7 @@ templateUrl: 'views/components/editor/umb-editor-content-header.html', scope: { name: "=", - nameLocked: "=", + nameDisabled: " -
{{ name }}
- From 9f65102916ef592234f6a1568adfc8ab9033d0ff Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Tue, 11 Sep 2018 15:36:24 +0200 Subject: [PATCH 07/18] use border color on node name input hover --- src/Umbraco.Web.UI.Client/src/less/components/editor.less | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/less/components/editor.less b/src/Umbraco.Web.UI.Client/src/less/components/editor.less index 47143f1539..f045b0adca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/editor.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/editor.less @@ -102,13 +102,15 @@ input.umb-editor-header__name-input { background: @white; border: 1px solid @gray-8; &:hover { - background-color: @gray-10; - border: 1px solid @gray-8; + border-color: @turquoise-d1; } } input.umb-editor-header__name-input:disabled { background-color: @gray-10; + &:hover{ + border-color: @gray-8; + } } .umb-editor-header__actions-menu { From 4cbd0d9178d846c4a7ffe980b233ab0b43c9e3bf Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 12 Sep 2018 11:47:04 +0200 Subject: [PATCH 08/18] Remove mandatory constraint on default language --- .../Migrations/Install/DatabaseDataCreator.cs | 2 +- .../Migrations/Upgrade/UmbracoPlan.cs | 1 + .../Upgrade/V_8_0_0/AddLockObjects.cs | 12 +- .../V_8_0_0/UpdateDefaultMandatoryLanguage.cs | 48 ++++++++ .../Persistence/Constants-Locks.cs | 43 +++++++ .../Persistence/Factories/LanguageFactory.cs | 18 ++- .../Implement/LanguageRepository.cs | 107 ++++++++++-------- .../Services/Implement/LocalizationService.cs | 6 + src/Umbraco.Core/Umbraco.Core.csproj | 1 + .../src/views/languages/edit.controller.js | 5 +- .../src/views/languages/edit.html | 2 +- .../Umbraco/config/lang/en_us.xml | 2 +- src/Umbraco.Web/Editors/LanguageController.cs | 64 ++++------- 13 files changed, 213 insertions(+), 98 deletions(-) create mode 100644 src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index ac74e6ee66..bfb8705480 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -232,7 +232,7 @@ namespace Umbraco.Core.Migrations.Install private void CreateLanguageData() { - _database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)" }); + _database.Insert(Constants.DatabaseSchema.Tables.Language, "id", false, new LanguageDto { Id = 1, IsoCode = "en-US", CultureName = "English (United States)", IsDefaultVariantLanguage = true }); } private void CreateContentChildTypeData() diff --git a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs index 48ac39a630..09846ed6e2 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/UmbracoPlan.cs @@ -117,6 +117,7 @@ namespace Umbraco.Core.Migrations.Upgrade Chain("{EB34B5DC-BB87-4005-985E-D983EA496C38}"); // from 7.12.0 Chain("{517CE9EA-36D7-472A-BF4B-A0D6FB1B8F89}"); // from 7.12.0 Chain("{BBD99901-1545-40E4-8A5A-D7A675C7D2F2}"); // from 7.12.0 + Chain("{2C87AA47-D1BC-4ECB-8A73-2D8D1046C27F}"); //FINAL diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs index d595c70fa0..7c0b26dd53 100644 --- a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/AddLockObjects.cs @@ -1,4 +1,5 @@ -using Umbraco.Core.Persistence.Dtos; +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 { @@ -23,11 +24,18 @@ namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 private void EnsureLockObject(int id, string name) { - var db = Database; + EnsureLockObject(Database, id, name); + } + + internal static void EnsureLockObject(IUmbracoDatabase db, int id, string name) + { + // not if it already exists var exists = db.Exists(id); if (exists) return; + // be safe: delete old umbracoNode lock objects if any db.Execute($"DELETE FROM umbracoNode WHERE id={id};"); + // then create umbracoLock object db.Execute($"INSERT umbracoLock (id, name, value) VALUES ({id}, '{name}', 1);"); } diff --git a/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs new file mode 100644 index 0000000000..dd5fe7c369 --- /dev/null +++ b/src/Umbraco.Core/Migrations/Upgrade/V_8_0_0/UpdateDefaultMandatoryLanguage.cs @@ -0,0 +1,48 @@ +using Umbraco.Core.Persistence; +using Umbraco.Core.Persistence.Dtos; + +namespace Umbraco.Core.Migrations.Upgrade.V_8_0_0 +{ + public class UpdateDefaultMandatoryLanguage : MigrationBase + { + public UpdateDefaultMandatoryLanguage(IMigrationContext context) + : base(context) + { } + + public override void Migrate() + { + // add the new languages lock object + AddLockObjects.EnsureLockObject(Database, Constants.Locks.Languages, "Languages"); + + // get all existing languages + var selectDtos = Sql() + .Select() + .From(); + + var dtos = Database.Fetch(selectDtos); + + // get the id of the language which is already the default one, if any, + // else get the lowest language id, which will become the default language + var defaultId = int.MaxValue; + foreach (var dto in dtos) + { + if (dto.IsDefaultVariantLanguage) + { + defaultId = dto.Id; + break; + } + + if (dto.Id < defaultId) defaultId = dto.Id; + } + + // update, so that language with that id is now default and mandatory + var updateDefault = Sql() + .Update(u => u + .Set(x => x.IsDefaultVariantLanguage, true) + .Set(x => x.Mandatory, true)) + .Where(x => x.Id == defaultId); + + Database.Execute(updateDefault); + } + } +} diff --git a/src/Umbraco.Core/Persistence/Constants-Locks.cs b/src/Umbraco.Core/Persistence/Constants-Locks.cs index 6f5d4bb0dc..1dcd2408e7 100644 --- a/src/Umbraco.Core/Persistence/Constants-Locks.cs +++ b/src/Umbraco.Core/Persistence/Constants-Locks.cs @@ -3,17 +3,60 @@ namespace Umbraco.Core { static partial class Constants { + /// + /// Defines lock objects. + /// public static class Locks { + /// + /// All servers. + /// public const int Servers = -331; + + /// + /// All content and media types. + /// public const int ContentTypes = -332; + + /// + /// The entire content tree, i.e. all content items. + /// public const int ContentTree = -333; + + /// + /// The entire media tree, i.e. all media items. + /// public const int MediaTree = -334; + + /// + /// The entire member tree, i.e. all members. + /// public const int MemberTree = -335; + + /// + /// All media types. + /// public const int MediaTypes = -336; + + /// + /// All member types. + /// public const int MemberTypes = -337; + + /// + /// All domains. + /// public const int Domains = -338; + + /// + /// All key-values. + /// public const int KeyValues = -339; + + /// + /// All languages. + /// + public const int Languages = -340; } } } diff --git a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs index 7b24411498..30bd9c68ed 100644 --- a/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs +++ b/src/Umbraco.Core/Persistence/Factories/LanguageFactory.cs @@ -8,7 +8,14 @@ namespace Umbraco.Core.Persistence.Factories { public static ILanguage BuildEntity(LanguageDto dto) { - var lang = new Language(dto.IsoCode) { CultureName = dto.CultureName, Id = dto.Id, IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, Mandatory = dto.Mandatory }; + var lang = new Language(dto.IsoCode) + { + CultureName = dto.CultureName, + Id = dto.Id, + IsDefaultVariantLanguage = dto.IsDefaultVariantLanguage, + Mandatory = dto.Mandatory + }; + // reset dirty initial properties (U4-1946) lang.ResetDirtyProperties(false); return lang; @@ -16,7 +23,14 @@ namespace Umbraco.Core.Persistence.Factories public static LanguageDto BuildDto(ILanguage entity) { - var dto = new LanguageDto { CultureName = entity.CultureName, IsoCode = entity.IsoCode, IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, Mandatory = entity.Mandatory }; + var dto = new LanguageDto + { + CultureName = entity.CultureName, + IsoCode = entity.IsoCode, + IsDefaultVariantLanguage = entity.IsDefaultVariantLanguage, + Mandatory = entity.Mandatory + }; + if (entity.HasIdentity) dto.Id = short.Parse(entity.Id.ToString(CultureInfo.InvariantCulture)); diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs index 740015683a..ae5d9ae8b8 100644 --- a/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs +++ b/src/Umbraco.Core/Persistence/Repositories/Implement/LanguageRepository.cs @@ -54,11 +54,6 @@ namespace Umbraco.Core.Persistence.Repositories.Implement // get languages var languages = Database.Fetch(sql).Select(ConvertFromDto).OrderBy(x => x.Id).ToList(); - // fix inconsistencies: there has to be a default language, and it has to be mandatory - var defaultLanguage = languages.FirstOrDefault(x => x.IsDefaultVariantLanguage) ?? languages.First(); - defaultLanguage.IsDefaultVariantLanguage = true; - defaultLanguage.Mandatory = true; - // initialize the code-id map lock (_codeIdMap) { @@ -131,70 +126,83 @@ namespace Umbraco.Core.Persistence.Repositories.Implement protected override void PersistNewItem(ILanguage entity) { - if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureInfo == null || entity.CultureName.IsNullOrWhiteSpace()) - throw new InvalidOperationException("The required language data is missing"); + // validate iso code and culture name + if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace()) + throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name."); - ((EntityBase)entity).AddingEntity(); + ((EntityBase) entity).AddingEntity(); + // deal with entity becoming the new default entity if (entity.IsDefaultVariantLanguage) { - //if this entity is flagged as the default, we need to set all others to false - Database.Execute(Sql().Update(u => u.Set(x => x.IsDefaultVariantLanguage, false))); - //We need to clear the whole cache since all languages will be updated - IsolatedCache.ClearAllCache(); + // set all other entities to non-default + // safe (no race cond) because the service locks languages + var setAllDefaultToFalse = Sql() + .Update(u => u.Set(x => x.IsDefaultVariantLanguage, false)); + Database.Execute(setAllDefaultToFalse); } - ; + // insert var dto = LanguageFactory.BuildDto(entity); - var id = Convert.ToInt32(Database.Insert(dto)); entity.Id = id; - entity.ResetDirtyProperties(); - } protected override void PersistUpdatedItem(ILanguage entity) { - if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureInfo == null || entity.CultureName.IsNullOrWhiteSpace()) - throw new InvalidOperationException("The required language data is missing"); + // validate iso code and culture name + if (entity.IsoCode.IsNullOrWhiteSpace() || entity.CultureName.IsNullOrWhiteSpace()) + throw new InvalidOperationException("Cannot save a language without an ISO code and a culture name."); - ((EntityBase)entity).UpdatingEntity(); + ((EntityBase) entity).UpdatingEntity(); if (entity.IsDefaultVariantLanguage) { - //if this entity is flagged as the default, we need to set all others to false - Database.Execute(Sql().Update(u => u.Set(x => x.IsDefaultVariantLanguage, false))); - //We need to clear the whole cache since all languages will be updated - IsolatedCache.ClearAllCache(); + // deal with entity becoming the new default entity + + // set all other entities to non-default + // safe (no race cond) because the service locks languages + var setAllDefaultToFalse = Sql() + .Update(u => u.Set(x => x.IsDefaultVariantLanguage, false)); + Database.Execute(setAllDefaultToFalse); } - + else + { + // deal with the entity not being default anymore + // which is illegal - another entity has to become default + var selectDefaultId = Sql() + .Select(x => x.Id) + .From() + .Where(x => x.IsDefaultVariantLanguage); + + var defaultId = Database.ExecuteScalar(selectDefaultId); + if (entity.Id == defaultId) + throw new InvalidOperationException($"Cannot save the default language ({entity.IsoCode}) as non-default. Make another language the default language instead."); + } + + // update var dto = LanguageFactory.BuildDto(entity); - Database.Update(dto); - entity.ResetDirtyProperties(); - - //Clear the cache entries that exist by key/iso - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.IsoCode)); - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.CultureName)); } protected override void PersistDeletedItem(ILanguage entity) { - //we need to validate that we can delete this language - if (entity.IsDefaultVariantLanguage) - throw new InvalidOperationException($"Cannot delete the default language ({entity.IsoCode})"); + // validate that the entity is not the default language. + // safe (no race cond) because the service locks languages - var count = Database.ExecuteScalar(Sql().SelectCount().From()); - if (count == 1) - throw new InvalidOperationException($"Cannot delete the default language ({entity.IsoCode})"); + var selectDefaultId = Sql() + .Select(x => x.Id) + .From() + .Where(x => x.IsDefaultVariantLanguage); + var defaultId = Database.ExecuteScalar(selectDefaultId); + if (entity.Id == defaultId) + throw new InvalidOperationException($"Cannot delete the default language ({entity.IsoCode})."); + + // delete base.PersistDeletedItem(entity); - - //Clear the cache entries that exist by key/iso - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.IsoCode)); - IsolatedCache.ClearCacheItem(RepositoryCacheKeys.GetKey(entity.CultureName)); } #endregion @@ -262,17 +270,20 @@ namespace Umbraco.Core.Persistence.Repositories.Implement private ILanguage GetDefault() { // get all cached, non-cloned - var all = TypedCachePolicy.GetAllCached(PerformGetAll); + var languages = TypedCachePolicy.GetAllCached(PerformGetAll).ToList(); + var language = languages.FirstOrDefault(x => x.IsDefaultVariantLanguage); + if (language != null) return language; + + // this is an anomaly, the service/repo should ensure it cannot happen + Logger.Warn("There is no default language. Fix this anomaly by editing the language table in database and setting one language as the default language."); + + // still, don't kill the site, and return "something" ILanguage first = null; - foreach (var language in all) + foreach (var l in languages) { - // if one language is default, return - if (language.IsDefaultVariantLanguage) - return language; - // keep track of language with lowest id - if (first == null || language.Id < first.Id) - first = language; + if (first == null || l.Id < first.Id) + first = l; } return first; diff --git a/src/Umbraco.Core/Services/Implement/LocalizationService.cs b/src/Umbraco.Core/Services/Implement/LocalizationService.cs index 663ecf586c..e136cb5a68 100644 --- a/src/Umbraco.Core/Services/Implement/LocalizationService.cs +++ b/src/Umbraco.Core/Services/Implement/LocalizationService.cs @@ -360,6 +360,9 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope()) { + // write-lock languages to guard against race conds when dealing with default language + scope.WriteLock(Constants.Locks.Languages); + var saveEventArgs = new SaveEventArgs(language); if (scope.Events.DispatchCancelable(SavingLanguage, this, saveEventArgs)) { @@ -386,6 +389,9 @@ namespace Umbraco.Core.Services.Implement { using (var scope = ScopeProvider.CreateScope()) { + // write-lock languages to guard against race conds when dealing with default language + scope.WriteLock(Constants.Locks.Languages); + var deleteEventArgs = new DeleteEventArgs(language); if (scope.Events.DispatchCancelable(DeletingLanguage, this, deleteEventArgs)) { diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index f584621fc0..02bad84a31 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -355,6 +355,7 @@ + diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js index afb5333ded..a46671fb56 100644 --- a/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/languages/edit.controller.js @@ -122,9 +122,7 @@ } function toggleMandatory() { - if(!vm.language.isDefault) { - vm.language.isMandatory = !vm.language.isMandatory; - } + vm.language.isMandatory = !vm.language.isMandatory; } function toggleDefault() { @@ -136,7 +134,6 @@ vm.language.isDefault = !vm.language.isDefault; if(vm.language.isDefault) { - vm.language.isMandatory = true; vm.showDefaultLanguageInfo = true; } else { vm.showDefaultLanguageInfo = false; diff --git a/src/Umbraco.Web.UI.Client/src/views/languages/edit.html b/src/Umbraco.Web.UI.Client/src/views/languages/edit.html index 6aaf915960..265fe0fd2b 100644 --- a/src/Umbraco.Web.UI.Client/src/views/languages/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/languages/edit.html @@ -47,7 +47,7 @@ -
+
diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 60337ba3b1..fea307abb4 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -1708,7 +1708,7 @@ To manage your website, simply open the Umbraco back office and start adding con Mandatory Properties on this language has to be filled out before the node can be published. Default language - An Umbraco site can only have one default langugae set. + An Umbraco site can only have one default language set. Switching default language may result in default content missing. diff --git a/src/Umbraco.Web/Editors/LanguageController.cs b/src/Umbraco.Web/Editors/LanguageController.cs index 96019da702..8df19027a4 100644 --- a/src/Umbraco.Web/Editors/LanguageController.cs +++ b/src/Umbraco.Web/Editors/LanguageController.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Net; @@ -8,8 +7,6 @@ using System.Web.Http; using AutoMapper; using Umbraco.Core; using Umbraco.Core.Models; -using Umbraco.Core.Persistence; -using Umbraco.Web.Models; using Umbraco.Web.Mvc; using Umbraco.Web.WebApi; using Umbraco.Web.WebApi.Filters; @@ -56,27 +53,7 @@ namespace Umbraco.Web.Editors if (lang == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound)); - var model = Mapper.Map(lang); - - //if there's only one language, by default it is the default - var allLangs = Services.LocalizationService.GetAllLanguages().OrderBy(x => x.Id).ToList(); - if (!lang.IsDefaultVariantLanguage) - { - if (allLangs.Count == 1) - { - model.IsDefaultVariantLanguage = true; - model.Mandatory = true; - } - else if (allLangs.All(x => !x.IsDefaultVariantLanguage)) - { - //if no language has the default flag, then the defaul language is the one with the lowest id - model.IsDefaultVariantLanguage = allLangs[0].Id == lang.Id; - model.Mandatory = allLangs[0].Id == lang.Id; - } - } - - - return model; + return Mapper.Map(lang); } /// @@ -90,11 +67,10 @@ namespace Umbraco.Web.Editors var language = Services.LocalizationService.GetLanguageById(id); if (language == null) return NotFound(); - var totalLangs = Services.LocalizationService.GetAllLanguages().Count(); - - if (language.IsDefaultVariantLanguage || totalLangs == 1) + // the service would not let us do it, but test here nevertheless + if (language.IsDefaultVariantLanguage) { - var message = $"Language '{language.IsoCode}' is currently set to 'default' or it is the only installed language and can not be deleted."; + var message = $"Language '{language.IsoCode}' is currently set to 'default' and can not be deleted."; throw new HttpResponseException(Request.CreateNotificationValidationErrorResponse(message)); } @@ -113,16 +89,17 @@ namespace Umbraco.Web.Editors if (!ModelState.IsValid) throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); - var found = Services.LocalizationService.GetLanguageByIsoCode(language.IsoCode); + // this is prone to race conds but the service will not let us proceed anyways + var existing = Services.LocalizationService.GetLanguageByIsoCode(language.IsoCode); - if (found != null && language.Id != found.Id) + if (existing != null && language.Id != existing.Id) { - //someone is trying to create a language that alraedy exist + //someone is trying to create a language that already exist ModelState.AddModelError("IsoCode", "The language " + language.IsoCode + " already exists"); throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); } - if (found == null) + if (existing == null) { CultureInfo culture; try @@ -146,11 +123,20 @@ namespace Umbraco.Web.Editors return Mapper.Map(newLang); } - found.Mandatory = language.Mandatory; - found.IsDefaultVariantLanguage = language.IsDefaultVariantLanguage; - Services.LocalizationService.Save(found); - return Mapper.Map(found); - } - + existing.Mandatory = language.Mandatory; + + // note that the service will prevent the default language from being "un-defaulted" + // but does not hurt to test here - though the UI should prevent it too + if (existing.IsDefaultVariantLanguage && !language.IsDefaultVariantLanguage) + { + ModelState.AddModelError("IsDefaultVariantLanguage", "Cannot un-default the default language."); + throw new HttpResponseException(Request.CreateValidationErrorResponse(ModelState)); + } + + existing.IsDefaultVariantLanguage = language.IsDefaultVariantLanguage; + + Services.LocalizationService.Save(existing); + return Mapper.Map(existing); + } } } From e771e781ddfe659411055b9ae4b023992c898ed8 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 14 Sep 2018 00:14:03 +1000 Subject: [PATCH 09/18] finally it actually previews --- .../JsInitializationTests.cs | 4 +- src/Umbraco.Web.UI.Client/gulpfile.js | 2 +- .../components/content/edit.controller.js | 4 +- .../preview.controller.js} | 84 ++++++++++--------- src/Umbraco.Web.UI/Umbraco.Web.UI.csproj | 11 --- .../Umbraco/Views/Preview/Background.cshtml | 57 ------------- .../Umbraco/Views/Preview/Border.cshtml | 20 ----- .../Umbraco/Views/Preview/Color.cshtml | 4 - .../Views/Preview/Googlefontpicker.cshtml | 34 -------- .../Umbraco/Views/Preview/GridRow.cshtml | 8 -- .../Umbraco/Views/Preview/Index.cshtml | 42 +++++++--- .../Umbraco/Views/Preview/Layout.cshtml | 10 --- .../Umbraco/Views/Preview/Margin.cshtml | 14 ---- .../Umbraco/Views/Preview/Padding.cshtml | 14 ---- .../Umbraco/Views/Preview/Radius.cshtml | 21 ----- .../Umbraco/Views/Preview/Shadow.cshtml | 8 -- .../Umbraco/Views/Preview/Slider.cshtml | 8 -- .../Editors/BackOfficeController.cs | 7 +- src/Umbraco.Web/Editors/BackOfficeModel.cs | 1 + .../Editors/BackOfficePreviewModel.cs | 15 ++++ src/Umbraco.Web/Editors/PreviewController.cs | 35 +++++--- .../ContentEditing/BackOfficePreview.cs | 13 --- .../Mvc/DisableBrowserCacheAttribute.cs | 11 ++- .../Mvc/DisableClientCacheAttribute.cs | 26 ------ .../UI/JavaScript/AssetInitialization.cs | 6 +- .../UI/JavaScript/JsInitialization.cs | 64 ++++++++++++-- src/Umbraco.Web/UI/JavaScript/Main.js | 8 +- .../UI/JavaScript/PreviewInitialize.js | 14 ++++ .../UI/JavaScript/Resources.Designer.cs | 50 ++++++++--- src/Umbraco.Web/UI/JavaScript/Resources.resx | 3 + src/Umbraco.Web/Umbraco.Web.csproj | 4 +- 31 files changed, 250 insertions(+), 352 deletions(-) rename src/Umbraco.Web.UI.Client/src/{canvasdesigner/canvasdesigner.controller.js => preview/preview.controller.js} (66%) delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Background.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Border.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Color.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Googlefontpicker.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/GridRow.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Layout.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Margin.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Padding.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Radius.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Shadow.cshtml delete mode 100644 src/Umbraco.Web.UI/Umbraco/Views/Preview/Slider.cshtml create mode 100644 src/Umbraco.Web/Editors/BackOfficePreviewModel.cs delete mode 100644 src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs delete mode 100644 src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs create mode 100644 src/Umbraco.Web/UI/JavaScript/PreviewInitialize.js diff --git a/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs b/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs index dacba39163..bcb102726a 100644 --- a/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs +++ b/src/Umbraco.Tests/Web/AngularIntegration/JsInitializationTests.cs @@ -19,7 +19,7 @@ namespace Umbraco.Tests.Web.AngularIntegration [Test] public void Parse_Main() { - var result = JsInitialization.WriteScript(new[] {"[World]", "Hello" }); + var result = JsInitialization.WriteScript("[World]", "Hello", "Blah"); Assert.AreEqual(@"LazyLoad.js([World], function () { //we need to set the legacy UmbClientMgr path @@ -27,7 +27,7 @@ namespace Umbraco.Tests.Web.AngularIntegration jQuery(document).ready(function () { - angular.bootstrap(document, ['umbraco']); + angular.bootstrap(document, ['Blah']); }); });".StripWhitespace(), result.StripWhitespace()); diff --git a/src/Umbraco.Web.UI.Client/gulpfile.js b/src/Umbraco.Web.UI.Client/gulpfile.js index 5afe6ecf44..2d34e8ecb3 100644 --- a/src/Umbraco.Web.UI.Client/gulpfile.js +++ b/src/Umbraco.Web.UI.Client/gulpfile.js @@ -80,7 +80,7 @@ var sources = { //js files for backoffie //processed in the js task js: { - preview: { files: ["src/canvasdesigner/**/*.js"], out: "umbraco.canvasdesigner.js" }, + preview: { files: ["src/preview/**/*.js"], out: "umbraco.preview.js" }, installer: { files: ["src/installer/**/*.js"], out: "umbraco.installer.js" }, controllers: { files: ["src/{views,controllers}/**/*.controller.js"], out: "umbraco.controllers.js" }, directives: { files: ["src/common/directives/**/*.js"], out: "umbraco.directives.js" }, diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index afc3e45be7..2c32f4a9ca 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -471,10 +471,10 @@ // Chromes popup blocker will kick in if a window is opened // without the initial scoped request. This trick will fix that. // - var previewWindow = $window.open('preview/#?init=true', 'umbpreview'); + var previewWindow = $window.open('preview/?init=true', 'umbpreview'); // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/#?id=' + content.id; + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; //The user cannot save if they don't have access to do that, in which case we just want to preview //and that's it otherwise they'll get an unauthorized access message diff --git a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js similarity index 66% rename from src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js rename to src/Umbraco.Web.UI.Client/src/preview/preview.controller.js index 697599b97d..b3525613ef 100644 --- a/src/Umbraco.Web.UI.Client/src/canvasdesigner/canvasdesigner.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -3,10 +3,10 @@ /* Canvasdesigner panel app and controller */ /*********************************************************************************************************/ -var app = angular.module("Umbraco.canvasdesigner", ['umbraco.resources', 'umbraco.services']) - - .controller("Umbraco.canvasdesignerController", function ($scope, $http, $window, $timeout, $location, dialogService) { +var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.services']) + .controller("previewController", function ($scope, $http, $window, $timeout, $location, dialogService) { + //gets a real query string value function getParameterByName(name, url) { if (!url) url = $window.location.href; @@ -18,7 +18,28 @@ var app = angular.module("Umbraco.canvasdesigner", ['umbraco.resources', 'umbrac return decodeURIComponent(results[2].replace(/\+/g, ' ')); } - var isInit = $location.search().init; + function configureSignalR(iframe) { + // signalr hub + var previewHub = $.connection.previewHub; + + previewHub.client.refreshed = function (message, sender) { + console.log("Notified by SignalR preview hub (" + message + ")."); + + if ($scope.pageId != message) { + console.log("Not a notification for us (" + $scope.pageId + ")."); + return; + } + + var iframeDoc = (iframe.contentWindow || iframe.contentDocument); + iframeDoc.location.reload(); + }; + + $.connection.hub.start() + .done(function () { console.log("Connected to SignalR preview hub (ID=" + $.connection.hub.id + ")"); }) + .fail(function () { console.log("Could not connect to SignalR preview hub."); }); + } + + var isInit = getParameterByName("init"); 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 @@ -45,6 +66,7 @@ var app = angular.module("Umbraco.canvasdesigner", ['umbraco.resources', 'umbrac $scope.pageId = pageId; $scope.pageUrl = "frame?id=" + pageId; + $scope.valueAreLoaded = false; $scope.devices = [ { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, @@ -73,8 +95,9 @@ var app = angular.module("Umbraco.canvasdesigner", ['umbraco.resources', 'umbrac window.top.location.href = "../endPreview.aspx?redir=%2f" + $scope.pageId; }; - $scope.onFrameLoaded = function () { + $scope.onFrameLoaded = function (iframe) { $scope.frameLoaded = true; + configureSignalR(iframe); } /*****************************************************************************/ @@ -91,54 +114,35 @@ var app = angular.module("Umbraco.canvasdesigner", ['umbraco.resources', 'umbrac .component('previewIFrame', { - template: "
", - controller: function ($element) { + template: "
", + controller: function ($element, $scope, angularHelper) { var vm = this; - vm.$onInit = function () { - - ////TODO: Move this to the callback on the controller - - //// signalr hub - //var previewHub = $.connection.previewHub; - - //previewHub.client.refreshed = function (message, sender) { - // console.log("Notified by SignalR preview hub (" + message + ")."); - - // if ($scope.pageId != message) { - // console.log("Not a notification for us (" + $scope.pageId + ")."); - // return; - // } - - // var iframe = ($element.context.contentWindow || $element.context.contentDocument); - // iframe.location.reload(); - //}; - - //$.connection.hub.start() - // .done(function () { console.log("Connected to SignalR preview hub (ID=" + $.connection.hub.id + ")"); }) - // .fail(function () { console.log("Could not connect to SignalR preview hub."); }); - }; - vm.$postLink = function () { - $element.find("#resultFrame").on("load", function () { - var iframe = $element.find("#resultFrame").get(0); - hideUmbracoPreviewBadge(iframe); - vm.onLoaded(); - scope.$apply(); - }); + var resultFrame = $element.find("#resultFrame"); + resultFrame.on("load", iframeReady); + vm.srcDelayed = vm.src; }; + function iframeReady() { + var iframe = $element.find("#resultFrame").get(0); + hideUmbracoPreviewBadge(iframe); + angularHelper.safeApply($scope, function () { + vm.onLoaded({ iframe: iframe }); + }); + } + function hideUmbracoPreviewBadge (iframe) { - if (iframe && iframe.document.getElementById("umbracoPreviewBadge")) { - iframe.document.getElementById("umbracoPreviewBadge").style.display = "none"; + if (iframe && iframe.contentDocument && iframe.contentDocument.getElementById("umbracoPreviewBadge")) { + iframe.contentDocument.getElementById("umbracoPreviewBadge").style.display = "none"; } }; }, controllerAs: "vm", bindings: { - src: "@", + src: "<", onLoaded: "&" } diff --git a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj index e277a40319..4df862dcb3 100644 --- a/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj +++ b/src/Umbraco.Web.UI/Umbraco.Web.UI.csproj @@ -426,18 +426,7 @@ - - - - - - - - - - - diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Background.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Background.cshtml deleted file mode 100644 index 6e3793d840..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Background.cshtml +++ /dev/null @@ -1,57 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
-
-
- -
-
- - -
-
- -
- - diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Border.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Border.cshtml deleted file mode 100644 index 40d46a0f4d..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Border.cshtml +++ /dev/null @@ -1,20 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
-
    -
  • -
-
- -
-
- - -
- -
-
-
- -
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Color.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Color.cshtml deleted file mode 100644 index 02213f2cd2..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Color.cshtml +++ /dev/null @@ -1,4 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
-
-
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Googlefontpicker.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Googlefontpicker.cshtml deleted file mode 100644 index e1e61e379e..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Googlefontpicker.cshtml +++ /dev/null @@ -1,34 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
-
- Aa - {{ item.values.fontFamily }} - -
-
- -
- - diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/GridRow.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/GridRow.cshtml deleted file mode 100644 index 52f24cb25e..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/GridRow.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
- -
- -
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml index 21b7d3cbb2..4924985689 100644 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml +++ b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Index.cshtml @@ -1,16 +1,30 @@ -@using System.Web.Mvc.Html -@inherits System.Web.Mvc.WebViewPage +@using Umbraco.Core +@using ClientDependency.Core +@using ClientDependency.Core.Mvc +@using Umbraco.Core.IO +@using Umbraco.Web +@using Umbraco.Core.Configuration + +@inherits System.Web.Mvc.WebViewPage @{ var disableDevicePreview = Model.DisableDevicePreview.ToString().ToLowerInvariant(); + + Html + .RequiresCss("assets/css/canvasdesigner.css", "Umbraco"); } - Umbraco Canvas Designer - + + Umbraco Preview + + + @Html.RenderCssHere( + new BasicPath("Umbraco", IOHelper.ResolveUrl(SystemDirectories.Umbraco))) + - +
@if (string.IsNullOrWhiteSpace(Model.PreviewExtendedHeaderView) == false) @@ -18,8 +32,8 @@ @Html.Partial(Model.PreviewExtendedHeaderView) } -
+ - + + diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Layout.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Layout.cshtml deleted file mode 100644 index 3df51ae23f..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Layout.cshtml +++ /dev/null @@ -1,10 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
- Box - Wide - Full -
- -
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Margin.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Margin.cshtml deleted file mode 100644 index f14e592420..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Margin.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
-
    -
  • -
-
- -
-
-
- -
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Padding.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Padding.cshtml deleted file mode 100644 index 3f2c440945..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Padding.cshtml +++ /dev/null @@ -1,14 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
-
    -
  • -
-
- -
-
-
- -
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Radius.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Radius.cshtml deleted file mode 100644 index 1e8a96b712..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Radius.cshtml +++ /dev/null @@ -1,21 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
-
    - -
  • - - - - -
  • - -
-
- -
-
-
- -
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Shadow.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Shadow.cshtml deleted file mode 100644 index 6b9d4763e8..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Shadow.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
-
-
- -
diff --git a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Slider.cshtml b/src/Umbraco.Web.UI/Umbraco/Views/Preview/Slider.cshtml deleted file mode 100644 index 414159d714..0000000000 --- a/src/Umbraco.Web.UI/Umbraco/Views/Preview/Slider.cshtml +++ /dev/null @@ -1,8 +0,0 @@ -@inherits System.Web.Mvc.WebViewPage -
- -
-
-
- -
diff --git a/src/Umbraco.Web/Editors/BackOfficeController.cs b/src/Umbraco.Web/Editors/BackOfficeController.cs index d201919cef..ab89390bf6 100644 --- a/src/Umbraco.Web/Editors/BackOfficeController.cs +++ b/src/Umbraco.Web/Editors/BackOfficeController.cs @@ -44,7 +44,7 @@ namespace Umbraco.Web.Editors /// Represents a controller user to render out the default back office view and JS results. ///
[UmbracoRequireHttps] - [DisableClientCache] + [DisableBrowserCache] public class BackOfficeController : UmbracoController { private readonly ManifestParser _manifestParser; @@ -192,7 +192,8 @@ namespace Umbraco.Web.Editors //get the legacy ActionJs file references to append as well var legacyActionJsRef = GetLegacyActionJs(LegacyJsActionType.JsUrl); - var result = initJs.GetJavascriptInitialization(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef); + var files = initJs.OptimizeBackOfficeScriptFiles(HttpContext, JsInitialization.GetDefaultInitialization(), legacyActionJsRef); + var result = JsInitialization.GetJavascriptInitialization(HttpContext, files, "umbraco"); result += initCss.GetStylesheetInitialization(HttpContext); return JavaScript(result); @@ -211,7 +212,7 @@ namespace Umbraco.Web.Editors var initJs = new JsInitialization(_manifestParser); var initCss = new CssInitialization(_manifestParser); var assets = new List(); - assets.AddRange(initJs.GetScriptFiles(HttpContext, Enumerable.Empty())); + assets.AddRange(initJs.OptimizeBackOfficeScriptFiles(HttpContext, Enumerable.Empty())); assets.AddRange(initCss.GetStylesheetFiles(HttpContext)); return new JArray(assets); } diff --git a/src/Umbraco.Web/Editors/BackOfficeModel.cs b/src/Umbraco.Web/Editors/BackOfficeModel.cs index 75a388ee80..9833121301 100644 --- a/src/Umbraco.Web/Editors/BackOfficeModel.cs +++ b/src/Umbraco.Web/Editors/BackOfficeModel.cs @@ -3,6 +3,7 @@ using Umbraco.Web.Features; namespace Umbraco.Web.Editors { + public class BackOfficeModel { public BackOfficeModel(UmbracoFeatures features, IGlobalSettings globalSettings) diff --git a/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs b/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs new file mode 100644 index 0000000000..1298575e50 --- /dev/null +++ b/src/Umbraco.Web/Editors/BackOfficePreviewModel.cs @@ -0,0 +1,15 @@ +using Umbraco.Core.Configuration; +using Umbraco.Web.Features; + +namespace Umbraco.Web.Editors +{ + public class BackOfficePreviewModel : BackOfficeModel + { + public BackOfficePreviewModel(UmbracoFeatures features, IGlobalSettings globalSettings) : base(features, globalSettings) + { + } + + public bool DisableDevicePreview => Features.Disabled.DisableDevicePreview; + public string PreviewExtendedHeaderView => Features.Enabled.PreviewExtendedView; + } +} diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index f5e9074e7d..94979dfd63 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -1,6 +1,7 @@ using System; using System.Web; using System.Web.Mvc; +using System.Web.UI; using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Web.Composing; @@ -8,6 +9,7 @@ using Umbraco.Web.Features; using Umbraco.Web.Models.ContentEditing; using Umbraco.Web.Mvc; using Umbraco.Web.PublishedCache; +using Umbraco.Web.UI.JavaScript; using Constants = Umbraco.Core.Constants; namespace Umbraco.Web.Editors @@ -29,13 +31,10 @@ namespace Umbraco.Web.Editors } [UmbracoAuthorize(redirectToUmbracoLogin: true)] + [DisableBrowserCache] public ActionResult Index() { - var model = new BackOfficePreview - { - DisableDevicePreview = _features.Disabled.DisableDevicePreview, - PreviewExtendedHeaderView = _features.Enabled.PreviewExtendedView - }; + var model = new BackOfficePreviewModel(_features, _globalSettings); if (model.PreviewExtendedHeaderView.IsNullOrWhiteSpace() == false) { @@ -49,6 +48,20 @@ namespace Umbraco.Web.Editors return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + "Index.cshtml", model); } + /// + /// Returns the JavaScript file for preview + /// + /// + [MinifyJavaScriptResult(Order = 0)] + [OutputCache(Order = 1, VaryByParam = "none", Location = OutputCacheLocation.Server, Duration = 5000)] + public JavaScriptResult Application() + { + var files = JsInitialization.OptimizeScriptFiles(HttpContext, JsInitialization.GetPreviewInitialization()); + var result = JsInitialization.GetJavascriptInitialization(HttpContext, files, "umbraco.preview"); + + return JavaScript(result); + } + /// /// The endpoint that is loaded within the preview iframe /// @@ -68,11 +81,11 @@ namespace Umbraco.Web.Editors return null; } - //fixme: not sure we need this anymore since there is no canvas editing - then we can remove that route too - public ActionResult Editors(string editor) - { - if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException(nameof(editor)); - return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + editor.Replace(".html", string.Empty) + ".cshtml"); - } + ////fixme: not sure we need this anymore since there is no canvas editing - then we can remove that route too + //public ActionResult Editors(string editor) + //{ + // if (string.IsNullOrEmpty(editor)) throw new ArgumentNullException(nameof(editor)); + // return View(_globalSettings.Path.EnsureEndsWith('/') + "Views/Preview/" + editor.Replace(".html", string.Empty) + ".cshtml"); + //} } } diff --git a/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs b/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs deleted file mode 100644 index f8633acdd0..0000000000 --- a/src/Umbraco.Web/Models/ContentEditing/BackOfficePreview.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Umbraco.Web.Models.ContentEditing -{ - /// - /// The model representing Previewing of a content item from the back office - /// - public class BackOfficePreview - { - public string PreviewExtendedHeaderView { get; set; } - - //TODO: We could potentially have a 'footer' view - public bool DisableDevicePreview { get; set; } - } -} diff --git a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs index bcf7b34b4f..83682c8573 100644 --- a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs @@ -9,9 +9,9 @@ namespace Umbraco.Web.Mvc /// public class DisableBrowserCacheAttribute : ActionFilterAttribute { - public override void OnActionExecuted(ActionExecutedContext filterContext) + public override void OnResultExecuting(ResultExecutingContext filterContext) { - base.OnActionExecuted(filterContext); + base.OnResultExecuting(filterContext); // could happens if exception (but afaik this wouldn't happen in MVC) if (filterContext.HttpContext == null || filterContext.HttpContext.Response == null || @@ -20,6 +20,13 @@ namespace Umbraco.Web.Mvc return; } + if (filterContext.IsChildAction) + { + return; + } + + filterContext.HttpContext.Response.Cache.SetLastModified(DateTime.Now); + filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false); filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); filterContext.HttpContext.Response.Cache.SetMaxAge(TimeSpan.Zero); filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches); diff --git a/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs b/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs deleted file mode 100644 index a874d93d7a..0000000000 --- a/src/Umbraco.Web/Mvc/DisableClientCacheAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Web; -using System.Web.Mvc; - -namespace Umbraco.Web.Mvc -{ - /// - /// Will ensure that client-side cache does not occur by sending the correct response headers - /// - public class DisableClientCacheAttribute : ActionFilterAttribute - { - public override void OnResultExecuting(ResultExecutingContext filterContext) - { - if (filterContext.IsChildAction) base.OnResultExecuting(filterContext); - - filterContext.HttpContext.Response.Cache.SetExpires(DateTime.Now.AddDays(-10)); - filterContext.HttpContext.Response.Cache.SetLastModified(DateTime.Now); - filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false); - filterContext.HttpContext.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches); - filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); - filterContext.HttpContext.Response.Cache.SetNoStore(); - - base.OnResultExecuting(filterContext); - } - } -} diff --git a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs index 9c5e0f1ec2..cd7270ac62 100644 --- a/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/AssetInitialization.cs @@ -28,7 +28,7 @@ namespace Umbraco.Web.UI.JavaScript return toParse.Split(new[] { DependencyPathRenderer.Delimiter }, StringSplitOptions.RemoveEmptyEntries); } - protected IEnumerable OptimizeAssetCollection(IEnumerable assets, ClientDependencyType assetType, HttpContextBase httpContext) + internal static IEnumerable OptimizeAssetCollection(IEnumerable assets, ClientDependencyType assetType, HttpContextBase httpContext) { if (httpContext == null) throw new ArgumentNullException(nameof(httpContext)); @@ -41,11 +41,11 @@ namespace Umbraco.Web.UI.JavaScript // ike lib/blah/blah.js so we need to turn them into absolutes here if (x.StartsWith("/") == false && Uri.IsWellFormedUriString(x, UriKind.Relative)) { - return (IClientDependencyFile) new BasicFile(assetType) { FilePath = new Uri(requestUrl, x).AbsolutePath }; + return new BasicFile(assetType) { FilePath = new Uri(requestUrl, x).AbsolutePath }; } return assetType == ClientDependencyType.Javascript - ? (IClientDependencyFile) new JavascriptFile(x) + ? new JavascriptFile(x) : (IClientDependencyFile) new CssFile(x); }).ToList(); diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs index 4b3ef62b58..0471b47e8e 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialization.cs @@ -33,16 +33,20 @@ namespace Umbraco.Web.UI.JavaScript private static readonly Regex Token = new Regex("(\"##\\w+?##\")", RegexOptions.Compiled); /// - /// Processes all found manifest files and outputs the main.js file containing all plugin manifests + /// Gets the JS initialization script to boot the back office application /// - public string GetJavascriptInitialization(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) + /// + /// + /// + /// The angular module name to boot + /// + /// + public static string GetJavascriptInitialization(HttpContextBase httpContext, IEnumerable scripts, string angularModule) { - var files = GetScriptFiles(httpContext, umbracoInit, additionalJsFiles); - var jarray = new StringBuilder(); jarray.AppendLine("["); var first = true; - foreach (var file in files) + foreach (var file in scripts) { if (first) first = false; else jarray.AppendLine(","); @@ -53,10 +57,22 @@ namespace Umbraco.Web.UI.JavaScript } jarray.Append("]"); - return WriteScript(jarray.ToString(), IOHelper.ResolveUrl(SystemDirectories.Umbraco)); + return WriteScript(jarray.ToString(), IOHelper.ResolveUrl(SystemDirectories.Umbraco), angularModule); } - public IEnumerable GetScriptFiles(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) + /// + /// Returns a list of optimized script paths for the back office + /// + /// + /// + /// + /// + /// Cache busted/optimized script paths for the back office including manifest and property editor scripts + /// + /// + /// Used to cache bust and optimize script paths for the back office + /// + public IEnumerable OptimizeBackOfficeScriptFiles(HttpContextBase httpContext, IEnumerable umbracoInit, IEnumerable additionalJsFiles = null) { var scripts = new HashSet(); foreach (var script in umbracoInit) @@ -75,6 +91,26 @@ namespace Umbraco.Web.UI.JavaScript return scripts.ToArray(); } + /// + /// Returns a list of optimized script paths + /// + /// + /// + /// + /// + /// Used to cache bust and optimize script paths + /// + public static IEnumerable OptimizeScriptFiles(HttpContextBase httpContext, IEnumerable scriptFiles) + { + var scripts = new HashSet(); + foreach (var script in scriptFiles) + scripts.Add(script); + + scripts = new HashSet(OptimizeAssetCollection(scripts, ClientDependencyType.Javascript, httpContext)); + + return scripts.ToArray(); + } + /// /// Returns the default config as a JArray /// @@ -85,15 +121,25 @@ namespace Umbraco.Web.UI.JavaScript return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString()); } + /// + /// Returns the default config as a JArray + /// + /// + internal static IEnumerable GetPreviewInitialization() + { + var resources = JsonConvert.DeserializeObject(Resources.PreviewInitialize); + return resources.Where(x => x.Type == JTokenType.String).Select(x => x.ToString()); + } + /// /// Parses the JsResources.Main and replaces the replacement tokens accordingly. /// /// /// - internal static string WriteScript(params string[] replacements) + internal static string WriteScript(string scripts, string umbracoPath, string angularModule) { var count = 0; - + var replacements = new[] { scripts, umbracoPath, angularModule }; // replace, catering for the special syntax when we have // js function() objects contained in the json diff --git a/src/Umbraco.Web/UI/JavaScript/Main.js b/src/Umbraco.Web/UI/JavaScript/Main.js index 2998a98b6a..8aa431376a 100644 --- a/src/Umbraco.Web/UI/JavaScript/Main.js +++ b/src/Umbraco.Web/UI/JavaScript/Main.js @@ -1,10 +1,12 @@ LazyLoad.js("##JsInitialize##", function () { //we need to set the legacy UmbClientMgr path - UmbClientMgr.setUmbracoPath('"##UmbracoPath##"'); + if ((typeof UmbClientMgr) !== "undefined") { + UmbClientMgr.setUmbracoPath('"##UmbracoPath##"'); + } jQuery(document).ready(function () { - angular.bootstrap(document, ['umbraco']); + angular.bootstrap(document, ['"##AngularModule##"']); }); -}); \ No newline at end of file +}); diff --git a/src/Umbraco.Web/UI/JavaScript/PreviewInitialize.js b/src/Umbraco.Web/UI/JavaScript/PreviewInitialize.js new file mode 100644 index 0000000000..0b0c24f378 --- /dev/null +++ b/src/Umbraco.Web/UI/JavaScript/PreviewInitialize.js @@ -0,0 +1,14 @@ +[ + '../lib/jquery/jquery.min.js', + '../lib/angular/angular.js', + '../lib/underscore/underscore-min.js', + '../lib/umbraco/Extensions.js', + '../js/app.js', + '../js/umbraco.resources.js', + '../js/umbraco.services.js', + '../js/umbraco.interceptors.js', + '../ServerVariables', + '../lib/signalr/jquery.signalR.js', + '../BackOffice/signalr/hubs', + '../js/umbraco.preview.js' +] diff --git a/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs b/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs index 4ac359c360..2f320ef839 100644 --- a/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs +++ b/src/Umbraco.Web/UI/JavaScript/Resources.Designer.cs @@ -63,22 +63,22 @@ namespace Umbraco.Web.UI.JavaScript { /// /// Looks up a localized string similar to [ /// 'lib/jquery/jquery.min.js', - /// 'lib/angular/1.1.5/angular.min.js', + /// 'lib/jquery-ui/jquery-ui.min.js', + /// 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', + /// + /// 'lib/angular/angular.js', /// 'lib/underscore/underscore-min.js', /// /// 'lib/moment/moment.min.js', /// - /// 'lib/jquery-ui/jquery-ui.min.js', - /// 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', + /// 'lib/animejs/anime.min.js', /// - /// 'lib/angular/1.1.5/angular-cookies.min.js', - /// 'lib/angular/1.1.5/angular-mobile.js', - /// 'lib/angular/1.1.5/angular-sanitize.min.js', - /// - /// 'lib/angular/angular-ui-sortable.js', - /// - /// 'lib/angular-dynamic-locale/tmhDynamicLocale.min.js', - /// 'lib [rest of string was truncated]";. + /// 'lib/angular-route/angular-route.js', + /// 'lib/angular-cookies/angular-cookies.js', + /// 'lib/angular-touch/angular-touch.js', + /// 'lib/angular-sanitize/angular-sanitize.js', + /// 'lib/angular-animate/angular-animate.js', + /// [rest of string was truncated]";. /// internal static string JsInitialize { get { @@ -93,10 +93,11 @@ namespace Umbraco.Web.UI.JavaScript { /// /// jQuery(document).ready(function () { /// - /// angular.bootstrap(document, ['umbraco']); + /// angular.bootstrap(document, ['##AngularModule##']); /// /// }); - ///});. + ///}); + ///. /// internal static string Main { get { @@ -104,6 +105,29 @@ namespace Umbraco.Web.UI.JavaScript { } } + /// + /// Looks up a localized string similar to [ + /// '../lib/jquery/jquery.min.js', + /// '../lib/angular/angular.js', + /// '../lib/underscore/underscore-min.js', + /// '../lib/umbraco/Extensions.js', + /// '../js/app.js', + /// '../js/umbraco.resources.js', + /// '../js/umbraco.services.js', + /// '../js/umbraco.interceptors.js', + /// '../ServerVariables', + /// '../lib/signalr/jquery.signalR.js', + /// '../BackOffice/signalr/hubs', + /// '../js/umbraco.canvasdesigner.js' + ///] + ///. + /// + internal static string PreviewInitialize { + get { + return ResourceManager.GetString("PreviewInitialize", resourceCulture); + } + } + /// /// Looks up a localized string similar to //TODO: This would be nicer as an angular module so it can be injected into stuff... that'd be heaps nicer, but ///// how to do that when this is not a regular JS file, it is a server side JS file and RequireJS seems to only want diff --git a/src/Umbraco.Web/UI/JavaScript/Resources.resx b/src/Umbraco.Web/UI/JavaScript/Resources.resx index 2e03928e43..34cea3a2d4 100644 --- a/src/Umbraco.Web/UI/JavaScript/Resources.resx +++ b/src/Umbraco.Web/UI/JavaScript/Resources.resx @@ -124,6 +124,9 @@ Main.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;Windows-1252 + + previewinitialize.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + servervariables.js;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index 1e1fab758a..0a7eecb4c8 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -111,6 +111,7 @@ + @@ -258,7 +259,6 @@ - @@ -637,7 +637,6 @@ - @@ -1451,6 +1450,7 @@ + ASPXCodeBehind From 59a5f50b5ff04a4275beb1917d07ea1983b24cc1 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 14 Sep 2018 00:44:36 +1000 Subject: [PATCH 10/18] now you can preview specific variants --- .../components/content/edit.controller.js | 6 ++++- .../src/preview/preview.controller.js | 22 +++++++------------ src/Umbraco.Web/Editors/PreviewController.cs | 5 +++-- .../Mvc/DisableBrowserCacheAttribute.cs | 5 +++++ .../Routing/ContentFinderByIdPath.cs | 8 +++++++ 5 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 2c32f4a9ca..d857e8b8dd 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -474,7 +474,11 @@ var previewWindow = $window.open('preview/?init=true', 'umbpreview'); // Build the correct path so both /#/ and #/ work. - var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?id=' + content.id; + var query = 'id=' + content.id; + if ($scope.culture) { + query += "&culture=" + $scope.culture; + } + var redirect = Umbraco.Sys.ServerVariables.umbracoSettings.umbracoPath + '/preview/?' + query; //The user cannot save if they don't have access to do that, in which case we just want to preview //and that's it otherwise they'll get an unauthorized access message 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 b3525613ef..e8b5ff9e8c 100644 --- a/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js +++ b/src/Umbraco.Web.UI.Client/src/preview/preview.controller.js @@ -47,26 +47,20 @@ var app = angular.module("umbraco.preview", ['umbraco.resources', 'umbraco.servi return; } - var pageId = $location.search().id; - - //there is no page id query string hash so check if its part of a 'real' query string - //and if so, reload with the query string hash - if (!pageId) { - var queryStringPageId = getParameterByName("id"); - if (queryStringPageId) { - $location.search("id", queryStringPageId); - $window.location.reload(); - return; + $scope.pageId = $location.search().id || getParameterByName("id"); + var culture = $location.search().culture || getParameterByName("culture"); + + if ($scope.pageId) { + var query = 'id=' + $scope.pageId; + if (culture) { + query += "&culture=" + culture; } + $scope.pageUrl = "frame?" + query; } - $scope.isOpen = false; $scope.frameLoaded = false; - $scope.pageId = pageId; - $scope.pageUrl = "frame?id=" + pageId; - $scope.valueAreLoaded = false; $scope.devices = [ { name: "desktop", css: "desktop", icon: "icon-display", title: "Desktop" }, diff --git a/src/Umbraco.Web/Editors/PreviewController.cs b/src/Umbraco.Web/Editors/PreviewController.cs index 94979dfd63..6a91d20ae0 100644 --- a/src/Umbraco.Web/Editors/PreviewController.cs +++ b/src/Umbraco.Web/Editors/PreviewController.cs @@ -67,7 +67,7 @@ namespace Umbraco.Web.Editors /// /// [UmbracoAuthorize] - public ActionResult Frame(int id) + public ActionResult Frame(int id, string culture) { var user = _umbracoContext.Security.CurrentUser; @@ -76,7 +76,8 @@ namespace Umbraco.Web.Editors Response.Cookies.Set(new HttpCookie(Constants.Web.PreviewCookieName, previewToken)); // use a numeric url because content may not be in cache and so .Url would fail - Response.Redirect($"../../{id}.aspx", true); + var query = culture.IsNullOrWhiteSpace() ? string.Empty : $"?culture={culture}"; + Response.Redirect($"../../{id}.aspx{query}", true); return null; } diff --git a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs index 83682c8573..380ec4cd4e 100644 --- a/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs +++ b/src/Umbraco.Web/Mvc/DisableBrowserCacheAttribute.cs @@ -25,6 +25,11 @@ namespace Umbraco.Web.Mvc return; } + if (filterContext.HttpContext.Response.StatusCode != 200) + { + return; + } + filterContext.HttpContext.Response.Cache.SetLastModified(DateTime.Now); filterContext.HttpContext.Response.Cache.SetValidUntilExpires(false); filterContext.HttpContext.Response.Cache.SetCacheability(HttpCacheability.NoCache); diff --git a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs b/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs index 447d4e34af..8360ad7e38 100644 --- a/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs +++ b/src/Umbraco.Web/Routing/ContentFinderByIdPath.cs @@ -3,6 +3,7 @@ using Umbraco.Core; using Umbraco.Core.Configuration; using Umbraco.Core.Configuration.UmbracoSettings; using Umbraco.Core.Models.PublishedContent; +using System.Globalization; namespace Umbraco.Web.Routing { @@ -53,6 +54,13 @@ namespace Umbraco.Web.Routing if (node != null) { + //if we have a node, check if we have a culture in the query string + if (frequest.UmbracoContext.HttpContext.Request.QueryString.ContainsKey("culture")) + { + //we're assuming it will match a culture, if an invalid one is passed in, an exception will throw (there is no TryGetCultureInfo method), i think this is ok though + frequest.Culture = CultureInfo.GetCultureInfo(frequest.UmbracoContext.HttpContext.Request.QueryString["culture"]); + } + frequest.PublishedContent = node; _logger.Debug("Found node with id={PublishedContentId}", frequest.PublishedContent.Id); } From cdf9260f023ea4aa62b3750db72fa5c3b2daf9f3 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 14 Sep 2018 09:20:05 +0100 Subject: [PATCH 11/18] Update the hard coded content app aliases until this gets moved into a package.manifest or similar?! --- src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs | 4 ++-- .../Models/Mapping/ContentAppResolverExtensions.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs b/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs index 3821a56129..cebbe81500 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentAppResolver.cs @@ -13,7 +13,7 @@ namespace Umbraco.Web.Models.Mapping { private readonly ContentApp _contentApp = new ContentApp { - Alias = "content", + Alias = "umbContent", Name = "Content", Icon = "icon-document", View = "views/content/apps/content/content.html" @@ -21,7 +21,7 @@ namespace Umbraco.Web.Models.Mapping private readonly ContentApp _infoApp = new ContentApp { - Alias = "info", + Alias = "umbInfo", Name = "Info", Icon = "icon-info", View = "views/content/apps/info/info.html" diff --git a/src/Umbraco.Web/Models/Mapping/ContentAppResolverExtensions.cs b/src/Umbraco.Web/Models/Mapping/ContentAppResolverExtensions.cs index 8fc77beb6b..ad3fe041c7 100644 --- a/src/Umbraco.Web/Models/Mapping/ContentAppResolverExtensions.cs +++ b/src/Umbraco.Web/Models/Mapping/ContentAppResolverExtensions.cs @@ -21,7 +21,7 @@ namespace Umbraco.Web.Models.Mapping { var listViewApp = new ContentApp { - Alias = "childItems", + Alias = "umbListView", Name = "Child items", Icon = "icon-list", View = "views/content/apps/listview/listview.html" From fe60cdda5c9522b7af2ca1cde56fd5a0e6d591dd Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 14 Sep 2018 10:46:22 +0200 Subject: [PATCH 12/18] fix app aliases client side --- .../common/directives/components/content/edit.controller.js | 2 +- .../components/content/umbvariantcontent.directive.js | 6 +++--- .../content/umbvariantcontenteditors.directive.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 227cb954af..911b561ff3 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -147,7 +147,7 @@ // only create the save/publish/preview buttons if the // content app is "Conent" - if(app && app.alias !== "content") { + if(app && app.alias !== "umbContent" && app.alias !== "umbInfo") { $scope.defaultButton = null; $scope.subButtons = null; $scope.page.showPreviewButton = false; diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js index 54db6308cf..8545854992 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontent.directive.js @@ -40,7 +40,7 @@ // disable the name field if the active content app is not "Content" vm.nameDisabled = false; angular.forEach(vm.editor.content.apps, function(app){ - if(app.active && app.alias !== "content") { + if(app.active && app.alias !== "umbContent" && app.alias !== "umbInfo") { vm.nameDisabled = true; } }); @@ -78,9 +78,9 @@ * @param {any} item */ function selectApp(item) { - // disable the name field if the active content app is not "Content" + // disable the name field if the active content app is not "Content" or "Info" vm.nameDisabled = false; - if(item && item.alias !== "content") { + if(item && item.alias !== "umbContent" && item.alias !== "umbInfo") { vm.nameDisabled = true; } // call the callback if any is registered diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js index ce03a025a3..46d0a1c5c1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbvariantcontenteditors.directive.js @@ -191,7 +191,7 @@ //then assign the variant to a view model to the content app var contentApp = _.find(variant.apps, function (a) { - return a.alias === "content"; + return a.alias === "umbContent"; }); contentApp.viewModel = variant; @@ -221,7 +221,7 @@ var editor = vm.editors[e]; for (var i = 0; i < editor.content.apps.length; i++) { var app = editor.content.apps[i]; - if (app.alias === "content") { + if (app.alias === "umbContent") { app.active = true; } else { From f27bded4e8bf2bb73549ce04c6011864c3e9932f Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 14 Sep 2018 10:59:25 +0200 Subject: [PATCH 13/18] fix audit trail --- .../components/content/umbcontentnodeinfo.directive.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js index 062a7e2877..7c5cdc8857 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/umbcontentnodeinfo.directive.js @@ -315,7 +315,7 @@ // load audit trail when on the info tab evts.push(eventsService.on("app.tabChange", function (event, args) { $timeout(function(){ - if (args.alias === "info") { + if (args.alias === "umbInfo") { isInfoTab = true; loadAuditTrail(); } else { From 2ef33090a9dd5ada2e854b97db2312be41726f14 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 14 Sep 2018 11:52:22 +0200 Subject: [PATCH 14/18] update alias for node with list view scaffolds --- src/Umbraco.Web/Editors/ContentController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b08d8826eb..f53d9d625f 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -359,7 +359,7 @@ namespace Umbraco.Web.Editors var mapped = MapToDisplay(emptyContent); //remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); return mapped; } From cd151ac19a3c15d1e6d6f97256c11fe1f7d7e58e Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 14 Sep 2018 10:53:12 +0100 Subject: [PATCH 15/18] Update MediaApp resolver with new aliases --- src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs b/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs index 6880aff9a8..9cbc6bfeea 100644 --- a/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs +++ b/src/Umbraco.Web/Models/Mapping/MediaAppResolver.cs @@ -12,7 +12,7 @@ namespace Umbraco.Web.Models.Mapping { private static readonly ContentApp _contentApp = new ContentApp { - Alias = "content", + Alias = "umbContent", Name = "Content", Icon = "icon-document", View = "views/media/apps/content/content.html" @@ -20,7 +20,7 @@ namespace Umbraco.Web.Models.Mapping private static readonly ContentApp _infoApp = new ContentApp { - Alias = "info", + Alias = "umbInfo", Name = "Info", Icon = "icon-info", View = "views/media/apps/info/info.html" From 41fd9afab553878cef7edaa960f5f72b339ea0e9 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Fri, 14 Sep 2018 10:53:59 +0100 Subject: [PATCH 16/18] Update API Scaffold call to remove the List View app, now the alias has changed --- src/Umbraco.Web/Editors/ContentController.cs | 4 ++-- src/Umbraco.Web/Editors/MediaController.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Umbraco.Web/Editors/ContentController.cs b/src/Umbraco.Web/Editors/ContentController.cs index b08d8826eb..31d0ac0640 100644 --- a/src/Umbraco.Web/Editors/ContentController.cs +++ b/src/Umbraco.Web/Editors/ContentController.cs @@ -359,7 +359,7 @@ namespace Umbraco.Web.Editors var mapped = MapToDisplay(emptyContent); //remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); return mapped; } @@ -380,7 +380,7 @@ namespace Umbraco.Web.Editors var mapped = Mapper.Map(blueprint); //remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); return mapped; } diff --git a/src/Umbraco.Web/Editors/MediaController.cs b/src/Umbraco.Web/Editors/MediaController.cs index 70aa7e4da8..e44228a022 100644 --- a/src/Umbraco.Web/Editors/MediaController.cs +++ b/src/Umbraco.Web/Editors/MediaController.cs @@ -85,7 +85,7 @@ namespace Umbraco.Web.Editors var mapped = Mapper.Map(emptyContent); //remove the listview app if it exists - mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "childItems").ToList(); + mapped.ContentApps = mapped.ContentApps.Where(x => x.Alias != "umbListView").ToList(); return mapped; } From c9601365d6614ae4eac62c27cd05c9ba0506cde3 Mon Sep 17 00:00:00 2001 From: Mads Rasmussen Date: Fri, 14 Sep 2018 11:56:28 +0200 Subject: [PATCH 17/18] update list view empty state --- src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml index 77e37a439b..326ff2a994 100644 --- a/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml +++ b/src/Umbraco.Web.UI/Umbraco/config/lang/en_us.xml @@ -211,7 +211,7 @@ Last published There are no items to show There are no items to show in the list. - No content has been added + No child items have been added No members have been added Media Type Link to media item(s) From 4ac188f564e3c453424bcd1eeddb74b97546549a Mon Sep 17 00:00:00 2001 From: Stephan Date: Mon, 17 Sep 2018 12:36:45 +0200 Subject: [PATCH 18/18] Forgot to create languages lock object on install --- src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs index bfb8705480..8d8991c396 100644 --- a/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs +++ b/src/Umbraco.Core/Migrations/Install/DatabaseDataCreator.cs @@ -152,6 +152,7 @@ namespace Umbraco.Core.Migrations.Install _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTypes, Name = "MemberTypes" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.MemberTree, Name = "MemberTree" }); _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Domains, Name = "Domains" }); + _database.Insert(Constants.DatabaseSchema.Tables.Lock, "id", false, new LockDto { Id = Constants.Locks.Languages, Name = "Languages" }); } private void CreateContentTypeData()