From f6b25ae3ba70c8374c92cbceb9c50e46527fc914 Mon Sep 17 00:00:00 2001 From: Marco Otte-Witte Date: Wed, 31 Jul 2019 12:47:19 +0200 Subject: [PATCH] Better handling of title tag and related meta tags (#662) * rename Header @title to @headline * write title tags from page components' header * rename pageTitle arg to documentTitle * fix tests * fix formatting * restore blog index page title --- config/routes-map.js | 39 +++++++--------- .../lib/templates/author/template.hbs | 2 +- .../lib/templates/post/template.hbs | 2 +- .../lib/templates/start-page/template.hbs | 2 +- .../lib/templates/page/template.hbs | 2 +- .../lib/templates/page/template.hbs | 2 +- src/index.ts | 38 ++++++++++++++++ src/ui/components/HeadTag/component.ts | 11 ++++- src/ui/components/Header/component.ts | 14 ++++++ src/ui/components/Header/template.hbs | 6 ++- src/ui/components/Page404/template.hbs | 2 +- src/ui/components/PageCaseDdWrt/template.hbs | 9 +++- .../PageCaseStudyExpedition/template.hbs | 9 +++- .../PageCaseStudyTimify/template.hbs | 9 +++- .../PageCaseStudyTrainline/template.hbs | 9 +++- src/ui/components/PageContact/template.hbs | 2 +- .../PageElixirExpertise/template.hbs | 2 +- .../PageEmberExpertise/template.hbs | 2 +- .../PageFullStackEngineering/template.hbs | 9 +++- src/ui/components/PageHomepage/template.hbs | 2 +- .../PageLandingPwaWebinar/template.hbs | 5 ++- .../components/PageLegalImprint/template.hbs | 2 +- .../components/PageLegalPrivacy/template.hbs | 2 +- src/ui/components/PagePlaybook/template.hbs | 2 +- src/ui/components/PageServices/template.hbs | 3 +- .../PageTeamAugmentation/template.hbs | 9 +++- src/ui/components/PageTutoring/template.hbs | 9 +++- .../components/PageWhySimplabs/template.hbs | 7 ++- src/ui/components/PageWork/template.hbs | 3 +- src/ui/components/Simplabs/component.ts | 34 +------------- .../test-helpers/setup-rendering-test.ts | 45 +++++++++++++++++++ ssr/ssr-application.ts | 2 +- 32 files changed, 212 insertions(+), 84 deletions(-) create mode 100644 src/ui/components/Header/component.ts diff --git a/config/routes-map.js b/config/routes-map.js index 3563a396b0..33eed8b35d 100644 --- a/config/routes-map.js +++ b/config/routes-map.js @@ -8,7 +8,6 @@ module.exports = function() { let blogPostRoutes = posts.reduce((acc, post) => { acc[`/blog/${post.queryPath}`] = { component: post.componentName, - title: `${post.meta.title} | Blog`, bundle: { asset: `/blog/${post.queryPath}.js`, module: `__blog-${post.queryPath}__`, @@ -23,7 +22,6 @@ module.exports = function() { let blogAuthorsRoutes = authors.reduce((acc, author) => { acc[`/blog/author/${author.twitter}`] = { component: author.componentName, - title: `Posts by ${author.name} | Blog`, bundle: { asset: `/blog/author-${author.twitter}.js`, module: `__blog-author-${author.twitter}__`, @@ -39,46 +37,41 @@ module.exports = function() { ...blogPostRoutes, ...blogAuthorsRoutes, '/': { component: 'PageHomepage' }, - '/404': { component: 'Page404', title: 'Not found' }, - '/blog': { component: 'PageBlog', title: 'Blog', bundle: { asset: '/blog.js', module: '__blog__' } }, + '/404': { component: 'Page404' }, + '/blog': { component: 'PageBlog', bundle: { asset: '/blog.js', module: '__blog__' } }, '/calendar': { component: 'PageCalendar', - title: 'Calendar', bundle: { asset: '/calendar.js', module: '__calendar__' }, }, - '/cases/ddwrt': { component: 'PageCaseDdWrt', title: 'DD-WRT NXT | Work' }, - '/cases/expedition': { component: 'PageCaseStudyExpedition', title: 'Expedition | Work' }, - '/cases/timify': { component: 'PageCaseStudyTimify', title: 'Timify | Work' }, - '/cases/trainline': { component: 'PageCaseStudyTrainline', title: 'Trainline | Work' }, - '/contact': { component: 'PageContact', title: 'Contact' }, - '/expertise/ember': { component: 'PageEmberExpertise', title: 'Europe’s leading Ember experts' }, - '/expertise/elixir-phoenix': { component: 'PageElixirExpertise', title: 'Elixir & Phoenix' }, + '/cases/ddwrt': { component: 'PageCaseDdWrt' }, + '/cases/expedition': { component: 'PageCaseStudyExpedition' }, + '/cases/timify': { component: 'PageCaseStudyTimify' }, + '/cases/trainline': { component: 'PageCaseStudyTrainline' }, + '/contact': { component: 'PageContact' }, + '/expertise/ember': { component: 'PageEmberExpertise' }, + '/expertise/elixir-phoenix': { component: 'PageElixirExpertise' }, '/imprint': { component: 'PageLegalImprint', - title: 'Imprint', bundle: { asset: '/legal.js', module: '__legal__' }, }, '/playbook': { component: 'PagePlaybook', - title: 'Playbook', bundle: { asset: '/playbook.js', module: '__playbook__' }, }, '/privacy': { component: 'PageLegalPrivacy', - title: 'Privacy Policy', bundle: { asset: '/legal.js', module: '__legal__' }, }, - '/services': { component: 'PageServices', title: 'Services' }, + '/services': { component: 'PageServices' }, '/services/full-stack-engineering': { component: 'PageFullStackEngineering', - title: 'Full Stack Engineering | Services', }, - '/services/team-augmentation': { component: 'PageTeamAugmentation', title: 'Team Augmentation | Services' }, - '/services/tutoring': { component: 'PageTutoring', title: 'Tutoring | Services' }, - '/talks': { component: 'PageTalks', title: 'Talks', bundle: { asset: '/talks.js', module: '__talks__' } }, - '/why-simplabs': { component: 'PageWhySimplabs', title: 'Why simplabs' }, - '/work': { component: 'PageWork', title: 'Work' }, - '/webinars/modern-web': { component: 'PageLandingPwaWebinar', title: 'Webinar: Modern Web Applications' }, + '/services/team-augmentation': { component: 'PageTeamAugmentation' }, + '/services/tutoring': { component: 'PageTutoring' }, + '/talks': { component: 'PageTalks', bundle: { asset: '/talks.js', module: '__talks__' } }, + '/why-simplabs': { component: 'PageWhySimplabs' }, + '/work': { component: 'PageWork' }, + '/webinars/modern-web': { component: 'PageLandingPwaWebinar' }, }; return routes; diff --git a/lib/generate-blog/lib/templates/author/template.hbs b/lib/generate-blog/lib/templates/author/template.hbs index e4dbf1993e..1e6ef49814 100644 --- a/lib/generate-blog/lib/templates/author/template.hbs +++ b/lib/generate-blog/lib/templates/author/template.hbs @@ -1,5 +1,5 @@
-
+
photo of {{name}} diff --git a/lib/generate-blog/lib/templates/post/template.hbs b/lib/generate-blog/lib/templates/post/template.hbs index d9611a400f..b073fe3594 100644 --- a/lib/generate-blog/lib/templates/post/template.hbs +++ b/lib/generate-blog/lib/templates/post/template.hbs @@ -64,7 +64,7 @@ "@context": "http://schema.org" } -
+
-
+
diff --git a/lib/generate-calendar/lib/templates/page/template.hbs b/lib/generate-calendar/lib/templates/page/template.hbs index 6bf02ccc49..ba710b3b95 100644 --- a/lib/generate-calendar/lib/templates/page/template.hbs +++ b/lib/generate-calendar/lib/templates/page/template.hbs @@ -1,5 +1,5 @@
-
+

