From da4270ce35655b20ca32edc35090c33ce9845213 Mon Sep 17 00:00:00 2001 From: Felix Rieseberg Date: Tue, 25 Nov 2014 12:56:08 -0800 Subject: [PATCH] Dynamic Titles in Ghost Admin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Every route can set a title token that is combined with the blog’s title, resulting in titles like ‘Content - Test Blog’. - Subroutes are supported (‘Settings - General - Test Blog’) - The blog’s name is applied to and taken from the `config` object to spare Ember a REST call via `store.find(‘settings’)`. - Tests have been changed to test for the new titles. - The initially proposed solution (https://github.com/paddle8/ember-document-title) doesn’t play nice with EAK, which is why I went with this solution (https://gist.github.com/machty/8413411) by Ember.JS core dev @Machty. --- ghost/admin/initializers/ghost-config.js | 5 +- ghost/admin/router.js | 4 ++ ghost/admin/routes/application.js | 4 ++ ghost/admin/routes/debug.js | 2 + ghost/admin/routes/editor/edit.js | 2 + ghost/admin/routes/editor/new.js | 2 + ghost/admin/routes/error404.js | 1 + ghost/admin/routes/forgotten.js | 2 + ghost/admin/routes/posts.js | 2 + ghost/admin/routes/settings.js | 2 + ghost/admin/routes/settings/about.js | 2 + ghost/admin/routes/settings/apps.js | 2 + ghost/admin/routes/settings/general.js | 2 + ghost/admin/routes/settings/index.js | 2 + ghost/admin/routes/settings/tags.js | 2 + ghost/admin/routes/settings/users/index.js | 2 + ghost/admin/routes/settings/users/user.js | 2 + ghost/admin/routes/setup.js | 2 + ghost/admin/routes/signin.js | 3 ++ ghost/admin/routes/signout.js | 2 + ghost/admin/utils/document-title.js | 60 ++++++++++++++++++++++ 21 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 ghost/admin/utils/document-title.js diff --git a/ghost/admin/initializers/ghost-config.js b/ghost/admin/initializers/ghost-config.js index 4f74d54bac..519d900306 100644 --- a/ghost/admin/initializers/ghost-config.js +++ b/ghost/admin/initializers/ghost-config.js @@ -5,10 +5,11 @@ var ConfigInitializer = { var apps = $('body').data('apps'), tagsUI = $('body').data('tagsui'), fileStorage = $('body').data('filestorage'), - blogUrl = $('body').data('blogurl'); + blogUrl = $('body').data('blogurl'), + blogTitle = $('body').data('blogtitle'); application.register( - 'ghost:config', {apps: apps, fileStorage: fileStorage, blogUrl: blogUrl, tagsUI: tagsUI}, {instantiate: false} + 'ghost:config', {apps: apps, fileStorage: fileStorage, blogUrl: blogUrl, tagsUI: tagsUI, blogTitle: blogTitle}, {instantiate: false} ); application.inject('route', 'config', 'ghost:config'); diff --git a/ghost/admin/router.js b/ghost/admin/router.js index 684650ab10..d17127684b 100644 --- a/ghost/admin/router.js +++ b/ghost/admin/router.js @@ -1,9 +1,13 @@ /*global Ember */ +/* jshint unused: false */ import ghostPaths from 'ghost/utils/ghost-paths'; +import documentTitle from 'ghost/utils/document-title'; // ensure we don't share routes between all Router instances var Router = Ember.Router.extend(); +documentTitle(); + Router.reopen({ location: 'trailing-history', // use HTML5 History API instead of hash-tag based URLs rootURL: ghostPaths().adminRoot, // admin interface lives under sub-directory /ghost diff --git a/ghost/admin/routes/application.js b/ghost/admin/routes/application.js index fd1ec7203a..ccbbc27bf2 100644 --- a/ghost/admin/routes/application.js +++ b/ghost/admin/routes/application.js @@ -14,6 +14,10 @@ var ApplicationRoute = Ember.Route.extend(SimpleAuth.ApplicationRouteMixin, Shor enter: {action: 'confirmModal', scope: 'modal'} }, + title: function (tokens) { + return tokens.join(' - ') + ' - ' + this.get('config.blogTitle'); + }, + actions: { authorizationFailed: function () { var currentRoute = this.get('controller').get('currentRouteName'); diff --git a/ghost/admin/routes/debug.js b/ghost/admin/routes/debug.js index d7f5d5f2fd..d91ba7fa02 100644 --- a/ghost/admin/routes/debug.js +++ b/ghost/admin/routes/debug.js @@ -3,6 +3,8 @@ import styleBody from 'ghost/mixins/style-body'; import loadingIndicator from 'ghost/mixins/loading-indicator'; var DebugRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, { + titleToken: 'Debug', + classNames: ['settings'], beforeModel: function (transition) { diff --git a/ghost/admin/routes/editor/edit.js b/ghost/admin/routes/editor/edit.js index 4c151392f5..584666ada3 100644 --- a/ghost/admin/routes/editor/edit.js +++ b/ghost/admin/routes/editor/edit.js @@ -4,6 +4,8 @@ import isNumber from 'ghost/utils/isNumber'; import isFinite from 'ghost/utils/isFinite'; var EditorEditRoute = AuthenticatedRoute.extend(base, { + titleToken: 'Editor', + model: function (params) { var self = this, post, diff --git a/ghost/admin/routes/editor/new.js b/ghost/admin/routes/editor/new.js index 96da4d70a6..7adcdbde7a 100644 --- a/ghost/admin/routes/editor/new.js +++ b/ghost/admin/routes/editor/new.js @@ -2,6 +2,8 @@ import AuthenticatedRoute from 'ghost/routes/authenticated'; import base from 'ghost/mixins/editor-base-route'; var EditorNewRoute = AuthenticatedRoute.extend(base, { + titleToken: 'Editor', + model: function () { var self = this; return this.get('session.user').then(function (user) { diff --git a/ghost/admin/routes/error404.js b/ghost/admin/routes/error404.js index bfdd3c53af..53bdf69b1b 100644 --- a/ghost/admin/routes/error404.js +++ b/ghost/admin/routes/error404.js @@ -1,6 +1,7 @@ var Error404Route = Ember.Route.extend({ controllerName: 'error', templateName: 'error', + titleToken: 'Error', model: function () { return { diff --git a/ghost/admin/routes/forgotten.js b/ghost/admin/routes/forgotten.js index 90c816e2ab..694e18457a 100644 --- a/ghost/admin/routes/forgotten.js +++ b/ghost/admin/routes/forgotten.js @@ -2,6 +2,8 @@ import styleBody from 'ghost/mixins/style-body'; import loadingIndicator from 'ghost/mixins/loading-indicator'; var ForgottenRoute = Ember.Route.extend(styleBody, loadingIndicator, { + titleToken: 'Forgotten Password', + classNames: ['ghost-forgotten'] }); diff --git a/ghost/admin/routes/posts.js b/ghost/admin/routes/posts.js index 533e99a22d..83c23620d7 100644 --- a/ghost/admin/routes/posts.js +++ b/ghost/admin/routes/posts.js @@ -14,6 +14,8 @@ paginationSettings = { }; PostsRoute = AuthenticatedRoute.extend(ShortcutsRoute, styleBody, loadingIndicator, PaginationRouteMixin, { + titleToken: 'Content', + classNames: ['manage'], model: function () { diff --git a/ghost/admin/routes/settings.js b/ghost/admin/routes/settings.js index 5af5592dce..7ea5142fc6 100644 --- a/ghost/admin/routes/settings.js +++ b/ghost/admin/routes/settings.js @@ -3,6 +3,8 @@ import styleBody from 'ghost/mixins/style-body'; import loadingIndicator from 'ghost/mixins/loading-indicator'; var SettingsRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, { + titleToken: 'Settings', + classNames: ['settings'] }); diff --git a/ghost/admin/routes/settings/about.js b/ghost/admin/routes/settings/about.js index 8ae17f672d..0933418af6 100644 --- a/ghost/admin/routes/settings/about.js +++ b/ghost/admin/routes/settings/about.js @@ -3,6 +3,8 @@ import loadingIndicator from 'ghost/mixins/loading-indicator'; import styleBody from 'ghost/mixins/style-body'; var SettingsAboutRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, { + titleToken: 'About', + classNames: ['settings-view-about'], cachedConfig: false, diff --git a/ghost/admin/routes/settings/apps.js b/ghost/admin/routes/settings/apps.js index b691794639..b99e0fe9b1 100644 --- a/ghost/admin/routes/settings/apps.js +++ b/ghost/admin/routes/settings/apps.js @@ -3,6 +3,8 @@ import CurrentUserSettings from 'ghost/mixins/current-user-settings'; import styleBody from 'ghost/mixins/style-body'; var AppsRoute = AuthenticatedRoute.extend(styleBody, CurrentUserSettings, { + titleToken: 'Apps', + classNames: ['settings-view-apps'], beforeModel: function () { diff --git a/ghost/admin/routes/settings/general.js b/ghost/admin/routes/settings/general.js index 1f4008d100..9e0fa1afd9 100644 --- a/ghost/admin/routes/settings/general.js +++ b/ghost/admin/routes/settings/general.js @@ -11,6 +11,8 @@ var shortcuts = {}, shortcuts[ctrlOrCmd + '+s'] = {action: 'save'}; SettingsGeneralRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, CurrentUserSettings, ShortcutsRoute, { + titleToken: 'General', + classNames: ['settings-view-general'], beforeModel: function () { diff --git a/ghost/admin/routes/settings/index.js b/ghost/admin/routes/settings/index.js index a48b516456..03111bfce4 100644 --- a/ghost/admin/routes/settings/index.js +++ b/ghost/admin/routes/settings/index.js @@ -3,6 +3,8 @@ import CurrentUserSettings from 'ghost/mixins/current-user-settings'; import mobileQuery from 'ghost/utils/mobile'; var SettingsIndexRoute = MobileIndexRoute.extend(SimpleAuth.AuthenticatedRouteMixin, CurrentUserSettings, { + titleToken: 'Settings', + // Redirect users without permission to view settings, // and show the settings.general route unless the user // is mobile diff --git a/ghost/admin/routes/settings/tags.js b/ghost/admin/routes/settings/tags.js index 17e6dc5fa6..83543950db 100644 --- a/ghost/admin/routes/settings/tags.js +++ b/ghost/admin/routes/settings/tags.js @@ -3,6 +3,8 @@ import CurrentUserSettings from 'ghost/mixins/current-user-settings'; var TagsRoute = AuthenticatedRoute.extend(CurrentUserSettings, { + titleToken: 'Tags', + beforeModel: function () { if (!this.get('config.tagsUI')) { return this.transitionTo('settings.general'); diff --git a/ghost/admin/routes/settings/users/index.js b/ghost/admin/routes/settings/users/index.js index 9a6d56f0c2..800993aaeb 100644 --- a/ghost/admin/routes/settings/users/index.js +++ b/ghost/admin/routes/settings/users/index.js @@ -12,6 +12,8 @@ paginationSettings = { }; UsersIndexRoute = AuthenticatedRoute.extend(styleBody, PaginationRouteMixin, { + titleToken: 'Users', + classNames: ['settings-view-users'], setupController: function (controller, model) { diff --git a/ghost/admin/routes/settings/users/user.js b/ghost/admin/routes/settings/users/user.js index aea2cf69a7..b6f1f21db0 100644 --- a/ghost/admin/routes/settings/users/user.js +++ b/ghost/admin/routes/settings/users/user.js @@ -9,6 +9,8 @@ var shortcuts = {}, shortcuts[ctrlOrCmd + '+s'] = {action: 'save'}; SettingsUserRoute = AuthenticatedRoute.extend(styleBody, ShortcutsRoute, { + titleToken: 'User', + classNames: ['settings-view-user'], model: function (params) { diff --git a/ghost/admin/routes/setup.js b/ghost/admin/routes/setup.js index 65ec5ffc25..76a21c4ee0 100644 --- a/ghost/admin/routes/setup.js +++ b/ghost/admin/routes/setup.js @@ -2,6 +2,8 @@ import styleBody from 'ghost/mixins/style-body'; import loadingIndicator from 'ghost/mixins/loading-indicator'; var SetupRoute = Ember.Route.extend(styleBody, loadingIndicator, { + titleToken: 'Setup', + classNames: ['ghost-setup'], // use the beforeModel hook to check to see whether or not setup has been diff --git a/ghost/admin/routes/signin.js b/ghost/admin/routes/signin.js index d28d120e51..6db40151d0 100644 --- a/ghost/admin/routes/signin.js +++ b/ghost/admin/routes/signin.js @@ -2,7 +2,10 @@ import styleBody from 'ghost/mixins/style-body'; import loadingIndicator from 'ghost/mixins/loading-indicator'; var SigninRoute = Ember.Route.extend(styleBody, loadingIndicator, { + titleToken: 'Sign In', + classNames: ['ghost-login'], + beforeModel: function () { if (this.get('session').isAuthenticated) { this.transitionTo(SimpleAuth.Configuration.routeAfterAuthentication); diff --git a/ghost/admin/routes/signout.js b/ghost/admin/routes/signout.js index 3ba5f22289..94eb2463de 100644 --- a/ghost/admin/routes/signout.js +++ b/ghost/admin/routes/signout.js @@ -3,6 +3,8 @@ import styleBody from 'ghost/mixins/style-body'; import loadingIndicator from 'ghost/mixins/loading-indicator'; var SignoutRoute = AuthenticatedRoute.extend(styleBody, loadingIndicator, { + titleToken: 'Sign Out', + classNames: ['ghost-signout'], afterModel: function (model, transition) { diff --git a/ghost/admin/utils/document-title.js b/ghost/admin/utils/document-title.js new file mode 100644 index 0000000000..834be34103 --- /dev/null +++ b/ghost/admin/utils/document-title.js @@ -0,0 +1,60 @@ +var documentTitle = function () { + Ember.Route.reopen({ + // `titleToken` can either be a static string or a function + // that accepts a model object and returns a string (or array + // of strings if there are multiple tokens). + titleToken: null, + + // `title` can either be a static string or a function + // that accepts an array of tokens and returns a string + // that will be the document title. The `collectTitleTokens` action + // stops bubbling once a route is encountered that has a `title` + // defined. + title: null, + + _actions: { + collectTitleTokens: function (tokens) { + var titleToken = this.titleToken, + finalTitle; + + if (typeof this.titleToken === 'function') { + titleToken = this.titleToken(this.currentModel); + } + + if (Ember.isArray(titleToken)) { + tokens.unshift.apply(this, titleToken); + } else if (titleToken) { + tokens.unshift(titleToken); + } + + if (this.title) { + if (typeof this.title === 'function') { + finalTitle = this.title(tokens); + } else { + finalTitle = this.title; + } + + this.router.setTitle(finalTitle); + } else { + return true; + } + } + } + }); + + Ember.Router.reopen({ + updateTitle: function () { + this.send('collectTitleTokens', []); + }.on('didTransition'), + + setTitle: function (title) { + if (Ember.testing) { + this._title = title; + } else { + window.document.title = title; + } + } + }); +}; + +export default documentTitle;