From 2c91f9bf14641df38948fe7132e28bed296ddaa4 Mon Sep 17 00:00:00 2001 From: Kyle Weems Date: Wed, 7 Mar 2018 10:19:11 -0800 Subject: [PATCH 1/7] Reduce the first load impact of Moment by moving from using `moment-with-locales.js` to `moment.min.js` and loading in the specific locale files when the app authorizes. http://issues.umbraco.org/issue/U4-11044 --- src/Umbraco.Web.UI.Client/bower.json | 7 +++- .../src/common/services/user.service.js | 26 ++++++++++++- src/Umbraco.Web.UI.Client/src/init.js | 33 ++++++++-------- .../fileupload/fileupload.controller.js | 38 +++++++++---------- .../test/config/karma.conf.js | 2 +- src/Umbraco.Web/UI/JavaScript/JsInitialize.js | 2 +- 6 files changed, 69 insertions(+), 39 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/bower.json b/src/Umbraco.Web.UI.Client/bower.json index 4f2a106236..80d7d9366b 100644 --- a/src/Umbraco.Web.UI.Client/bower.json +++ b/src/Umbraco.Web.UI.Client/bower.json @@ -42,7 +42,12 @@ "codemirror" ], "sources": { - "moment": "bower_components/moment/min/moment-with-locales.js", + "moment": [ + "bower_components/moment/min/moment.min.js", + "bower_components/moment/min/moment-with-locales.js", + "bower_components/moment/min/moment-with-locales.min.js", + "bower_components/moment/locale/*.js" + ], "underscore": [ "bower_components/underscore/underscore-min.js", "bower_components/underscore/underscore-min.map" diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index f20c3df44f..12d7a75a77 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,5 +1,5 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, dialogService, $timeout, angularHelper, $http) { + .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http) { var currentUser = null; var lastUserId = null; @@ -276,6 +276,30 @@ angular.module('umbraco.services') return deferred.promise; }, + /** Loads the Moment.js Locale for the current user. */ + loadMomentLocaleForCurrentUser: function () { + var deferred = $q.defer(); + + var supportedLocales = []; + + this.getCurrentUser() + .then(function (user) { + var locale = user.locale.toLowerCase(); + if (locale !== 'en-us') { + var localeUrls = ['lib/moment/' + locale + '.js']; + if (locale.indexOf('-') > -1) { + localeUrls.push('lib/moment/' + locale.split('-')[0] + '.js') + } + assetsService.load(localeUrls).then(function() { + deferred.resolve(localeUrls); + }); + } else { + deferred.resolve(['']); + } + }); + return deferred.promise; + }, + /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ setUserTimeout: function (newTimeout) { setUserTimeoutInternal(newTimeout); diff --git a/src/Umbraco.Web.UI.Client/src/init.js b/src/Umbraco.Web.UI.Client/src/init.js index e86fa25c42..32462826fe 100644 --- a/src/Umbraco.Web.UI.Client/src/init.js +++ b/src/Umbraco.Web.UI.Client/src/init.js @@ -18,23 +18,26 @@ app.run(['userService', '$log', '$rootScope', '$location', 'queryStrings', 'navi eventsService.on("app.authenticated", function(evt, data) { assetsService._loadInitAssets().then(function() { - - //Register all of the tours on the server - tourService.registerAllTours().then(function () { - appReady(data); - - // Auto start intro tour - tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { - // start intro tour if it hasn't been completed or disabled - if (introTour && introTour.disabled !== true && introTour.completed !== true) { - tourService.startTour(introTour); - } + + // Loads the user's locale settings for Moment. + userService.loadMomentLocaleForCurrentUser().then(function() { + + //Register all of the tours on the server + tourService.registerAllTours().then(function () { + appReady(data); + + // Auto start intro tour + tourService.getTourByAlias("umbIntroIntroduction").then(function (introTour) { + // start intro tour if it hasn't been completed or disabled + if (introTour && introTour.disabled !== true && introTour.completed !== true) { + tourService.startTour(introTour); + } + }); + + }, function(){ + appReady(data); }); - - }, function(){ - appReady(data); }); - }); }); diff --git a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js index 3c8170e54b..10b00fd199 100644 --- a/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/propertyeditors/fileupload/fileupload.controller.js @@ -154,26 +154,24 @@ angular.module("umbraco") .controller('Umbraco.PropertyEditors.FileUploadController', fileUploadController) .run(function(mediaHelper, umbRequestHelper, assetsService){ if (mediaHelper && mediaHelper.registerFileResolver) { - assetsService.load(["lib/moment/moment-with-locales.js"]).then( - function () { - //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource - // they contain different data structures so if we need to query against it we need to be aware of this. - mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){ - if (thumbnail) { - if (mediaHelper.detectIfImageByExtension(property.value)) { - //get default big thumbnail from image processor - var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first"; - return thumbnailUrl; - } - else { - return null; - } - } - else { - return property.value; - } - }); + + //NOTE: The 'entity' can be either a normal media entity or an "entity" returned from the entityResource + // they contain different data structures so if we need to query against it we need to be aware of this. + mediaHelper.registerFileResolver("Umbraco.UploadField", function(property, entity, thumbnail){ + if (thumbnail) { + if (mediaHelper.detectIfImageByExtension(property.value)) { + //get default big thumbnail from image processor + var thumbnailUrl = property.value + "?rnd=" + moment(entity.updateDate).format("YYYYMMDDHHmmss") + "&width=500&animationprocessmode=first"; + return thumbnailUrl; + } + else { + return null; + } } - ); + else { + return property.value; + } + }); + } }); diff --git a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js index 711430d5ed..1b10ab0ce4 100644 --- a/src/Umbraco.Web.UI.Client/test/config/karma.conf.js +++ b/src/Umbraco.Web.UI.Client/test/config/karma.conf.js @@ -99,4 +99,4 @@ module.exports = function (config) { 'karma-phantomjs-launcher' ] }); -}; \ No newline at end of file +}; diff --git a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js index 961fb8e54e..39185cff3f 100644 --- a/src/Umbraco.Web/UI/JavaScript/JsInitialize.js +++ b/src/Umbraco.Web/UI/JavaScript/JsInitialize.js @@ -3,7 +3,7 @@ 'lib/angular/1.1.5/angular.min.js', 'lib/underscore/underscore-min.js', - 'lib/moment/moment-with-locales.js', + 'lib/moment/moment.min.js', 'lib/jquery-ui/jquery-ui.min.js', 'lib/jquery-ui-touch-punch/jquery.ui.touch-punch.js', From 564074a2bd2f6463b1719c8ce66915606896bcfe Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Tue, 13 Mar 2018 16:19:51 +0100 Subject: [PATCH 2/7] Adds check for existing moment locales before attempting to download them --- .../IO/FileSystemProviderManager.cs | 5 + .../common/services/momenthelper.service.js | 42 ++ .../src/common/services/user.service.js | 584 +++++++++--------- .../Editors/BackOfficeServerVariables.cs | 4 + src/Umbraco.Web/Editors/MomentController.cs | 28 + src/Umbraco.Web/Umbraco.Web.csproj | 1 + 6 files changed, 377 insertions(+), 287 deletions(-) create mode 100644 src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js create mode 100644 src/Umbraco.Web/Editors/MomentController.cs diff --git a/src/Umbraco.Core/IO/FileSystemProviderManager.cs b/src/Umbraco.Core/IO/FileSystemProviderManager.cs index e50322d95c..1e10d13890 100644 --- a/src/Umbraco.Core/IO/FileSystemProviderManager.cs +++ b/src/Umbraco.Core/IO/FileSystemProviderManager.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Configuration; +using System.IO; using System.Linq; using System.Reflection; using Umbraco.Core.Configuration; @@ -26,6 +27,7 @@ namespace Umbraco.Core.IO private ShadowWrapper _xsltFileSystem; private ShadowWrapper _masterPagesFileSystem; private ShadowWrapper _mvcViewsFileSystem; + private ShadowWrapper _libFileSystem; #region Singleton & Constructor @@ -113,6 +115,7 @@ namespace Umbraco.Core.IO var xsltFileSystem = new PhysicalFileSystem(SystemDirectories.Xslt); var masterPagesFileSystem = new PhysicalFileSystem(SystemDirectories.Masterpages); var mvcViewsFileSystem = new PhysicalFileSystem(SystemDirectories.MvcViews); + var libFileSystem = new PhysicalFileSystem(Path.Combine(SystemDirectories.Umbraco, "lib")); _macroPartialFileSystem = new ShadowWrapper(macroPartialFileSystem, "Views/MacroPartials", ScopeProvider); _partialViewsFileSystem = new ShadowWrapper(partialViewsFileSystem, "Views/Partials", ScopeProvider); @@ -123,6 +126,7 @@ namespace Umbraco.Core.IO _xsltFileSystem = new ShadowWrapper(xsltFileSystem, "xslt", ScopeProvider); _masterPagesFileSystem = new ShadowWrapper(masterPagesFileSystem, "masterpages", ScopeProvider); _mvcViewsFileSystem = new ShadowWrapper(mvcViewsFileSystem, "Views", ScopeProvider); + _libFileSystem = new ShadowWrapper(libFileSystem, "Lib", ScopeProvider); // filesystems obtained from GetFileSystemProvider are already wrapped and do not need to be wrapped again MediaFileSystem = GetFileSystemProvider(); @@ -143,6 +147,7 @@ namespace Umbraco.Core.IO public IFileSystem2 XsltFileSystem { get { return _xsltFileSystem; } } public IFileSystem2 MasterPagesFileSystem { get { return _mvcViewsFileSystem; } } public IFileSystem2 MvcViewsFileSystem { get { return _mvcViewsFileSystem; } } + public IFileSystem2 LibFileSystem { get { return _libFileSystem; } } public MediaFileSystem MediaFileSystem { get; private set; } #endregion diff --git a/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js new file mode 100644 index 0000000000..2bf506352d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js @@ -0,0 +1,42 @@ +(function () { + 'use strict'; + + function momenthelperService($q, $http, umbRequestHelper) { + + var existingLocales = []; + + function getSupportedLocales() { + var deferred = $q.defer(); + + if (existingLocales.length === 0) { + umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "momentApiBaseUrl", + "GetSupportedLocales")), + 'Failed to get cultures').then(function(locales) { + existingLocales = locales; + deferred.resolve(existingLocales); + }); + } else { + deferred.resolve(existingLocales); + } + + return deferred.promise; + } + + //////////// + + var service = { + getSupportedLocales: getSupportedLocales + }; + + return service; + + } + + angular.module('umbraco.services').factory('momenthelperService', momenthelperService); + + +})(); + diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 12d7a75a77..9beb4dc0af 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -1,309 +1,319 @@ angular.module('umbraco.services') - .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http) { + .factory('userService', function ($rootScope, eventsService, $q, $location, $log, securityRetryQueue, authResource, assetsService, dialogService, $timeout, angularHelper, $http, momenthelperService) { - var currentUser = null; - var lastUserId = null; - var loginDialog = null; + var currentUser = null; + var lastUserId = null; + var loginDialog = null; - //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server - // this is used so that we know when to go and get the user's remaining seconds directly. - var lastServerTimeoutSet = null; + //this tracks the last date/time that the user's remainingAuthSeconds was updated from the server + // this is used so that we know when to go and get the user's remaining seconds directly. + var lastServerTimeoutSet = null; - function openLoginDialog(isTimedOut) { - if (!loginDialog) { - loginDialog = dialogService.open({ + function openLoginDialog(isTimedOut) { + if (!loginDialog) { + loginDialog = dialogService.open({ - //very special flag which means that global events cannot close this dialog - manualClose: true, + //very special flag which means that global events cannot close this dialog + manualClose: true, - template: 'views/common/dialogs/login.html', - modalClass: "login-overlay", - animation: "slide", - show: true, - callback: onLoginDialogClose, - dialogData: { - isTimedOut: isTimedOut - } - }); - } - } - - function onLoginDialogClose(success) { - loginDialog = null; - - if (success) { - securityRetryQueue.retryAll(currentUser.name); - } - else { - securityRetryQueue.cancelAll(); - $location.path('/'); - } - } - - /** - This methods will set the current user when it is resolved and - will then start the counter to count in-memory how many seconds they have - remaining on the auth session - */ - function setCurrentUser(usr) { - if (!usr.remainingAuthSeconds) { - throw "The user object is invalid, the remainingAuthSeconds is required."; - } - currentUser = usr; - lastServerTimeoutSet = new Date(); - //start the timer - countdownUserTimeout(); - } - - /** - Method to count down the current user's timeout seconds, - this will continually count down their current remaining seconds every 5 seconds until - there are no more seconds remaining. - */ - function countdownUserTimeout() { - - $timeout(function () { - - if (currentUser) { - //countdown by 5 seconds since that is how long our timer is for. - currentUser.remainingAuthSeconds -= 5; - - //if there are more than 30 remaining seconds, recurse! - if (currentUser.remainingAuthSeconds > 30) { - - //we need to check when the last time the timeout was set from the server, if - // it has been more than 30 seconds then we'll manually go and retrieve it from the - // server - this helps to keep our local countdown in check with the true timeout. - if (lastServerTimeoutSet != null) { - var now = new Date(); - var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; - - if (seconds > 30) { - - //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we - // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. - lastServerTimeoutSet = null; - - //now go get it from the server - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - authResource.getRemainingTimeoutSeconds().then(function (result) { - setUserTimeoutInternal(result); - }); + template: 'views/common/dialogs/login.html', + modalClass: "login-overlay", + animation: "slide", + show: true, + callback: onLoginDialogClose, + dialogData: { + isTimedOut: isTimedOut + } }); - } } + } - //recurse the countdown! - countdownUserTimeout(); - } - else { + function onLoginDialogClose(success) { + loginDialog = null; - //we are either timed out or very close to timing out so we need to show the login dialog. - if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - try { - //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we - // don't actually care about this result. - authResource.getRemainingTimeoutSeconds(); - } - finally { - userAuthExpired(); - } - }); + if (success) { + securityRetryQueue.retryAll(currentUser.name); } else { - //we've got less than 30 seconds remaining so let's check the server - - if (lastServerTimeoutSet != null) { - //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we - // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. - lastServerTimeoutSet = null; - - //now go get it from the server - //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) - angularHelper.safeApply($rootScope, function () { - authResource.getRemainingTimeoutSeconds().then(function (result) { - setUserTimeoutInternal(result); - }); - }); - } - - //recurse the countdown! - countdownUserTimeout(); - + securityRetryQueue.cancelAll(); + $location.path('/'); } - } - } - }, 5000, //every 5 seconds - false); //false = do NOT execute a digest for every iteration - } - - /** Called to update the current user's timeout */ - function setUserTimeoutInternal(newTimeout) { - - - var asNumber = parseFloat(newTimeout); - if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { - currentUser.remainingAuthSeconds = newTimeout; - lastServerTimeoutSet = new Date(); - } - } - - /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */ - function userAuthExpired(isLogout) { - //store the last user id and clear the user - if (currentUser && currentUser.id !== undefined) { - lastUserId = currentUser.id; - } - - if (currentUser) { - currentUser.remainingAuthSeconds = 0; - } - - lastServerTimeoutSet = null; - currentUser = null; - - //broadcast a global event that the user is no longer logged in - eventsService.emit("app.notAuthenticated"); - - openLoginDialog(isLogout === undefined ? true : !isLogout); - } - - // Register a handler for when an item is added to the retry queue - securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) { - if (securityRetryQueue.hasMore()) { - userAuthExpired(); - } - }); - - return { - - /** Internal method to display the login dialog */ - _showLoginDialog: function () { - openLoginDialog(); - }, - /** Returns a promise, sends a request to the server to check if the current cookie is authorized */ - isAuthenticated: function () { - //if we've got a current user then just return true - if (currentUser) { - var deferred = $q.defer(); - deferred.resolve(true); - return deferred.promise; - } - return authResource.isAuthenticated(); - }, - - /** Returns a promise, sends a request to the server to validate the credentials */ - authenticate: function (login, password) { - - return authResource.performLogin(login, password) - .then(this.setAuthenticationSuccessful); - }, - setAuthenticationSuccessful: function (data) { - - //when it's successful, return the user data - setCurrentUser(data); - - var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" }; - - //broadcast a global event - eventsService.emit("app.authenticated", result); - return result; - }, - - /** Logs the user out - */ - logout: function () { - - return authResource.performLogout() - .then(function (data) { - userAuthExpired(); - //done! - return null; - }); - }, - - /** Refreshes the current user data with the data stored for the user on the server and returns it */ - refreshCurrentUser: function() { - var deferred = $q.defer(); - - authResource.getCurrentUser() - .then(function (data) { - - var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; - - setCurrentUser(data); - - deferred.resolve(currentUser); - }, function () { - //it failed, so they are not logged in - deferred.reject(); - }); - - return deferred.promise; - }, - - /** Returns the current user object in a promise */ - getCurrentUser: function (args) { - var deferred = $q.defer(); - - if (!currentUser) { - authResource.getCurrentUser() - .then(function (data) { - - var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; - - if (args && args.broadcastEvent) { - //broadcast a global event, will inform listening controllers to load in the user specific data - eventsService.emit("app.authenticated", result); - } - - setCurrentUser(data); - - deferred.resolve(currentUser); - }, function () { - //it failed, so they are not logged in - deferred.reject(); - }); - - } - else { - deferred.resolve(currentUser); } - return deferred.promise; - }, + /** + This methods will set the current user when it is resolved and + will then start the counter to count in-memory how many seconds they have + remaining on the auth session + */ + function setCurrentUser(usr) { + if (!usr.remainingAuthSeconds) { + throw "The user object is invalid, the remainingAuthSeconds is required."; + } + currentUser = usr; + lastServerTimeoutSet = new Date(); + //start the timer + countdownUserTimeout(); + } - /** Loads the Moment.js Locale for the current user. */ - loadMomentLocaleForCurrentUser: function () { - var deferred = $q.defer(); + /** + Method to count down the current user's timeout seconds, + this will continually count down their current remaining seconds every 5 seconds until + there are no more seconds remaining. + */ + function countdownUserTimeout() { - var supportedLocales = []; + $timeout(function () { - this.getCurrentUser() - .then(function (user) { - var locale = user.locale.toLowerCase(); - if (locale !== 'en-us') { - var localeUrls = ['lib/moment/' + locale + '.js']; - if (locale.indexOf('-') > -1) { - localeUrls.push('lib/moment/' + locale.split('-')[0] + '.js') + if (currentUser) { + //countdown by 5 seconds since that is how long our timer is for. + currentUser.remainingAuthSeconds -= 5; + + //if there are more than 30 remaining seconds, recurse! + if (currentUser.remainingAuthSeconds > 30) { + + //we need to check when the last time the timeout was set from the server, if + // it has been more than 30 seconds then we'll manually go and retrieve it from the + // server - this helps to keep our local countdown in check with the true timeout. + if (lastServerTimeoutSet != null) { + var now = new Date(); + var seconds = (now.getTime() - lastServerTimeoutSet.getTime()) / 1000; + + if (seconds > 30) { + + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; + + //now go get it from the server + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + }); + } + } + + //recurse the countdown! + countdownUserTimeout(); + } + else { + + //we are either timed out or very close to timing out so we need to show the login dialog. + if (Umbraco.Sys.ServerVariables.umbracoSettings.keepUserLoggedIn !== true) { + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + try { + //NOTE: We are calling this again so that the server can create a log that the timeout has expired, we + // don't actually care about this result. + authResource.getRemainingTimeoutSeconds(); + } + finally { + userAuthExpired(); + } + }); + } + else { + //we've got less than 30 seconds remaining so let's check the server + + if (lastServerTimeoutSet != null) { + //first we'll set the lastServerTimeoutSet to null - this is so we don't get back in to this loop while we + // wait for a response from the server otherwise we'll be making double/triple/etc... calls while we wait. + lastServerTimeoutSet = null; + + //now go get it from the server + //NOTE: the safeApply because our timeout is set to not run digests (performance reasons) + angularHelper.safeApply($rootScope, function () { + authResource.getRemainingTimeoutSeconds().then(function (result) { + setUserTimeoutInternal(result); + }); + }); + } + + //recurse the countdown! + countdownUserTimeout(); + + } } - assetsService.load(localeUrls).then(function() { - deferred.resolve(localeUrls); - }); - } else { - deferred.resolve(['']); } - }); - return deferred.promise; - }, + }, 5000, //every 5 seconds + false); //false = do NOT execute a digest for every iteration + } - /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ - setUserTimeout: function (newTimeout) { - setUserTimeoutInternal(newTimeout); - } - }; + /** Called to update the current user's timeout */ + function setUserTimeoutInternal(newTimeout) { - }); + + var asNumber = parseFloat(newTimeout); + if (!isNaN(asNumber) && currentUser && angular.isNumber(asNumber)) { + currentUser.remainingAuthSeconds = newTimeout; + lastServerTimeoutSet = new Date(); + } + } + + /** resets all user data, broadcasts the notAuthenticated event and shows the login dialog */ + function userAuthExpired(isLogout) { + //store the last user id and clear the user + if (currentUser && currentUser.id !== undefined) { + lastUserId = currentUser.id; + } + + if (currentUser) { + currentUser.remainingAuthSeconds = 0; + } + + lastServerTimeoutSet = null; + currentUser = null; + + //broadcast a global event that the user is no longer logged in + eventsService.emit("app.notAuthenticated"); + + openLoginDialog(isLogout === undefined ? true : !isLogout); + } + + // Register a handler for when an item is added to the retry queue + securityRetryQueue.onItemAddedCallbacks.push(function (retryItem) { + if (securityRetryQueue.hasMore()) { + userAuthExpired(); + } + }); + + return { + + /** Internal method to display the login dialog */ + _showLoginDialog: function () { + openLoginDialog(); + }, + /** Returns a promise, sends a request to the server to check if the current cookie is authorized */ + isAuthenticated: function () { + //if we've got a current user then just return true + if (currentUser) { + var deferred = $q.defer(); + deferred.resolve(true); + return deferred.promise; + } + return authResource.isAuthenticated(); + }, + + /** Returns a promise, sends a request to the server to validate the credentials */ + authenticate: function (login, password) { + + return authResource.performLogin(login, password) + .then(this.setAuthenticationSuccessful); + }, + setAuthenticationSuccessful: function (data) { + + //when it's successful, return the user data + setCurrentUser(data); + + var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "credentials" }; + + //broadcast a global event + eventsService.emit("app.authenticated", result); + return result; + }, + + /** Logs the user out + */ + logout: function () { + + return authResource.performLogout() + .then(function (data) { + userAuthExpired(); + //done! + return null; + }); + }, + + /** Refreshes the current user data with the data stored for the user on the server and returns it */ + refreshCurrentUser: function () { + var deferred = $q.defer(); + + authResource.getCurrentUser() + .then(function (data) { + + var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; + + setCurrentUser(data); + + deferred.resolve(currentUser); + }, function () { + //it failed, so they are not logged in + deferred.reject(); + }); + + return deferred.promise; + }, + + /** Returns the current user object in a promise */ + getCurrentUser: function (args) { + var deferred = $q.defer(); + + if (!currentUser) { + authResource.getCurrentUser() + .then(function (data) { + + var result = { user: data, authenticated: true, lastUserId: lastUserId, loginType: "implicit" }; + + if (args && args.broadcastEvent) { + //broadcast a global event, will inform listening controllers to load in the user specific data + eventsService.emit("app.authenticated", result); + } + + setCurrentUser(data); + + deferred.resolve(currentUser); + }, function () { + //it failed, so they are not logged in + deferred.reject(); + }); + + } + else { + deferred.resolve(currentUser); + } + + return deferred.promise; + }, + + /** Loads the Moment.js Locale for the current user. */ + loadMomentLocaleForCurrentUser: function () { + var deferred = $q.defer(); + + + this.getCurrentUser() + .then(function(user) { + var locale = user.locale.toLowerCase(); + + momenthelperService.getSupportedLocales().then(function(supportedLocales) { + + if (locale !== 'en-us') { + var localeUrls = []; + if (supportedLocales.indexOf(locale + '.js') > -1) { + localeUrls.push('lib/moment/' + locale + '.js'); + } + if (locale.indexOf('-') > -1) { + var majorLocale = locale.split('-')[0] + '.js'; + if (supportedLocales.indexOf(majorLocale) > -1) { + localeUrls.push('lib/moment/' + majorLocale); + } + } + assetsService.load(localeUrls).then(function() { + deferred.resolve(localeUrls); + }); + } else { + deferred.resolve(['']); + } + }); + }); + return deferred.promise; + + }, + + /** Called whenever a server request is made that contains a x-umb-user-seconds response header for which we can update the user's remaining timeout seconds */ + setUserTimeout: function (newTimeout) { + setUserTimeoutInternal(newTimeout); + } + }; + + }); diff --git a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs index d401c1c266..857929d63b 100644 --- a/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs +++ b/src/Umbraco.Web/Editors/BackOfficeServerVariables.cs @@ -268,6 +268,10 @@ namespace Umbraco.Web.Editors { "helpApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( controller => controller.GetContextHelpForPage("","","")) + }, + { + "momentApiBaseUrl", _urlHelper.GetUmbracoApiServiceBaseUrl( + controller => controller.GetSupportedLocales()) } } }, diff --git a/src/Umbraco.Web/Editors/MomentController.cs b/src/Umbraco.Web/Editors/MomentController.cs new file mode 100644 index 0000000000..c5bc305d9d --- /dev/null +++ b/src/Umbraco.Web/Editors/MomentController.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Http; +using Umbraco.Core.IO; +using Umbraco.Web.Mvc; + +namespace Umbraco.Web.Editors +{ + [PluginController("UmbracoApi")] + public class MomentController : UmbracoAuthorizedJsonController + { + + [HttpGet] + public IEnumerable GetSupportedLocales() + { + var momentLocaleFolder = "moment"; + var fileSystem = FileSystemProviderManager.Current.LibFileSystem; + var cultures = fileSystem.GetFiles(momentLocaleFolder, "*.js").ToList(); + for (var i = 0; i < cultures.Count(); i++) + { + cultures[i] = cultures[i] + .Substring(cultures[i].IndexOf(momentLocaleFolder) + momentLocaleFolder.Length + 1); + } + return cultures; + } + + } +} diff --git a/src/Umbraco.Web/Umbraco.Web.csproj b/src/Umbraco.Web/Umbraco.Web.csproj index c6d528bb3d..e77ffd1677 100644 --- a/src/Umbraco.Web/Umbraco.Web.csproj +++ b/src/Umbraco.Web/Umbraco.Web.csproj @@ -316,6 +316,7 @@ + From 6bafd3a2549a0292c9c02c064d81d3b849279cec Mon Sep 17 00:00:00 2001 From: Niels Hartvig Date: Tue, 13 Mar 2018 16:39:01 +0100 Subject: [PATCH 3/7] Removes ugly non-existing comment :) --- .../src/common/services/momenthelper.service.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js b/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js index 2bf506352d..47ca467200 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/momenthelper.service.js @@ -25,8 +25,6 @@ return deferred.promise; } - //////////// - var service = { getSupportedLocales: getSupportedLocales }; From b4aad1957379f3460232cd070a8ccf4e59023a6b Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 14 Mar 2018 13:23:33 +1100 Subject: [PATCH 4/7] Revert "Merge pull request #2508 from umbraco/temp-U4-11076" This reverts commit 4161d244c1aa59b025c7d758065578c111e7ee83, reversing changes made to eea87e411f37f118524e0d19f906a9866de72557. --- .../content/umbcontentnodeinfo.directive.js | 2 - .../content/umb-content-node-info.html | 26 ++- .../views/documenttypes/create.controller.js | 30 +-- .../src/views/documenttypes/create.html | 16 +- .../views/documenttypes/edit.controller.js | 220 ++++++++---------- .../Editors/BackOfficeServerVariables.cs | 24 +- src/Umbraco.Web/Features/DisabledFeatures.cs | 5 - src/Umbraco.Web/Features/EnabledFeatures.cs | 14 ++ src/Umbraco.Web/Features/UmbracoFeatures.cs | 12 +- src/Umbraco.Web/Mvc/RenderRouteHandler.cs | 12 +- src/Umbraco.Web/Umbraco.Web.csproj | 1 + .../Filters/FeatureAuthorizeAttribute.cs | 2 +- 12 files changed, 168 insertions(+), 196 deletions(-) create mode 100644 src/Umbraco.Web/Features/EnabledFeatures.cs 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 b2bbd9006a..2ce0c5a22e 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 @@ -8,8 +8,6 @@ var evts = []; var isInfoTab = false; scope.publishStatus = {}; - - scope.disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; function onInit() { diff --git a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html index f2be5604ed..564d50951e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/content/umb-content-node-info.html @@ -171,25 +171,27 @@ {{publishStatus.label}} - + {{node.createDateFormatted}} by {{ node.owner.name }} - + - - @@ -198,7 +200,7 @@
{{ node.id }}
{{ node.key }} - + diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js index aade96c3cc..4e734b76a6 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.controller.js @@ -14,21 +14,18 @@ function DocumentTypesCreateController($scope, $location, navigationService, con creatingFolder: false, }; - var disableTemplates = Umbraco.Sys.ServerVariables.features.disabledFeatures.disableTemplates; - $scope.model.disableTemplates = disableTemplates; - var node = $scope.dialogOptions.currentNode, localizeCreateFolder = localizationService.localize("defaultdialog_createFolder"); - $scope.showCreateFolder = function () { + $scope.showCreateFolder = function() { $scope.model.creatingFolder = true; }; - $scope.createContainer = function () { + $scope.createContainer = function() { - if (formHelper.submitForm({ scope: $scope, formCtrl: this.createFolderForm, statusMessage: localizeCreateFolder })) { + if (formHelper.submitForm({scope: $scope, formCtrl: this.createFolderForm, statusMessage: localizeCreateFolder})) { - contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function (folderId) { + contentTypeResource.createContainer(node.id, $scope.model.folderName).then(function(folderId) { navigationService.hideMenu(); @@ -47,7 +44,7 @@ function DocumentTypesCreateController($scope, $location, navigationService, con var section = appState.getSectionState("currentSection"); - }, function (err) { + }, function(err) { $scope.error = err; @@ -61,17 +58,14 @@ function DocumentTypesCreateController($scope, $location, navigationService, con } }; - // Disabling logic for creating document type with template if disableTemplates is set to true - if (!disableTemplates) { - $scope.createDocType = function () { - $location.search('create', null); - $location.search('notemplate', null); - $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true"); - navigationService.hideMenu(); - }; - } + $scope.createDocType = function() { + $location.search('create', null); + $location.search('notemplate', null); + $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true"); + navigationService.hideMenu(); + }; - $scope.createComponent = function () { + $scope.createComponent = function() { $location.search('create', null); $location.search('notemplate', null); $location.path("/settings/documenttypes/edit/" + node.id).search("create", "true").search("notemplate", "true"); diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html index e5043be785..b61a4c014e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/create.html @@ -4,7 +4,7 @@
Create an item under {{currentNode.name}}