Find out about upcoming events, conferences and meetups we will be attending or organizing.

diff --git a/lib/generate-talks-archive/lib/templates/page/template.hbs b/lib/generate-talks-archive/lib/templates/page/template.hbs index f64354233f..246fa4b067 100644 --- a/lib/generate-talks-archive/lib/templates/page/template.hbs +++ b/lib/generate-talks-archive/lib/templates/page/template.hbs @@ -1,5 +1,5 @@
-
+

We strongly believe in the value of sharing our expertise and experience with others. Here are a collection of talks that members of our team have given at various conferences all over the world in the past years.

diff --git a/src/index.ts b/src/index.ts index 1540564ba5..7c4ee2b1a3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,15 @@ function register(registry, key: string, object: any) { } } +function readRoutesMap() { + let script: HTMLElement | null = document.querySelector('[data-shoebox-routes]'); + if (script) { + return JSON.parse(script.innerText); + } else { + return {}; + } +} + app.registerInitializer({ initialize(registry) { function registerBundle(module) { @@ -37,6 +46,25 @@ app.registerInitializer({ public registerBundle(module) { registerBundle(module); } + + // tslint:disable-next-line:max-classes-per-file + class AppState { + + public static create() { + return new AppState(); + } + + public isSSR: boolean; + public route: string; + public origin: string; + public routesMap: IRoutesMap; + + constructor() { + this.isSSR = false; + this.route = window.location.pathname; + this.origin = window.location.origin; + this.routesMap = readRoutesMap(); + } } document.querySelectorAll('[data-shoebox]').forEach((shoebox: HTMLElement) => { @@ -65,6 +93,16 @@ app.registerInitializer({ 'headTags', `utils:/${app.appName}/head-tags/main`, ); + + registry.register( + `app-state:/${app.appName}/main/main`, + AppState + ); + registry.registerInjection( + `component`, + 'appState', + `app-state:/${app.appName}/main/main` + ); } }); diff --git a/src/ui/components/HeadTag/component.ts b/src/ui/components/HeadTag/component.ts index 94175edb2c..f87c6f1a94 100644 --- a/src/ui/components/HeadTag/component.ts +++ b/src/ui/components/HeadTag/component.ts @@ -1,10 +1,19 @@ import Component from '@glimmer/component'; export default class HeadTag extends Component { + private appState: IAppState; + constructor(options) { super(options); - this.setMetaTags(); + if (this.appState.isSSR) { + this.setMetaTags(); + } else { + // when changing routes, the willDestroy of a component previously in the DOM + // will be called *after* the constructor of one rendered *after* the route + // change, so we have to delay writing the tag… + window.setTimeout(() => this.setMetaTags()); + } } public willDestroy() { diff --git a/src/ui/components/Header/component.ts b/src/ui/components/Header/component.ts new file mode 100644 index 0000000000..030cd80e80 --- /dev/null +++ b/src/ui/components/Header/component.ts @@ -0,0 +1,14 @@ +import Component from '@glimmer/component'; + +export default class Header extends Component { + constructor(options) { + super(options); + + let documentTitle = this.args.documentTitle === undefined ? this.args.title : this.args.documentTitle; + this.documentTitle = formatformatDocumentTitle(documentTitle); + } +} + +function formatformatDocumentTitle(title) { + return `${title ? `${title} | ` : ''}simplabs`; +} diff --git a/src/ui/components/Header/template.hbs b/src/ui/components/Header/template.hbs index 6d4beadceb..e03875adac 100644 --- a/src/ui/components/Header/template.hbs +++ b/src/ui/components/Header/template.hbs @@ -1,4 +1,6 @@
+ +
- {{#if @title}} + {{#if @headline}}

{{#if @label}} {{@label}}: {{/if}} - {{@title}} + {{@headline}}

{{/if}} {{yield}} diff --git a/src/ui/components/Page404/template.hbs b/src/ui/components/Page404/template.hbs index 3766108b0b..525ecf10f2 100644 --- a/src/ui/components/Page404/template.hbs +++ b/src/ui/components/Page404/template.hbs @@ -1,5 +1,5 @@
-
+

We can't find the page you are looking for. diff --git a/src/ui/components/PageCaseDdWrt/template.hbs b/src/ui/components/PageCaseDdWrt/template.hbs index 126ba4477b..346b8fc1ad 100644 --- a/src/ui/components/PageCaseDdWrt/template.hbs +++ b/src/ui/components/PageCaseDdWrt/template.hbs @@ -1,5 +1,12 @@
-
+
+

DD-WRT is a Linux based firmware for wireless routers. Originally designed for the Linksys WRT54G series, it now runs on a wide variety of models and is installed on millions of devices worldwide. diff --git a/src/ui/components/PageCaseStudyExpedition/template.hbs b/src/ui/components/PageCaseStudyExpedition/template.hbs index e82363b055..eae3abb9f8 100644 --- a/src/ui/components/PageCaseStudyExpedition/template.hbs +++ b/src/ui/components/PageCaseStudyExpedition/template.hbs @@ -1,5 +1,12 @@

-
+
+

Expedition approached simplabs when they needed help laying the foundation for advanced features in their Elixir and Phoenix based API as well as making sure their Ember.js based client was following best practices. diff --git a/src/ui/components/PageCaseStudyTimify/template.hbs b/src/ui/components/PageCaseStudyTimify/template.hbs index 295a142422..224429453a 100644 --- a/src/ui/components/PageCaseStudyTimify/template.hbs +++ b/src/ui/components/PageCaseStudyTimify/template.hbs @@ -1,5 +1,12 @@

-
+
+

Timify is an online appointment scheduling service that connects service providers with clients. diff --git a/src/ui/components/PageCaseStudyTrainline/template.hbs b/src/ui/components/PageCaseStudyTrainline/template.hbs index 8c54c7ca97..bfc2763989 100644 --- a/src/ui/components/PageCaseStudyTrainline/template.hbs +++ b/src/ui/components/PageCaseStudyTrainline/template.hbs @@ -1,5 +1,12 @@

-
+
+

simplabs worked closely with Trainline’s in-house engineering team and built a mobile web app to accompany the existing desktop app. A dedicated site for mobile devices allowed Trainline to better serve customers on the go and leverage the full market potential. diff --git a/src/ui/components/PageContact/template.hbs b/src/ui/components/PageContact/template.hbs index 9583e789e5..e329619038 100644 --- a/src/ui/components/PageContact/template.hbs +++ b/src/ui/components/PageContact/template.hbs @@ -1,5 +1,5 @@

-
+

Whatever you're planning, we'd be excited to hear more. We can turn your ideas into successful digital products, improve existing ones or increase your team's effectiveness. diff --git a/src/ui/components/PageElixirExpertise/template.hbs b/src/ui/components/PageElixirExpertise/template.hbs index cb59e9c7e3..d3c560cfd0 100644 --- a/src/ui/components/PageElixirExpertise/template.hbs +++ b/src/ui/components/PageElixirExpertise/template.hbs @@ -1,5 +1,5 @@

-
+

Elixir combines the expressiveness and simplicity of Ruby with the performance and stability of the Erlang VM. Phoenix builds on the concepts first introduced by Ruby on Rails, combining them with a number of architectural improvements to take the architecture into the future.

diff --git a/src/ui/components/PageEmberExpertise/template.hbs b/src/ui/components/PageEmberExpertise/template.hbs index 8abaed5732..8dcee14493 100644 --- a/src/ui/components/PageEmberExpertise/template.hbs +++ b/src/ui/components/PageEmberExpertise/template.hbs @@ -1,5 +1,5 @@
-
+

simplabs has unique expertise and insights into Ember.js with a good part of our engineering team being on the Ember.js core team and maintaining widely adopted add-ons.

diff --git a/src/ui/components/PageFullStackEngineering/template.hbs b/src/ui/components/PageFullStackEngineering/template.hbs index d65ad00b7d..491acf28c3 100644 --- a/src/ui/components/PageFullStackEngineering/template.hbs +++ b/src/ui/components/PageFullStackEngineering/template.hbs @@ -1,5 +1,12 @@
-
+
+

Ambitious software solutions – From Idea to Release diff --git a/src/ui/components/PageHomepage/template.hbs b/src/ui/components/PageHomepage/template.hbs index e452c23911..694c2a0323 100644 --- a/src/ui/components/PageHomepage/template.hbs +++ b/src/ui/components/PageHomepage/template.hbs @@ -1,5 +1,5 @@
-
+

We deliver ambitious digital products on the web and mobile for our clients to rely on. Our expert team manages projects from idea to release, covering strategy, design and engineering.

diff --git a/src/ui/components/PageLandingPwaWebinar/template.hbs b/src/ui/components/PageLandingPwaWebinar/template.hbs index d11945238e..b8b20d3cd4 100644 --- a/src/ui/components/PageLandingPwaWebinar/template.hbs +++ b/src/ui/components/PageLandingPwaWebinar/template.hbs @@ -1,5 +1,8 @@
-
+

Want to know how forward looking marketing departments are taking advantage of modern web apps to capture new demand with incredible online experiences?

diff --git a/src/ui/components/PageLegalImprint/template.hbs b/src/ui/components/PageLegalImprint/template.hbs index 51f416f9a4..a68ac6d5b7 100644 --- a/src/ui/components/PageLegalImprint/template.hbs +++ b/src/ui/components/PageLegalImprint/template.hbs @@ -1,5 +1,5 @@
-
+

Publisher information according to §6 Teledienstgesetz (TDG) diff --git a/src/ui/components/PageLegalPrivacy/template.hbs b/src/ui/components/PageLegalPrivacy/template.hbs index 33af234367..1795806018 100644 --- a/src/ui/components/PageLegalPrivacy/template.hbs +++ b/src/ui/components/PageLegalPrivacy/template.hbs @@ -1,5 +1,5 @@
-
+

We are very delighted that you have shown interest in our enterprise. Data protection is of a particularly high priority for the management of the simplabs GmbH. The use of the Internet pages of the simplabs GmbH is possible without any indication of personal data; however, if a data subject wants to use special enterprise services via our website, processing of personal data could become necessary. If the processing of personal data is necessary and there is no statutory basis for such processing, we generally obtain consent from the data subject. diff --git a/src/ui/components/PagePlaybook/template.hbs b/src/ui/components/PagePlaybook/template.hbs index 3d536dfbc0..25b98764b5 100644 --- a/src/ui/components/PagePlaybook/template.hbs +++ b/src/ui/components/PagePlaybook/template.hbs @@ -1,5 +1,5 @@

-
+

We maintain a lean process that supports the team rather than stand in its way. It ensures the right tasks are being worked on at the right time and provides a reasonable level of short term predictability. At the same time it remains flexible enough to adapt to unexpected events. Our process does not depend on specific tools and works for projects and teams in all environments.

diff --git a/src/ui/components/PageServices/template.hbs b/src/ui/components/PageServices/template.hbs index 8184285a92..02b8f0c937 100644 --- a/src/ui/components/PageServices/template.hbs +++ b/src/ui/components/PageServices/template.hbs @@ -2,7 +2,8 @@
diff --git a/src/ui/components/PageTeamAugmentation/template.hbs b/src/ui/components/PageTeamAugmentation/template.hbs index 7da0210b36..bb2fcf1153 100644 --- a/src/ui/components/PageTeamAugmentation/template.hbs +++ b/src/ui/components/PageTeamAugmentation/template.hbs @@ -1,5 +1,12 @@
-
+
+

Bring in the experts when you need them diff --git a/src/ui/components/PageTutoring/template.hbs b/src/ui/components/PageTutoring/template.hbs index 35971c74d2..02682f14e3 100644 --- a/src/ui/components/PageTutoring/template.hbs +++ b/src/ui/components/PageTutoring/template.hbs @@ -1,5 +1,12 @@
-
+
+

Raising confidence for increased productivity and quality diff --git a/src/ui/components/PageWhySimplabs/template.hbs b/src/ui/components/PageWhySimplabs/template.hbs index d0d38438ed..28841f0f1f 100644 --- a/src/ui/components/PageWhySimplabs/template.hbs +++ b/src/ui/components/PageWhySimplabs/template.hbs @@ -1,5 +1,10 @@
-
+

We build cutting edge web apps for clients around the world. Our team of experts delivers everything from ideation to design and engineering, guiding our clients along the way.

diff --git a/src/ui/components/PageWork/template.hbs b/src/ui/components/PageWork/template.hbs index 3cf8cda48f..47e1ea33f8 100644 --- a/src/ui/components/PageWork/template.hbs +++ b/src/ui/components/PageWork/template.hbs @@ -2,7 +2,8 @@
diff --git a/src/ui/components/Simplabs/component.ts b/src/ui/components/Simplabs/component.ts index 71e55676cc..62b428c93c 100644 --- a/src/ui/components/Simplabs/component.ts +++ b/src/ui/components/Simplabs/component.ts @@ -21,7 +21,6 @@ declare global { interface IRoutesMap { [route: string]: { component: string; - title: string; bundle: any; parentBundle: any; }; @@ -53,13 +52,6 @@ export default class Simplabs extends Component { constructor(options) { super(options); - this.appState = this.appState || { - isSSR: false, - origin: window.location.origin, - route: window.location.pathname, - routesMap: this._readRoutesMap(), - }; - this._setupRouting(); this._bindInternalLinks(); this._restoreActiveComponentState(); @@ -86,10 +78,9 @@ export default class Simplabs extends Component { this.router = new Navigo(this.appState.origin); Object.keys(this.appState.routesMap).forEach(path => { - let { component, title = '', bundle, parentBundle } = (this.appState.routesMap as IRoutesMap)[path]; + let { component, bundle, parentBundle } = (this.appState.routesMap as IRoutesMap)[path]; let options: INavigoHooks = { after: () => { - this._setPageTitle(title); this._setCanonicalUrl(path); if (!this.appState.isSSR) { window.scrollTo(0, 0); @@ -206,16 +197,6 @@ export default class Simplabs extends Component { this.document.body.appendChild(script); } - private _setPageTitle(title) { - this.headTags.write('title', {}, {}, formatPageTitle(title)); - this.headTags.write('meta', { - name: 'twitter:title', - property: 'og:title', - }, { - content: title, - }); - } - private _setCanonicalUrl(path) { this.headTags.write('meta', { property: 'og:url', @@ -239,15 +220,6 @@ export default class Simplabs extends Component { } } } - - private _readRoutesMap() { - let script: HTMLElement | null = document.querySelector('[data-shoebox-routes]'); - if (script) { - return JSON.parse(script.innerText); - } else { - return {}; - } - } } function trackPageView(route) { @@ -257,10 +229,6 @@ function trackPageView(route) { } } -function formatPageTitle(title) { - return `${title ? `${title} | ` : ''}simplabs`; -} - function findLinkParent(target) { let element = target; while (element && element !== document) { diff --git a/src/utils/test-helpers/setup-rendering-test.ts b/src/utils/test-helpers/setup-rendering-test.ts index 0f47564933..7e3e5b2be8 100644 --- a/src/utils/test-helpers/setup-rendering-test.ts +++ b/src/utils/test-helpers/setup-rendering-test.ts @@ -1,6 +1,27 @@ import { classnames } from '@css-blocks/glimmer/dist/cjs/src/helpers/classnames'; import { concat } from '@css-blocks/glimmer/dist/cjs/src/helpers/concat'; import { setupRenderingTest as originalSetupRenderingTest } from '@glimmer/test-helpers'; +import HeadTags from '../head-tags'; +import hash from '../helpers/hash'; + +class TestAppState { + + public static create() { + return new TestAppState(); + } + + public isSSR: boolean; + public route: string; + public origin: string; + public routesMap: IRoutesMap; + + constructor() { + this.isSSR = false; + this.route = window.location.pathname; + this.origin = window.location.origin; + this.routesMap = {}; + } +} export const setupRenderingTest = function(hooks) { originalSetupRenderingTest(hooks); @@ -15,6 +36,30 @@ export const setupRenderingTest = function(hooks) { registry._resolver.registry._entries[ `helper:/${rootName}/components/-css-blocks-concat` ] = concat; + + registry._resolver.registry._entries[ + `helper:/${rootName}/components/hash` + ] = hash; + + registry.register( + `app-state:/${rootName}/main/main`, + TestAppState + ); + registry.registerInjection( + `component`, + 'appState', + `app-state:/${rootName}/main/main` + ); + + registry.register( + `utils:/${rootName}/head-tags/main`, + HeadTags + ); + registry.registerInjection( + `component`, + 'headTags', + `utils:/${rootName}/head-tags/main`, + ); } }); }); diff --git a/ssr/ssr-application.ts b/ssr/ssr-application.ts index 954b5c0cbb..2f5d8e1067 100644 --- a/ssr/ssr-application.ts +++ b/ssr/ssr-application.ts @@ -85,7 +85,7 @@ export default class SSRApplication extends Application { AppState ); registry.registerInjection( - `component:/${rootName}/components/Simplabs`, + `component`, 'appState', `app-state:/${rootName}/main/main` );