From d5ac23e3dbced1a36297febe507fc164aa32428d Mon Sep 17 00:00:00 2001 From: Khaled Garbaya Date: Wed, 8 Nov 2017 10:41:12 +0100 Subject: [PATCH 01/13] test: Fix integration test --- test/integration/courses.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/courses.test.js b/test/integration/courses.test.js index 2f323e6..060c3c2 100644 --- a/test/integration/courses.test.js +++ b/test/integration/courses.test.js @@ -22,7 +22,7 @@ describe('courses', () => { }) test('it should render a lesson', () => { - return request(app).get('/courses/hello-world/lessons/architecture-basics') + return request(app).get('/courses/hello-world/lessons/architecture') .expect(200) .then((response) => { expect(response.text.match(/class="lesson__title"/)).toBeTruthy() From 1c36ea6c8519bf3a8605c005245718fa3e80c6fb Mon Sep 17 00:00:00 2001 From: Khaled Garbaya Date: Wed, 8 Nov 2017 11:05:20 +0100 Subject: [PATCH 02/13] fix: Fix Editorial feature --- views/mixins/_editorialFeatures.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/mixins/_editorialFeatures.pug b/views/mixins/_editorialFeatures.pug index ed6a5d2..2955f92 100644 --- a/views/mixins/_editorialFeatures.pug +++ b/views/mixins/_editorialFeatures.pug @@ -8,7 +8,7 @@ mixin editorialFeatures(entry) +entryState(entry) .editorial-features__item a.editorial-features__text( - href=`https://app.contentful.com/spaces/${settings.space}/entries/${entry.sys.id}` + href=`https://app.contentful.com/spaces/${settings.spaceId}/entries/${entry.sys.id}` target='_blank' rel='noopener' ) Edit in the web app From 0858b832ad5bb79ced4a83f33ef4cb53c649f9df Mon Sep 17 00:00:00 2001 From: David Litvak Date: Wed, 8 Nov 2017 11:57:13 +0100 Subject: [PATCH 03/13] chore(localization): move locales to i18n folder --- i18n/i18n.js | 2 +- i18n/locales/de-DE.json | 77 +++++++++++++++++++++++++++++++++++++++++ i18n/locales/en-US.json | 77 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 i18n/locales/de-DE.json create mode 100644 i18n/locales/en-US.json diff --git a/i18n/i18n.js b/i18n/i18n.js index 2454e73..5542fe2 100644 --- a/i18n/i18n.js +++ b/i18n/i18n.js @@ -10,7 +10,7 @@ module.exports.initializeTranslations = () => { translations = {} - const localesPath = path.join(__dirname, '..', 'public', 'locales') + const localesPath = path.join(__dirname, 'locales') try { const files = fs.readdirSync(localesPath) diff --git a/i18n/locales/de-DE.json b/i18n/locales/de-DE.json new file mode 100644 index 0000000..9875605 --- /dev/null +++ b/i18n/locales/de-DE.json @@ -0,0 +1,77 @@ +{ + "defaultTitle": "Die Beispielanwendung", + "whatIsThisApp": "Was ist die Beispielanwendung?", + "metaDescription": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.", + "metaTwitterCard": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.", + "metaImageAlt": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.", + "metaImageDescription": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.", + "viewOnGithub": "Auf GitHub ansehen", + "apiSwitcherHelp": "Ansehen des veröffentlichten und unveröffentlichten Inhalts durch Wechsel von Delivery und Preview APIs.", + "apiLabelHelpcda": "(veröffentlichter Inhalt)", + "apiLabelHelpcpa": "(unveröffentlichter Inhalt)", + "locale": "Sprache", + "localeQuestion": "Sie arbeiten mit verschiedenen Sprachen? Dann können Sie die Sprache für Anfragen an die Content Delivery API definieren.", + "settingsLabel": "Einstellungen", + "logoAlt": "Die Beispielanwendung für Contentful", + "homeLabel": "Home", + "coursesLabel": "Kurse", + "footerDisclaimer": "Powered by Contentful. Diese Website und deren Materialien existieren nur für Demonstrationszwecken. Sie können diese benutzen, um den Inhalt ihres Contentful Kontos anzusehen.", + "imprintLabel": "Impressum", + "contactUsLabel": "Uns Kontaktieren", + "modalTitle": "Ein referenzierbares Beispiel für Entwickler, die Contentful benutzen.", + "modalIntro": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen. Sie stellt eine Plattform fürs online Lernen dar, die mithilfe von Contentful gebaut wurde. (sehr meta!)", + "modalCodeIntro": "Wenn Sie es bevorzugen, sich die Hände schmutzig zu machen, sehen sie sich die Anwendung hier an", + "modalCTALabel": "Gut, verstanden.", + "editorialFeaturesHint": "Bearbeiten Sie diesen Eintrag in unserer Web App. Sie müssen sich eingelogged haben und Zugang zum Space haben, um diese Funktion nutzen zu können.", + "draftLabel": "Entwurf", + "pendingChangesLabel": "Änderungen vorbehalten", + "lessonModuleErrorTitle": "⚠️ Ungültiges Lektionsmodul", + "lessonModuleErrorBody": "Konnte den Typ nicht erkennen: ", + "nextLessonLabel": "Nächste Lektion ansehen", + "imageErrorTitle": "⚠️ Bild fehlt", + "viewCourseLabel": "Kurs ansehen", + "categoriesWelcomeLabel": "Willkommen zur folgenden Kategorie: ", + "sitemapWelcomeLabel": "Willkommen zur folgenden Kategorie: ", + "tableOfContentsLabel": "Inhalt", + "courseOverviewLabel": "Kurs Übersicht", + "overviewLabel": "Übersicht", + "durationLabel": "Dauer", + "minutesLabel": "min", + "skillLevelLabel": "Komplexität", + "startCourseLabel": "Kurs beginnen", + "categoriesLabel": "Kategorien", + "allCoursesLabel": "Alle Kurse", + "somethingWentWrongLabel": "Hmm, etwas ging schief.", + "tryLabel": "Versuchen Sie", + "contentModelChangedErrorLabel": "Überprüfen Sie, ob das Content Model verändert wurde.", + "draftOrPublishedErrorLabel": "Überprüfen Sie, ob es nicht veröffentlichte Änderungen gibt.", + "localeContentErrorLabel": "Überprüfen Sie, ob es Inhalt für diese Sprache gibt.", + "verifyCredentialsErrorLabel": "Überprüfen Sie, ob die Zugangsdaten richtig und nicht abgelaufen sind.", + "stackTraceErrorLabel": "Schauen Sie sich den folgenden Stack Trace an", + "errorLabel": "Fehler", + "stackTraceLabel": "Stack Trace", + "companyLabel": "Firma", + "officeLabel": "Büro in Berlin", + "germanyLabel": "Deutschland", + "registrationCourtLabel": "Amtsgericht", + "managingDirectorLabel": "Verwalter", + "vatNumberLabel": "Steuernummer", + "settingsIntroLabel": "Um Inhalt von unseren APIs zu bekommen, müssen Anwendungen von Kunden sich authentifizieren, sowohl mit der Space ID als auch dem Access Token.", + "changesSavedLabel": "Änderungen erfolgreich gespeichert!", + "errorOcurredTitleLabel": "Fehler aufgetreten", + "errorOcurredMessageLabel": "Einige Fehler sind aufgetreten. Bitte schauen Sie sich die Fehlermeldungen neben den Feldern an.", + "connectedToSpaceLabel": "Mit einem Space verbinden.", + "spaceIdLabel": "Space ID", + "spaceIdHelpText": "Die Space Id ist eine eindeutige Identifizierung für Ihren Space.", + "accessTokenLabel": "Access Token", + "contentDeliveryApiHelpText": "Schauen Sie sich veröffentlichten Inhalt mit dieser API an.", + "contentPreviewApiHelpText": "Schauen Sie sich unveröffentlichten Inhalt mit dieser API an. (z.B. Inhalt im Zustand “Entwurf”).", + "enableEditorialFeaturesLabel": "Editoriale Funktionen aktivieren.", + "enableEditorialFeaturesHelpText": "Aktivieren, um Bearbeitung und weitere kontextabhängige Helferlein zu aktivieren. Damit dies funktioniert, müssen sie Zugang zu dem Space haben.", + "saveSettingsButtonLabel": "Einstellungen Speichern", + "fieldIsRequiredLabel": "Diese Feld ist notwendig.", + "deliveryKeyInvalidLabel": "Ihr Delivery API Zugangsschlüssel ist ungültig.", + "spaceOrTokenInvalid": "Dieser Space existiert nicht, oder Ihr Access Token kommt nicht von diesem Space.", + "somethingWentWrongLabel": "Irgendetwas lief falsch.", + "previewKeyInvalidLabel": "Ihr Preview API Zugangsschlüssel ist ungültig." +} diff --git a/i18n/locales/en-US.json b/i18n/locales/en-US.json new file mode 100644 index 0000000..1932523 --- /dev/null +++ b/i18n/locales/en-US.json @@ -0,0 +1,77 @@ +{ + "defaultTitle": "The Example App", + "whatIsThisApp": "What is this example app?", + "metaDescription": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.", + "metaTwitterCard": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.", + "metaImageAlt": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.", + "metaImageDescription": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.", + "viewOnGithub": "View on Github", + "apiSwitcherHelp": "View the published or draft content by simply switching between the Deliver and Preview APIs.", + "apiLabelHelpcda": "(published content)", + "apiLabelHelpcpa": "(draft content)", + "locale": "Locale", + "localeQuestion": "Working with multiple languages? You can query the Content Delivery API for a specific locale.", + "settingsLabel": "Settings", + "logoAlt": "Contentful Example App", + "homeLabel": "Home", + "coursesLabel": "Courses", + "footerDisclaimer": "Powered by Contentful. This website and the materials found on it are for demo purposes. You can use this to preview the content created on your Contentful account.", + "imprintLabel": "Imprint", + "contactUsLabel": "Contact us", + "modalTitle": "A referenceable example for developers using Contentful", + "modalIntro": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful. This app is an online learning platform which teaches you how Contentful was used to build this app (so meta)!", + "modalCodeIntro": "If you prefer to start by getting your hands dirty with code, check out this app on", + "modalCTALabel": "Ok, got it.", + "editorialFeaturesHint": "Edit this entry in our web app. You have to be logged in and have access to the connected space to use this feature.", + "draftLabel": "draft", + "pendingChangesLabel": "pending changes", + "lessonModuleErrorTitle": "⚠️ Invalid lesson module", + "lessonModuleErrorBody": "Could not determine type of", + "nextLessonLabel": "View next lesson", + "imageErrorTitle": "⚠️ Image missing", + "viewCourseLabel": "view course", + "categoriesWelcomeLabel": "Welcome to", + "sitemapWelcomeLabel": "Welcome to", + "tableOfContentsLabel": "Table of contents", + "courseOverviewLabel": "Cource overview", + "overviewLabel": "Overview", + "durationLabel": "Duration", + "minutesLabel": "min", + "skillLevelLabel": "Skill level", + "startCourseLabel": "Start course", + "categoriesLabel": "Categories", + "allCoursesLabel": "All courses", + "somethingWentWrongLabel": "Oops Something went wrong", + "tryLabel": "Try", + "contentModelChangedErrorLabel": "Check if the content model has changed", + "draftOrPublishedErrorLabel": "Check the selection has content in draft or published state (for Preview or Delivery)", + "localeContentErrorLabel": "Check if there's any content for this locale", + "verifyCredentialsErrorLabel": "Verify credentials are correct and up to date", + "stackTraceErrorLabel": "Check the stack trace below", + "errorLabel": "Error", + "stackTraceLabel": "Stack trace", + "companyLabel": "Company", + "officeLabel": "Office Berlin", + "germanyLabel": "Germany", + "registrationCourtLabel": "Registration Court", + "managingDirectorLabel": "Managing Director", + "vatNumberLabel": "VAT Number", + "settingsIntroLabel": "To query and get content using the APIs, client applications need to authenticate with both the Space ID and an access token.", + "changesSavedLabel": "Changes saved successfully!", + "errorOcurredTitleLabel": "Error occurred", + "errorOcurredMessageLabel": "Some errors occurred. Please check the error messages next to the fields.", + "connectedToSpaceLabel": "Connected to space", + "spaceIdLabel": "Space ID", + "spaceIdHelpText": "The Space ID is a unique identifier for your space.", + "accessTokenLabel": "access token", + "contentDeliveryApiHelpText": "View published content using this API.", + "contentPreviewApiHelpText": "Preview unpublished content using this API (i.e. content with “Draft” status).", + "enableEditorialFeaturesLabel": "Enable editorial features", + "enableEditorialFeaturesHelpText": "Enable to display an edit link and other contextual helpers for authors. You need to have access to the connected space to make this work.", + "saveSettingsButtonLabel": "Save settings", + "fieldIsRequiredLabel": "This field is required", + "deliveryKeyInvalidLabel": "Your Delivery API key is invalid.", + "spaceOrTokenInvalid": "This space does not exist or your access token is not associated with your space.", + "somethingWentWrongLabel": "Something went wrong", + "previewKeyInvalidLabel": "Your Preview API key is invalid." +} From 1c1a35226947e1bb63c03eae47516035b99358c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benedikt=20R=C3=B6tsch?= Date: Wed, 8 Nov 2017 13:10:05 +0100 Subject: [PATCH 04/13] feat(modules): add image caption and shadow --- public/stylesheets/style.css | 2 +- views/mixins/_lessonModuleImage.pug | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 1c3e9a7..4adbb83 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -1 +1 @@ -/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;white-space:normal}progress{display:inline-block;vertical-align:baseline}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none}@font-face{font-family:roboto;src:url(/fonts/roboto-regular-webfont.woff2) format("woff2"),url(/fonts/roboto-regular-webfont.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:roboto;src:url(/fonts/roboto-italic-webfont.woff2) format("woff2"),url(/fonts/roboto-italic-webfont.woff) format("woff");font-weight:400;font-style:italic}@font-face{font-family:roboto;src:url(/fonts/roboto-bold-webfont.woff2) format("woff2"),url(/fonts/roboto-bold-webfont.woff) format("woff");font-weight:700;font-style:normal}@font-face{font-family:roboto;src:url(/fonts/roboto-bolditalic-webfont.woff2) format("woff2"),url(/fonts/roboto-bolditalic-webfont.woff) format("woff");font-weight:700;font-style:italic}@font-face{font-family:robotomedium;src:url(/fonts/roboto-medium-webfont.woff2) format("woff2"),url(/fonts/roboto-medium-webfont.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:robotomedium;src:url(/fonts/roboto-mediumitalic-webfont.woff2) format("woff2"),url(/fonts/roboto-mediumitalic-webfont.woff) format("woff");font-weight:400;font-style:italic}.cta,button,fieldset,input,legend,optgroup,option,select,textarea{-webkit-box-sizing:border-box;box-sizing:border-box;outline:none;font-family:Raleway,sans-serif;font-size:16px;color:#536171;vertical-align:top;display:block;margin:32px 0;text-align:left}datalist{font-family:Raleway,sans-serif;font-size:16px}label{display:block;margin:32px 0 0;text-align:left;font-weight:700;font-size:.875em}label+input{margin-top:8px}input[type=checkbox],input[type=radio]{margin:0 0 4px!important}input[type=checkbox]+label,input[type=radio]+label{margin:0 0 0 8px;display:inline-block}input[list],input[type=color],input[type=date],input[type=datetime-local],input[type=email],input[type=file],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],textarea{width:100%;max-width:100%;padding:8px;background-color:#fff;border-radius:5px;border:1px solid #d3dce0}input[list],input[type=color],input[type=date],input[type=datetime-local],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{height:40px;line-height:40px;-webkit-appearance:none}textarea{-webkit-appearance:none;overflow:auto}input[type=range]{height:40px;width:100%;max-width:100%}input[type=file]{min-height:40px}input[type=search]{height:40px;-webkit-appearance:none}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=checkbox],input[type=radio]{display:inline-block;vertical-align:middle}fieldset{padding:0;border:0}legend{padding:0;font-weight:inherit}.cta,button,input[type=button],input[type=image],input[type=reset],input[type=submit]{display:inline-block;height:40px;min-width:200px;background-color:#5c9fef;padding:0 2em;cursor:pointer;line-height:40px;color:#fff;font-weight:400;-webkit-appearance:none;-moz-appearance:none;border:none;border-radius:3px;text-align:center}input[type=image]{text-align:center;padding:8px}button[disabled],input[disabled],option[disabled],select[disabled],textarea[disabled]{cursor:not-allowed}input:focus,option:focus,select:focus,textarea:focus{border-color:#5c9fef;color:#536171}.cta:focus,button:focus{background-color:#3c80cf;color:#fff}input[type=checkbox]:focus,input[type=radio]:focus{outline:2px dashed #d3dce0;border-radius:4px}.cta:focus,.cta:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background-color:#3c80cf;color:#fff}.form-item+.form-item{margin-top:32px}.form-item input{margin:0 0 8px}.form-item__help-text{font-size:.875em;color:#8091a5}.form-item__error-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-item__error-icon{width:12px;height:12px;padding-right:.3em;color:#cd3f39}.form-item__error-message{font-size:.875em;color:#cd3f39}.status-block{display:-webkit-box;display:-ms-flexbox;display:flex;margin:32px 0;padding:16px;border-radius:4px;font-size:.875em}.status-block--success{background:#f4fffb}.status-block--error{background:#fbe3e2}.status-block--info{background:#e8f7ff}.status-block__icon{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:24px;height:24px;margin-right:8px}.status-block__icon--success{color:#0eb87f}.status-block__icon--error{color:#cd3f39}.status-block__icon--info{color:#a9b9c0}.status-block__content{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.status-block__title{font-weight:700;color:#2a3039}.status-block__message{color:#536171}table{width:100%;border:1px solid #e5ebed;border-collapse:collapse}td,th{padding:10px;line-height:1.5}td:not(:last-of-type),th:not(:last-of-type){border-right:1px solid #e5ebed}th{background-color:#f7f9fa;border-bottom:1px solid #e5ebed;color:#2a3039;font-weight:400;font-family:robotomedium,Helvetica,sans-serif;text-align:left}tbody td{border-bottom:1px dashed #e5ebed}h1,h2,h3,h4,h5,h6{font-family:robotomedium,Helvetica,sans-serif;font-weight:400;line-height:1.31;color:#2a3039}h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,h6:first-child{margin-top:0}a{color:inherit;text-decoration:none}.form-item a,label a,p a{color:#5c9fef;text-decoration:underline}.form-item a:hover,label a:hover,p a:hover{color:#3c80cf;text-decoration:underline}p:first-child{margin-top:0}p:last-child{margin-bottom:0}blockquote{margin:0;padding:0 0 0 16px;font-style:italic;border-left:4px solid #c3cfd5}.layout-centered{max-width:980px}.layout-centered,.layout-centered-small{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;margin:0 auto;padding:0 2vw}.layout-centered-small{max-width:620px}.layout-no-sidebar,.layout-sidebar{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;max-width:980px;padding:0 16px;margin:0 auto}.layout-sidebar__sidebar-header{padding:16px 32px;border-bottom:1px solid #eee}.layout-sidebar__sidebar-title{font-family:robotomedium,Helvetica,sans-serif;font-size:1.25em;font-weight:400;margin:0}.layout-sidebar__sidebar-content{background:#f7f9fa;padding:16px}.layout-sidebar__content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.layout-sidebar__content>*{max-width:732px;width:100%}.grid-list{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.grid-list__item{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;margin-bottom:32px}.header{margin-bottom:24px}.header__upper-wrapper{background:#536171;padding:8px 0;color:#b4c3ca}.header__upper-wrapper a.active,.header__upper-wrapper a:hover{color:#fff}.header__upper{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-size:.875em}.header__upper-first,.header__upper-second{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.header__upper-first,.header__upper-link,.header__upper-second{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.header__upper-copy,.header__upper-link{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.header__upper-copy{margin:0 0 8px}.header__upper-icon{display:inline-block;width:20px;height:20px;margin-right:8px}.header__lower{-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding-top:16px;padding-bottom:8px;border-bottom:1px solid #e5ebed}.header__lower,.header__title{display:-webkit-box;display:-ms-flexbox;display:flex}.header__title{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.header__title img{display:inline-block;width:24px;height:auto;margin-right:8px}.header__logo{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.header__logo img{width:100%;height:auto;max-width:169px}.header__controls{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:distribute;justify-content:space-around;margin:0 0 8px}.header__controls,.header__controls_group{display:-webkit-box;display:-ms-flexbox;display:flex}.header__controls_group{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;position:relative;margin:0 0 16px;border:none;border-radius:4px;color:inherit}.header__controls_group:last-child{margin:0}.header__controls_label{position:relative;z-index:5;cursor:pointer}.header__controls_label:hover{color:#fff}.header__controls_label:hover:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='5' viewBox='0 0 10 5' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' d='M0 0l5 5 5-5z'/%3E%3C/svg%3E")}.header__controls_label:after{content:"";display:inline-block;width:10px;height:5px;margin-left:8px;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='5' viewBox='0 0 10 5' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23B4C3CA' d='M0 0l5 5 5-5z'/%3E%3C/svg%3E");background-size:contain;background-position:50%;background-repeat:no-repeat;vertical-align:middle}.header__controls_dropdown{position:absolute;z-index:6;-webkit-box-sizing:border-box;box-sizing:border-box;right:0;width:260px;max-width:90vw;margin:12px 0 0;opacity:0;background:#fff;border-radius:2px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1);-webkit-transition:opacity .3s ease;transition:opacity .3s ease;pointer-events:none}.header__controls_dropdown--active{opacity:1;pointer-events:all}.header__controls_dropdown--active:before{content:"";position:absolute;left:50%;top:-5px;width:10px;height:10px;background-color:inherit;-webkit-transform:translateX(-50%) rotate(45deg);transform:translateX(-50%) rotate(45deg)}.header__controls_help_text{padding:16px;color:#8091a5;line-height:1.29}.header__controls_button{-webkit-box-sizing:border-box;box-sizing:border-box;height:auto;width:100%;margin:0;padding:0 16px;border-radius:0;font-size:1em;text-align:left}.header__controls_button,.header__controls_button:hover{background:#fff;color:#263545}.header__controls_button--active,.header__controls_button--active:hover{background:#d3dce0}.footer{padding:32px 0}.footer p{margin:0}.footer__upper{-ms-flex-wrap:wrap;flex-wrap:wrap;min-height:80px;border-bottom:1px solid #e5ebed}.footer__upper,.footer__upper>*{display:-webkit-box;display:-ms-flexbox;display:flex}.footer__upper>*{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.footer__navigation{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.footer__apps{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.footer__apps a{display:inline-block;width:138px}.footer__apps a+a{margin-left:8px}.footer__apps a img{width:100%;height:auto}.footer__lower{min-height:80px}.footer__disclaimer,.footer__lower{display:-webkit-box;display:-ms-flexbox;display:flex}.footer__disclaimer{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.footer__disclaimer-logo{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:140px;height:28px;margin-right:16px}.footer__disclaimer-text{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;color:#a9b9c0;font-size:.75em;line-height:1.5}.footer__social{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0 0 0 32px}.footer__social a+a{margin-left:16px}.footer__social svg{color:#a9b9c0;width:24px;height:24px}.breadcrumb{margin-bottom:8px;font-size:.875em}.breadcrumb ul{display:-webkit-box;display:-ms-flexbox;display:flex;list-style:none;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0}.breadcrumb ul,.breadcrumb ul li{margin:0}.breadcrumb ul li:after{content:"";display:inline-block;background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5' height='8' viewBox='0 0 5 8'%3E%3Cpath fill='none' stroke='%23536171' stroke-width='1.5' d='M1 7l3-2.89L1 1'/%3E%3C/svg%3E");background-size:contain;background-position:50%;background-repeat:no-repeat;width:4px;height:8px;padding:0 8px}.breadcrumb ul li:last-child:after{display:none}.breadcrumb ul li a{display:inline-block}.breadcrumb ul li a:hover{color:#3c80cf}.editorial-features{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-line-pack:center;align-content:center;margin-bottom:16px}.editorial-features a{color:#5c9fef}.editorial-features__item{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-line-pack:center;align-content:center;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;margin-right:8px}.editorial-features__item:last-child{margin-right:0}.editorial-features__hint-wrapper{position:relative;z-index:3;margin-left:8px}.editorial-features__hint-wrapper:hover>div{opacity:1;-webkit-transition-delay:0s;transition-delay:0s}.editorial-features__hint-icon{width:24px;height:24px;cursor:help}.editorial-features__hint-message{position:absolute;z-index:4;-webkit-box-sizing:border-box;box-sizing:border-box;top:24px;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);width:260px;max-width:90vw;margin:12px 0 0;padding:16px;background:#536171;border-radius:2px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1);opacity:0;-webkit-transition:opacity .3s ease .3s;transition:opacity .3s ease .3s;pointer-events:none;font-size:.75em;line-height:1.5;color:#fff}.editorial-features__hint-message:before{content:"";position:absolute;left:50%;top:-5px;width:10px;height:10px;background-color:inherit;-webkit-transform:translateX(-50%) rotate(45deg);transform:translateX(-50%) rotate(45deg)}.main-navigation ul{display:-webkit-box;display:-ms-flexbox;display:flex;list-style:none;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:0}.main-navigation ul,.main-navigation ul li{margin:0}.main-navigation ul li+li{margin-left:8px}.main-navigation ul li a{display:block;text-transform:uppercase;font-weight:700;padding:.7em 1em;color:#8091a5;border-radius:4px}.main-navigation ul li a.active,.main-navigation ul li a:hover{background:#f7f9fa}.main-navigation ul li a.active{color:#536171}.modal{display:none}.modal--visible{display:block}.modal__overlay{position:fixed;z-index:7;left:0;right:0;top:0;bottom:0;opacity:.3;background-color:#3b3d40;cursor:pointer}.modal__wrapper{position:fixed;z-index:8;left:50%;top:20vh;-webkit-transform:translateX(-50%);transform:translateX(-50%);-webkit-box-sizing:border-box;box-sizing:border-box;width:80vw;max-width:800px;padding:32px 48px;background-color:#fff;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1);border-radius:4px}.modal__title{margin-bottom:2vh;font-size:1em;text-align:center}.modal__content{color:#8091a5;font-size:.875em}.modal__content a{color:#5c9fef;text-decoration:underline}.modal__content a:hover{color:#3c80cf}.modal__cta-wrapper{text-align:center;margin-top:32px}.modal__cta{display:inline-block;padding-bottom:.3em;border-bottom:1px solid #5c9fef;color:#5c9fef;font-size:.75em;font-weight:700;line-height:2.17;letter-spacing:2px;text-transform:uppercase}.modal__cta:hover{color:#3c80cf;border-bottom-color:#3c80cf}.modal__close-wrapper{position:absolute;z-index:9;top:16px;right:16px}.modal__close-button{display:block;width:18px;height:18px;color:#a9b9c0}.modal__close-button svg{width:100%;height:100%}.pill{display:inline-block;vertical-align:middle;padding:0 8px;background:#3c80cf;border-radius:4px;text-transform:uppercase;font-size:14px;font-size:.875rem;letter-spacing:1px;color:#fff}.pill--draft{background-color:#e6ae17}.pill--pending-changes{background-color:#3e97d6}.sidebar-menu__list{list-style:none;margin:0;padding:0}.sidebar-menu__item{margin:0 0 16px;padding:0;font-size:.9em;line-height:1.8}.sidebar-menu__link{display:block;color:#8091a5}.sidebar-menu__link.active,.sidebar-menu__link:hover{color:#536171}.sidebar-menu__link.active{font-family:robotomedium,Helvetica,sans-serif}.table-of-contents__list{list-style:none;margin:0;padding:0;padding-left:32px}.table-of-contents__item{margin:0 0 8px;padding:0;font-size:.9em;line-height:1.8}.table-of-contents__link{display:block;color:#8091a5}.table-of-contents__link.active,.table-of-contents__link:hover{color:#536171}.table-of-contents__link.active{position:relative;font-family:robotomedium,Helvetica,sans-serif}.table-of-contents__link.active:after,.table-of-contents__link.active:before{position:absolute;top:0}.table-of-contents__link.active:before{content:"";left:-48px;bottom:0;width:3px;background:#536171}.table-of-contents__link.visited{position:relative}.table-of-contents__link.visited:after,.table-of-contents__link.visited:before{position:absolute;top:0}.table-of-contents__link.visited:after{content:"";display:block;width:16px;height:16px;top:50%;left:-32px;-webkit-transform:translateY(-50%);transform:translateY(-50%);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%23536171'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath d='M0 0h20v20H0z'/%3E%3Cpath fill='%23A9B9C0' fill-rule='nonzero' d='M9.992 1.667c-4.6 0-8.325 3.733-8.325 8.333s3.725 8.333 8.325 8.333c4.608 0 8.341-3.733 8.341-8.333S14.6 1.667 9.992 1.667zm.008 15A6.665 6.665 0 0 1 3.333 10 6.665 6.665 0 0 1 10 3.333 6.665 6.665 0 0 1 16.667 10 6.665 6.665 0 0 1 10 16.667z'/%3E%3Cpath stroke='%23A9B9C0' stroke-width='1.333' d='M13.333 7.5l-5 5-1.666-1.786'/%3E%3C/g%3E%3C/svg%3E");background-size:contain;background-position:50%;background-repeat:no-repeat}body{background-color:#fff;color:#536171;font-family:roboto,Helvetica,sans-serif;font-size:16px;font-size:1rem;line-height:1.5em}.main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-height:100vh}.main__header{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.main__content{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.main__footer{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.course-card{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding:32px;border-radius:4px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1)}.course-card__categories{-webkit-box-flex:0;-ms-flex:0 0 1.5em;flex:0 0 1.5em;margin-bottom:16px;height:1.5em}.course-card__category{display:inline-block;color:#8091a5;font-size:.75em;font-family:robotomedium,Helvetica,sans-serif}.course-card__category:after{content:" • ";display:inline-block;padding:0 .5em}.course-card__category:last-child:after{display:none}.course-card__category-link{display:inline-block;letter-spacing:2px}.course-card__title{-webkit-box-flex:0;-ms-flex:0 1 12vh;flex:0 1 12vh;max-height:120px;margin:0;overflow:hidden;padding-bottom:16px;margin-bottom:16px;border-bottom:1px solid #e5ebed;font-family:robotomedium,Helvetica,sans-serif;font-weight:400;font-size:1.625em;line-height:1.38}.course-card__title a:after{content:" "}.course-card__title .pill{margin-bottom:3px}.course-card__description{-webkit-box-flex:0;-ms-flex:0 1 18vh;flex:0 1 18vh;max-height:150px;overflow:hidden;margin:0 0 32px;line-height:1.63;color:#536171}.course-card__link-wrapper{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.course-card__link{display:inline-block;padding-bottom:.3em;border-bottom:1px solid #5c9fef;text-transform:uppercase;color:#5c9fef;font-size:.75em;line-height:2.17;letter-spacing:2px}.course-card__link:hover{color:#3c80cf;border-bottom-color:#3c80cf}.course__title{margin-bottom:32px}.course__overview{font-family:robotomedium,Helvetica,sans-serif}.course__overview-title{border-bottom:1px solid #eee;padding:16px 0;margin:0;line-height:1.31;font-weight:400;text-transform:uppercase;text-align:center}.course__overview-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:16px;border-bottom:1px solid #eee;line-height:1.54;font-size:.8em}.course__overview-icon{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:24px;height:24px;padding-right:16px}.course__overview-value{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto}.course__overview-cta-wrapper{padding:16px 0;text-align:center}.course__overview-cta{margin:0}.course__cta{margin-bottom:0}.module-copy--emphasized{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:24px 0;background-color:#8091a5;border-radius:4px;color:#c3cfd5}.module-copy--emphasized a{color:inherit}.module-copy--emphasized a:hover{color:#fff}.module-copy__first--emphasized{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:50vw;padding:0 32px}.module-copy__second--emphasized{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.module-copy__headline--emphasized{color:#fff;font-size:1.25em}.module-copy__copy--emphasized{font-size:.875em}.module-copy__cta--emphasized{background-color:#536171;color:#fff;margin-bottom:0}.module-hero-image__wrapper{position:relative;overflow:hidden;max-height:60vh;border-radius:4px}.module-hero-image__image{display:block;max-width:100%;height:auto;margin:0 auto}.module-hero-image__headline-wrapper{position:absolute;left:0;right:0;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.module-hero-image__headline{color:#fff;text-align:center;font-size:4.5vw;line-height:1;text-shadow:0 3px 0 #b2a98f,0 14px 10px rgba(0,0,0,.15),0 24px 2px rgba(0,0,0,.1),0 34px 30px rgba(0,0,0,.1)}.module-higlighted-course__wrapper{position:relative;overflow:hidden;height:50vh;min-height:250px;max-height:500px;border-radius:4px;background-size:cover;background-repeat:no-repeat;background-position:50%}.module-higlighted-course__overlay{z-index:1;position:absolute;left:0;right:0;top:0;bottom:0;opacity:.8;border-radius:4px;background-color:#3c3d41}.module-higlighted-course__content{z-index:2;position:absolute;left:0;right:0;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding:32px 64px;color:#a9b9c0}.module-higlighted-course__categories{-webkit-box-flex:0;-ms-flex:0 0 1.5em;flex:0 0 1.5em}.module-higlighted-course__category{display:inline-block;margin-right:32px;font-size:.75em;letter-spacing:2px}.module-higlighted-course__title{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;margin:0 0 8px;color:#fff;font-size:4vw;line-height:1.3;white-space:nowrap}.module-higlighted-course__description-wrapper{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;max-width:400px;line-height:1.3}.module-higlighted-course__description-wrapper p{margin:0}.module-higlighted-course__link-wrapper{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;margin-top:32px}.module-higlighted-course__link{display:inline-block;border-bottom:1px solid #fff;text-transform:uppercase;color:#fff;font-size:.75em;line-height:2.17;letter-spacing:2px}.module{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;max-width:980px;margin:0 auto 32px;padding:0 2vw}.module img{width:100%;height:auto}.module:last-child{margin-bottom:0}.modules-container{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.lesson__title{margin-bottom:32px}.lesson__cta{margin-bottom:0}.lesson-module{margin-bottom:32px}.lesson-module:last-child{margin-bottom:0}.lesson-module img{display:block;max-width:100%;height:auto;margin:0 auto}.lesson-module-code__code-area{background:#f7f9fa}.lesson-module-code__trigger{display:inline-block;height:30px;padding:0 1em;margin-right:1em;background:#fff;border-top:3px solid transparent;line-height:30px;font-family:robotomedium,Helvetica,sans-serif;font-size:.875em;color:#8091a5;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.lesson-module-code__trigger:last-child{margin-right:0}.lesson-module-code__trigger:hover{color:#3c80cf}.lesson-module-code__trigger--active{background:#f7f9fa;color:#536171!important;cursor:default;border-color:#c3cfd5}.lesson-module-code__code{display:none}.lesson-module-code__code pre{margin:0}.lesson-module-code__code--active{display:block}@media (min-width:700px){.layout-no-sidebar{padding-left:276px}.layout-sidebar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.layout-sidebar__sidebar{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:228px;border-radius:4px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1)}.layout-sidebar__content{-webkit-box-flex:0;-ms-flex:0 1 732px;flex:0 1 732px;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box;margin-left:32px}.grid-list{margin:0 -16px}.grid-list,.grid-list__item{-webkit-box-flex:0;-ms-flex:0 0 calc(50% - 32px);flex:0 0 calc(50% - 32px)}.grid-list__item{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0 16px 32px}.header{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.header__upper-first{margin-bottom:0}.header__upper-first,.header__upper-second{-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.header__upper-copy{margin:0 16px 0 0}.header__logo{-webkit-box-flex:0;-ms-flex:0 0 200px;flex:0 0 200px;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:start}.header__controls{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.header__controls,.header__controls_group{margin:0 16px 0 0}.footer__apps,.main-navigation ul{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.modal__wrapper{padding:64px 96px}.modal__title{margin-bottom:32px;font-size:1.25em}.modal__content{font-size:1em}.modal__close-wrapper{top:32px;right:32px}.modal__close-button{width:24px;height:24px}.course__overview{float:right;border-radius:4px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1);width:228px;margin:0 0 32px 32px}.module-copy--emphasized{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.module-copy__first--emphasized{padding-left:32px}.module-copy__second--emphasized{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:0 32px;width:20vw}.module-hero-image__headline{font-size:4em}.module-higlighted-course__title{font-size:3.25em}.module-higlighted-course__description-wrapper{font-size:1.25em}} +/*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;white-space:normal}progress{display:inline-block;vertical-align:baseline}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none}@font-face{font-family:roboto;src:url(/fonts/roboto-regular-webfont.woff2) format("woff2"),url(/fonts/roboto-regular-webfont.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:roboto;src:url(/fonts/roboto-italic-webfont.woff2) format("woff2"),url(/fonts/roboto-italic-webfont.woff) format("woff");font-weight:400;font-style:italic}@font-face{font-family:roboto;src:url(/fonts/roboto-bold-webfont.woff2) format("woff2"),url(/fonts/roboto-bold-webfont.woff) format("woff");font-weight:700;font-style:normal}@font-face{font-family:roboto;src:url(/fonts/roboto-bolditalic-webfont.woff2) format("woff2"),url(/fonts/roboto-bolditalic-webfont.woff) format("woff");font-weight:700;font-style:italic}@font-face{font-family:robotomedium;src:url(/fonts/roboto-medium-webfont.woff2) format("woff2"),url(/fonts/roboto-medium-webfont.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:robotomedium;src:url(/fonts/roboto-mediumitalic-webfont.woff2) format("woff2"),url(/fonts/roboto-mediumitalic-webfont.woff) format("woff");font-weight:400;font-style:italic}.cta,button,fieldset,input,legend,optgroup,option,select,textarea{-webkit-box-sizing:border-box;box-sizing:border-box;outline:none;font-family:Raleway,sans-serif;font-size:16px;color:#536171;vertical-align:top;display:block;margin:32px 0;text-align:left}datalist{font-family:Raleway,sans-serif;font-size:16px}label{display:block;margin:32px 0 0;text-align:left;font-weight:700;font-size:.875em}label+input{margin-top:8px}input[type=checkbox],input[type=radio]{margin:0 0 4px!important}input[type=checkbox]+label,input[type=radio]+label{margin:0 0 0 8px;display:inline-block}input[list],input[type=color],input[type=date],input[type=datetime-local],input[type=email],input[type=file],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],textarea{width:100%;max-width:100%;padding:8px;background-color:#fff;border-radius:5px;border:1px solid #d3dce0}input[list],input[type=color],input[type=date],input[type=datetime-local],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{height:40px;line-height:40px;-webkit-appearance:none}textarea{-webkit-appearance:none;overflow:auto}input[type=range]{height:40px;width:100%;max-width:100%}input[type=file]{min-height:40px}input[type=search]{height:40px;-webkit-appearance:none}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=checkbox],input[type=radio]{display:inline-block;vertical-align:middle}fieldset{padding:0;border:0}legend{padding:0;font-weight:inherit}.cta,button,input[type=button],input[type=image],input[type=reset],input[type=submit]{display:inline-block;height:40px;min-width:200px;background-color:#5c9fef;padding:0 2em;cursor:pointer;line-height:40px;color:#fff;font-weight:400;-webkit-appearance:none;-moz-appearance:none;border:none;border-radius:3px;text-align:center}input[type=image]{text-align:center;padding:8px}button[disabled],input[disabled],option[disabled],select[disabled],textarea[disabled]{cursor:not-allowed}input:focus,option:focus,select:focus,textarea:focus{border-color:#5c9fef;color:#536171}.cta:focus,button:focus{background-color:#3c80cf;color:#fff}input[type=checkbox]:focus,input[type=radio]:focus{outline:2px dashed #d3dce0;border-radius:4px}.cta:focus,.cta:hover,button:focus,button:hover,input[type=button]:focus,input[type=button]:hover,input[type=reset]:focus,input[type=reset]:hover,input[type=submit]:focus,input[type=submit]:hover{background-color:#3c80cf;color:#fff}.form-item+.form-item{margin-top:32px}.form-item input{margin:0 0 8px}.form-item__help-text{font-size:.875em;color:#8091a5}.form-item__error-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.form-item__error-icon{width:12px;height:12px;padding-right:.3em;color:#cd3f39}.form-item__error-message{font-size:.875em;color:#cd3f39}.status-block{display:-webkit-box;display:-ms-flexbox;display:flex;margin:32px 0;padding:16px;border-radius:4px;font-size:.875em}.status-block--success{background:#f4fffb}.status-block--error{background:#fbe3e2}.status-block--info{background:#e8f7ff}.status-block__icon{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:24px;height:24px;margin-right:8px}.status-block__icon--success{color:#0eb87f}.status-block__icon--error{color:#cd3f39}.status-block__icon--info{color:#a9b9c0}.status-block__content{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.status-block__title{font-weight:700;color:#2a3039}.status-block__message{color:#536171}table{width:100%;border:1px solid #e5ebed;border-collapse:collapse}td,th{padding:10px;line-height:1.5}td:not(:last-of-type),th:not(:last-of-type){border-right:1px solid #e5ebed}th{background-color:#f7f9fa;border-bottom:1px solid #e5ebed;color:#2a3039;font-weight:400;font-family:robotomedium,Helvetica,sans-serif;text-align:left}tbody td{border-bottom:1px dashed #e5ebed}h1,h2,h3,h4,h5,h6{font-family:robotomedium,Helvetica,sans-serif;font-weight:400;line-height:1.31;color:#2a3039}h1:first-child,h2:first-child,h3:first-child,h4:first-child,h5:first-child,h6:first-child{margin-top:0}a{color:inherit;text-decoration:none}.form-item a,label a,p a{color:#5c9fef;text-decoration:underline}.form-item a:hover,label a:hover,p a:hover{color:#3c80cf;text-decoration:underline}p:first-child{margin-top:0}p:last-child{margin-bottom:0}blockquote{margin:0;padding:0 0 0 16px;font-style:italic;border-left:4px solid #c3cfd5}.layout-centered{max-width:980px}.layout-centered,.layout-centered-small{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;margin:0 auto;padding:0 2vw}.layout-centered-small{max-width:620px}.layout-no-sidebar,.layout-sidebar{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;max-width:980px;padding:0 16px;margin:0 auto}.layout-sidebar__sidebar-header{padding:16px 32px;border-bottom:1px solid #eee}.layout-sidebar__sidebar-title{font-family:robotomedium,Helvetica,sans-serif;font-size:1.25em;font-weight:400;margin:0}.layout-sidebar__sidebar-content{background:#f7f9fa;padding:16px}.layout-sidebar__content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.layout-sidebar__content>*{max-width:732px;width:100%}.grid-list{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.grid-list__item{-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;margin-bottom:32px}.header{margin-bottom:24px}.header__upper-wrapper{background:#536171;padding:8px 0;color:#b4c3ca}.header__upper-wrapper a.active,.header__upper-wrapper a:hover{color:#fff}.header__upper{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;font-size:.875em}.header__upper-first,.header__upper-second{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.header__upper-first,.header__upper-link,.header__upper-second{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.header__upper-copy,.header__upper-link{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex}.header__upper-copy{margin:0 0 8px}.header__upper-icon{display:inline-block;width:20px;height:20px;margin-right:8px}.header__lower{-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;padding-top:16px;padding-bottom:8px;border-bottom:1px solid #e5ebed}.header__lower,.header__title{display:-webkit-box;display:-ms-flexbox;display:flex}.header__title{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.header__title img{display:inline-block;width:24px;height:auto;margin-right:8px}.header__logo{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-flex:0;-ms-flex:0 0 100%;flex:0 0 100%;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.header__logo img{width:100%;height:auto;max-width:169px}.header__controls{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-ms-flex-pack:distribute;justify-content:space-around;margin:0 0 8px}.header__controls,.header__controls_group{display:-webkit-box;display:-ms-flexbox;display:flex}.header__controls_group{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;position:relative;margin:0 0 16px;border:none;border-radius:4px;color:inherit}.header__controls_group:last-child{margin:0}.header__controls_label{position:relative;z-index:5;cursor:pointer}.header__controls_label:hover{color:#fff}.header__controls_label:hover:after{background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='5' viewBox='0 0 10 5' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' d='M0 0l5 5 5-5z'/%3E%3C/svg%3E")}.header__controls_label:after{content:"";display:inline-block;width:10px;height:5px;margin-left:8px;background-image:url("data:image/svg+xml;charset=utf-8,%3Csvg width='10' height='5' viewBox='0 0 10 5' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23B4C3CA' d='M0 0l5 5 5-5z'/%3E%3C/svg%3E");background-size:contain;background-position:50%;background-repeat:no-repeat;vertical-align:middle}.header__controls_dropdown{position:absolute;z-index:6;-webkit-box-sizing:border-box;box-sizing:border-box;right:0;width:260px;max-width:90vw;margin:12px 0 0;opacity:0;background:#fff;border-radius:2px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1);-webkit-transition:opacity .3s ease;transition:opacity .3s ease;pointer-events:none}.header__controls_dropdown--active{opacity:1;pointer-events:all}.header__controls_dropdown--active:before{content:"";position:absolute;left:50%;top:-5px;width:10px;height:10px;background-color:inherit;-webkit-transform:translateX(-50%) rotate(45deg);transform:translateX(-50%) rotate(45deg)}.header__controls_help_text{padding:16px;color:#8091a5;line-height:1.29}.header__controls_button{-webkit-box-sizing:border-box;box-sizing:border-box;height:auto;width:100%;margin:0;padding:0 16px;border-radius:0;font-size:1em;text-align:left}.header__controls_button,.header__controls_button:hover{background:#fff;color:#263545}.header__controls_button--active,.header__controls_button--active:hover{background:#d3dce0}.footer{padding:32px 0}.footer p{margin:0}.footer__upper{-ms-flex-wrap:wrap;flex-wrap:wrap;min-height:80px;border-bottom:1px solid #e5ebed}.footer__upper,.footer__upper>*{display:-webkit-box;display:-ms-flexbox;display:flex}.footer__upper>*{-webkit-box-align:center;-ms-flex-align:center;align-items:center}.footer__navigation{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.footer__apps{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.footer__apps a{display:inline-block;width:138px}.footer__apps a+a{margin-left:8px}.footer__apps a img{width:100%;height:auto}.footer__lower{min-height:80px}.footer__disclaimer,.footer__lower{display:-webkit-box;display:-ms-flexbox;display:flex}.footer__disclaimer{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.footer__disclaimer-logo{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:140px;height:28px;margin-right:16px}.footer__disclaimer-text{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;color:#a9b9c0;font-size:.75em;line-height:1.5}.footer__social{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:0 0 0 32px}.footer__social a+a{margin-left:16px}.footer__social svg{color:#a9b9c0;width:24px;height:24px}.breadcrumb{margin-bottom:8px;font-size:.875em}.breadcrumb ul{display:-webkit-box;display:-ms-flexbox;display:flex;list-style:none;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0}.breadcrumb ul,.breadcrumb ul li{margin:0}.breadcrumb ul li:after{content:"";display:inline-block;background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='5' height='8' viewBox='0 0 5 8'%3E%3Cpath fill='none' stroke='%23536171' stroke-width='1.5' d='M1 7l3-2.89L1 1'/%3E%3C/svg%3E");background-size:contain;background-position:50%;background-repeat:no-repeat;width:4px;height:8px;padding:0 8px}.breadcrumb ul li:last-child:after{display:none}.breadcrumb ul li a{display:inline-block}.breadcrumb ul li a:hover{color:#3c80cf}.editorial-features{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-ms-flex-line-pack:center;align-content:center;margin-bottom:16px}.editorial-features a{color:#5c9fef}.editorial-features__item{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-line-pack:center;align-content:center;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;margin-right:8px}.editorial-features__item:last-child{margin-right:0}.editorial-features__hint-wrapper{position:relative;z-index:3;margin-left:8px}.editorial-features__hint-wrapper:hover>div{opacity:1;-webkit-transition-delay:0s;transition-delay:0s}.editorial-features__hint-icon{width:24px;height:24px;cursor:help}.editorial-features__hint-message{position:absolute;z-index:4;-webkit-box-sizing:border-box;box-sizing:border-box;top:24px;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%);width:260px;max-width:90vw;margin:12px 0 0;padding:16px;background:#536171;border-radius:2px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1);opacity:0;-webkit-transition:opacity .3s ease .3s;transition:opacity .3s ease .3s;pointer-events:none;font-size:.75em;line-height:1.5;color:#fff}.editorial-features__hint-message:before{content:"";position:absolute;left:50%;top:-5px;width:10px;height:10px;background-color:inherit;-webkit-transform:translateX(-50%) rotate(45deg);transform:translateX(-50%) rotate(45deg)}.main-navigation ul{display:-webkit-box;display:-ms-flexbox;display:flex;list-style:none;-ms-flex-wrap:wrap;flex-wrap:wrap;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;padding:0}.main-navigation ul,.main-navigation ul li{margin:0}.main-navigation ul li+li{margin-left:8px}.main-navigation ul li a{display:block;text-transform:uppercase;font-weight:700;padding:.7em 1em;color:#8091a5;border-radius:4px}.main-navigation ul li a.active,.main-navigation ul li a:hover{background:#f7f9fa}.main-navigation ul li a.active{color:#536171}.modal{display:none}.modal--visible{display:block}.modal__overlay{position:fixed;z-index:7;left:0;right:0;top:0;bottom:0;opacity:.3;background-color:#3b3d40;cursor:pointer}.modal__wrapper{position:fixed;z-index:8;left:50%;top:20vh;-webkit-transform:translateX(-50%);transform:translateX(-50%);-webkit-box-sizing:border-box;box-sizing:border-box;width:80vw;max-width:800px;padding:32px 48px;background-color:#fff;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1);border-radius:4px}.modal__title{margin-bottom:2vh;font-size:1em;text-align:center}.modal__content{color:#8091a5;font-size:.875em}.modal__content a{color:#5c9fef;text-decoration:underline}.modal__content a:hover{color:#3c80cf}.modal__cta-wrapper{text-align:center;margin-top:32px}.modal__cta{display:inline-block;padding-bottom:.3em;border-bottom:1px solid #5c9fef;color:#5c9fef;font-size:.75em;font-weight:700;line-height:2.17;letter-spacing:2px;text-transform:uppercase}.modal__cta:hover{color:#3c80cf;border-bottom-color:#3c80cf}.modal__close-wrapper{position:absolute;z-index:9;top:16px;right:16px}.modal__close-button{display:block;width:18px;height:18px;color:#a9b9c0}.modal__close-button svg{width:100%;height:100%}.pill{display:inline-block;vertical-align:middle;padding:0 8px;background:#3c80cf;border-radius:4px;text-transform:uppercase;font-size:14px;font-size:.875rem;letter-spacing:1px;color:#fff}.pill--draft{background-color:#e6ae17}.pill--pending-changes{background-color:#3e97d6}.sidebar-menu__list{list-style:none;margin:0;padding:0}.sidebar-menu__item{margin:0 0 16px;padding:0;font-size:.9em;line-height:1.8}.sidebar-menu__link{display:block;color:#8091a5}.sidebar-menu__link.active,.sidebar-menu__link:hover{color:#536171}.sidebar-menu__link.active{font-family:robotomedium,Helvetica,sans-serif}.table-of-contents__list{list-style:none;margin:0;padding:0;padding-left:32px}.table-of-contents__item{margin:0 0 8px;padding:0;font-size:.9em;line-height:1.8}.table-of-contents__link{display:block;color:#8091a5}.table-of-contents__link.active,.table-of-contents__link:hover{color:#536171}.table-of-contents__link.active{position:relative;font-family:robotomedium,Helvetica,sans-serif}.table-of-contents__link.active:after,.table-of-contents__link.active:before{position:absolute;top:0}.table-of-contents__link.active:before{content:"";left:-48px;bottom:0;width:3px;background:#536171}.table-of-contents__link.visited{position:relative}.table-of-contents__link.visited:after,.table-of-contents__link.visited:before{position:absolute;top:0}.table-of-contents__link.visited:after{content:"";display:block;width:16px;height:16px;top:50%;left:-32px;-webkit-transform:translateY(-50%);transform:translateY(-50%);background:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%23536171'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cpath d='M0 0h20v20H0z'/%3E%3Cpath fill='%23A9B9C0' fill-rule='nonzero' d='M9.992 1.667c-4.6 0-8.325 3.733-8.325 8.333s3.725 8.333 8.325 8.333c4.608 0 8.341-3.733 8.341-8.333S14.6 1.667 9.992 1.667zm.008 15A6.665 6.665 0 0 1 3.333 10 6.665 6.665 0 0 1 10 3.333 6.665 6.665 0 0 1 16.667 10 6.665 6.665 0 0 1 10 16.667z'/%3E%3Cpath stroke='%23A9B9C0' stroke-width='1.333' d='M13.333 7.5l-5 5-1.666-1.786'/%3E%3C/g%3E%3C/svg%3E");background-size:contain;background-position:50%;background-repeat:no-repeat}body{background-color:#fff;color:#536171;font-family:roboto,Helvetica,sans-serif;font-size:16px;font-size:1rem;line-height:1.5em}.main{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;min-height:100vh}.main__header{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.main__content{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.main__footer{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8f8}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.course-card{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding:32px;border-radius:4px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1)}.course-card__categories{-webkit-box-flex:0;-ms-flex:0 0 1.5em;flex:0 0 1.5em;margin-bottom:16px;height:1.5em}.course-card__category{display:inline-block;color:#8091a5;font-size:.75em;font-family:robotomedium,Helvetica,sans-serif}.course-card__category:after{content:" • ";display:inline-block;padding:0 .5em}.course-card__category:last-child:after{display:none}.course-card__category-link{display:inline-block;letter-spacing:2px}.course-card__title{-webkit-box-flex:0;-ms-flex:0 1 12vh;flex:0 1 12vh;max-height:120px;margin:0;overflow:hidden;padding-bottom:16px;margin-bottom:16px;border-bottom:1px solid #e5ebed;font-family:robotomedium,Helvetica,sans-serif;font-weight:400;font-size:1.625em;line-height:1.38}.course-card__title a:after{content:" "}.course-card__title .pill{margin-bottom:3px}.course-card__description{-webkit-box-flex:0;-ms-flex:0 1 18vh;flex:0 1 18vh;max-height:150px;overflow:hidden;margin:0 0 32px;line-height:1.63;color:#536171}.course-card__link-wrapper{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto}.course-card__link{display:inline-block;padding-bottom:.3em;border-bottom:1px solid #5c9fef;text-transform:uppercase;color:#5c9fef;font-size:.75em;line-height:2.17;letter-spacing:2px}.course-card__link:hover{color:#3c80cf;border-bottom-color:#3c80cf}.course__title{margin-bottom:32px}.course__overview{font-family:robotomedium,Helvetica,sans-serif}.course__overview-title{border-bottom:1px solid #eee;padding:16px 0;margin:0;line-height:1.31;font-weight:400;text-transform:uppercase;text-align:center}.course__overview-item{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;padding:16px;border-bottom:1px solid #eee;line-height:1.54;font-size:.8em}.course__overview-icon{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:24px;height:24px;padding-right:16px}.course__overview-value{-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto}.course__overview-cta-wrapper{padding:16px 0;text-align:center}.course__overview-cta{margin:0}.course__cta{margin-bottom:0}.module-copy img{display:block;max-width:100%;height:auto;margin:0 auto;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1)}.module-copy--emphasized{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:24px 0;background-color:#8091a5;border-radius:4px;color:#c3cfd5}.module-copy--emphasized a{color:inherit}.module-copy--emphasized a:hover{color:#fff}.module-copy__first--emphasized{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;width:50vw;padding:0 32px}.module-copy__second--emphasized{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.module-copy__headline--emphasized{color:#fff;font-size:1.25em}.module-copy__copy--emphasized{font-size:.875em}.module-copy__cta--emphasized{background-color:#536171;color:#fff;margin-bottom:0}.module-hero-image__wrapper{position:relative;overflow:hidden;max-height:60vh;border-radius:4px}.module-hero-image__image{display:block;max-width:100%;height:auto;margin:0 auto}.module-hero-image__headline-wrapper{position:absolute;left:0;right:0;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.module-hero-image__headline{color:#fff;text-align:center;font-size:4.5vw;line-height:1;text-shadow:0 3px 0 #b2a98f,0 14px 10px rgba(0,0,0,.15),0 24px 2px rgba(0,0,0,.1),0 34px 30px rgba(0,0,0,.1)}.module-higlighted-course__wrapper{position:relative;overflow:hidden;height:50vh;min-height:250px;max-height:500px;border-radius:4px;background-size:cover;background-repeat:no-repeat;background-position:50%}.module-higlighted-course__overlay{z-index:1;position:absolute;left:0;right:0;top:0;bottom:0;opacity:.8;border-radius:4px;background-color:#3c3d41}.module-higlighted-course__content{z-index:2;position:absolute;left:0;right:0;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;padding:32px 64px;color:#a9b9c0}.module-higlighted-course__categories{-webkit-box-flex:0;-ms-flex:0 0 1.5em;flex:0 0 1.5em}.module-higlighted-course__category{display:inline-block;margin-right:32px;font-size:.75em;letter-spacing:2px}.module-higlighted-course__title{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;margin:0 0 8px;color:#fff;font-size:4vw;line-height:1.3;white-space:nowrap}.module-higlighted-course__description-wrapper{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;max-width:400px;line-height:1.3}.module-higlighted-course__description-wrapper p{margin:0}.module-higlighted-course__link-wrapper{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;margin-top:32px}.module-higlighted-course__link{display:inline-block;border-bottom:1px solid #fff;text-transform:uppercase;color:#fff;font-size:.75em;line-height:2.17;letter-spacing:2px}.module{-webkit-box-sizing:border-box;box-sizing:border-box;width:100%;max-width:980px;margin:0 auto 32px;padding:0 2vw}.module img{width:100%;height:auto}.module:last-child{margin-bottom:0}.modules-container{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto}.lesson__title{margin-bottom:32px}.lesson__cta{margin-bottom:0}.lesson-module{margin-bottom:32px}.lesson-module:last-child{margin-bottom:0}.lesson-module-copy img{display:block;max-width:100%;height:auto;margin:0 auto;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1)}.lesson-module-code__code-area{background:#f7f9fa}.lesson-module-code__trigger{display:inline-block;height:30px;padding:0 1em;margin-right:1em;background:#fff;border-top:3px solid transparent;line-height:30px;font-family:robotomedium,Helvetica,sans-serif;font-size:.875em;color:#8091a5;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.lesson-module-code__trigger:last-child{margin-right:0}.lesson-module-code__trigger:hover{color:#3c80cf}.lesson-module-code__trigger--active{background:#f7f9fa;color:#536171!important;cursor:default;border-color:#c3cfd5}.lesson-module-code__code{display:none}.lesson-module-code__code pre{margin:0}.lesson-module-code__code--active{display:block}.lesson-module-image__figure{margin:0}.lesson-module-image__image{display:block;max-width:100%;height:auto;margin:0 auto;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1)}.lesson-module-image__caption{margin-top:16px;font-style:italic;line-height:1.63;color:#a9b9c0}@media (min-width:700px){.layout-no-sidebar{padding-left:276px}.layout-sidebar{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start}.layout-sidebar__sidebar{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;width:228px;border-radius:4px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1)}.layout-sidebar__content{-webkit-box-flex:0;-ms-flex:0 1 732px;flex:0 1 732px;width:100%;-webkit-box-sizing:border-box;box-sizing:border-box;margin-left:32px}.grid-list{margin:0 -16px}.grid-list,.grid-list__item{-webkit-box-flex:0;-ms-flex:0 0 calc(50% - 32px);flex:0 0 calc(50% - 32px)}.grid-list__item{-webkit-box-sizing:border-box;box-sizing:border-box;margin:0 16px 32px}.header{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.header__upper-first{margin-bottom:0}.header__upper-first,.header__upper-second{-webkit-box-flex:0;-ms-flex:0 1 auto;flex:0 1 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.header__upper-copy{margin:0 16px 0 0}.header__logo{-webkit-box-flex:0;-ms-flex:0 0 200px;flex:0 0 200px;-webkit-box-pack:start;-ms-flex-pack:start;justify-content:start}.header__controls{-webkit-box-flex:0;-ms-flex:0 0 auto;flex:0 0 auto;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row}.header__controls,.header__controls_group{margin:0 16px 0 0}.footer__apps,.main-navigation ul{-webkit-box-pack:end;-ms-flex-pack:end;justify-content:flex-end}.modal__wrapper{padding:64px 96px}.modal__title{margin-bottom:32px;font-size:1.25em}.modal__content{font-size:1em}.modal__close-wrapper{top:32px;right:32px}.modal__close-button{width:24px;height:24px}.course__overview{float:right;border-radius:4px;-webkit-box-shadow:0 1px 3px 1px rgba(0,0,0,.1);box-shadow:0 1px 3px 1px rgba(0,0,0,.1);width:228px;margin:0 0 32px 32px}.module-copy--emphasized{-ms-flex-wrap:nowrap;flex-wrap:nowrap}.module-copy__first--emphasized{padding-left:32px}.module-copy__second--emphasized{-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;padding:0 32px;width:20vw}.module-hero-image__headline{font-size:4em}.module-higlighted-course__title{font-size:3.25em}.module-higlighted-course__description-wrapper{font-size:1.25em}} \ No newline at end of file diff --git a/views/mixins/_lessonModuleImage.pug b/views/mixins/_lessonModuleImage.pug index fd69ad4..72ce95b 100644 --- a/views/mixins/_lessonModuleImage.pug +++ b/views/mixins/_lessonModuleImage.pug @@ -1,6 +1,9 @@ mixin lessonModuleImage(module) .lesson-module.lesson-module-image if module.fields.image && module.fields.image.fields.file && module.fields.image.fields.file.url - img.lesson-module-image__image(src=module.fields.image.fields.file.url alt=module.fields.image.fields.title) + figure.lesson-module-image__figure + img.lesson-module-image__image(src=module.fields.image.fields.file.url alt=module.fields.image.fields.title) + if module.fields.caption + figcaption.lesson-module-image__caption= module.fields.caption else h3 #{translate('imageErrorTitle', currentLocale.code)} From a48fd1b63d3f13e1cf1a1a8ab9da688b3b3282f9 Mon Sep 17 00:00:00 2001 From: Mario Bodemann Date: Wed, 8 Nov 2017 13:24:56 +0100 Subject: [PATCH 05/13] =?UTF-8?q?fix:=20clarify=20German=20translation?= =?UTF-8?q?=F0=9F=87=A9=F0=9F=87=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- i18n/locales/de-DE.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/i18n/locales/de-DE.json b/i18n/locales/de-DE.json index 9875605..1d9eb74 100644 --- a/i18n/locales/de-DE.json +++ b/i18n/locales/de-DE.json @@ -13,11 +13,11 @@ "localeQuestion": "Sie arbeiten mit verschiedenen Sprachen? Dann können Sie die Sprache für Anfragen an die Content Delivery API definieren.", "settingsLabel": "Einstellungen", "logoAlt": "Die Beispielanwendung für Contentful", - "homeLabel": "Home", + "homeLabel": "Startseite", "coursesLabel": "Kurse", "footerDisclaimer": "Powered by Contentful. Diese Website und deren Materialien existieren nur für Demonstrationszwecken. Sie können diese benutzen, um den Inhalt ihres Contentful Kontos anzusehen.", "imprintLabel": "Impressum", - "contactUsLabel": "Uns Kontaktieren", + "contactUsLabel": "Kontakt", "modalTitle": "Ein referenzierbares Beispiel für Entwickler, die Contentful benutzen.", "modalIntro": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen. Sie stellt eine Plattform fürs online Lernen dar, die mithilfe von Contentful gebaut wurde. (sehr meta!)", "modalCodeIntro": "Wenn Sie es bevorzugen, sich die Hände schmutzig zu machen, sehen sie sich die Anwendung hier an", @@ -37,7 +37,7 @@ "overviewLabel": "Übersicht", "durationLabel": "Dauer", "minutesLabel": "min", - "skillLevelLabel": "Komplexität", + "skillLevelLabel": "Schwierigkeit", "startCourseLabel": "Kurs beginnen", "categoriesLabel": "Kategorien", "allCoursesLabel": "Alle Kurse", @@ -60,14 +60,14 @@ "changesSavedLabel": "Änderungen erfolgreich gespeichert!", "errorOcurredTitleLabel": "Fehler aufgetreten", "errorOcurredMessageLabel": "Einige Fehler sind aufgetreten. Bitte schauen Sie sich die Fehlermeldungen neben den Feldern an.", - "connectedToSpaceLabel": "Mit einem Space verbinden.", + "connectedToSpaceLabel": "Verbunden mit Space ", "spaceIdLabel": "Space ID", "spaceIdHelpText": "Die Space Id ist eine eindeutige Identifizierung für Ihren Space.", "accessTokenLabel": "Access Token", "contentDeliveryApiHelpText": "Schauen Sie sich veröffentlichten Inhalt mit dieser API an.", "contentPreviewApiHelpText": "Schauen Sie sich unveröffentlichten Inhalt mit dieser API an. (z.B. Inhalt im Zustand “Entwurf”).", "enableEditorialFeaturesLabel": "Editoriale Funktionen aktivieren.", - "enableEditorialFeaturesHelpText": "Aktivieren, um Bearbeitung und weitere kontextabhängige Helferlein zu aktivieren. Damit dies funktioniert, müssen sie Zugang zu dem Space haben.", + "enableEditorialFeaturesHelpText": "Aktivieren, um Bearbeitung und weitere kontextabhängige Helfer zu aktivieren. Damit dies funktioniert, müssen sie Zugang zu dem Space haben.", "saveSettingsButtonLabel": "Einstellungen Speichern", "fieldIsRequiredLabel": "Diese Feld ist notwendig.", "deliveryKeyInvalidLabel": "Ihr Delivery API Zugangsschlüssel ist ungültig.", From 81577c82fd07a4106a585ed85119031c67963992 Mon Sep 17 00:00:00 2001 From: David Litvak Bruno Date: Wed, 8 Nov 2017 15:39:24 +0100 Subject: [PATCH 06/13] Enhance Breadcrumbs (#39) * chore(localization): remove duplicated locale files * feat(localization): add localization for breadcrumbs * feat(localization): add beadcrumb enhancements * feat(debugging): add npm run debug command * chore(localization): add unit tests * chore(breadcrumbs): fix tests and typos * chore(localization): add docs for translationAvailable * chore(localization): refactor translationAvailable --- i18n/i18n.js | 11 ++++++ i18n/locales/en-US.json | 1 + lib/breadcrumb.js | 13 ++++++- lib/enhance-breadcrumb.js | 13 +++++++ package.json | 1 + public/locales/de-DE.json | 77 --------------------------------------- public/locales/en-US.json | 77 --------------------------------------- routes/courses.js | 12 ++++++ test/unit/index.test.js | 19 +++++++++- test/unit/mocks/index.js | 1 + 10 files changed, 67 insertions(+), 158 deletions(-) create mode 100644 lib/enhance-breadcrumb.js delete mode 100644 public/locales/de-DE.json delete mode 100644 public/locales/en-US.json diff --git a/i18n/i18n.js b/i18n/i18n.js index 5542fe2..91cd84a 100644 --- a/i18n/i18n.js +++ b/i18n/i18n.js @@ -44,3 +44,14 @@ module.exports.translate = (symbol, locale = 'en-US') => { return translatedValue } + +/** + * Checks if string is translatable + * @param symbol string Identifier for static text + * @param locale string Locale code + * + * @returns boolean + */ +module.exports.translationAvaliable = (symbol, locale = 'en-US') => { + return !!(translations[locale] || {})[symbol] +} diff --git a/i18n/locales/en-US.json b/i18n/locales/en-US.json index 1932523..79ccea4 100644 --- a/i18n/locales/en-US.json +++ b/i18n/locales/en-US.json @@ -15,6 +15,7 @@ "logoAlt": "Contentful Example App", "homeLabel": "Home", "coursesLabel": "Courses", + "lessonsLabel": "Lessons", "footerDisclaimer": "Powered by Contentful. This website and the materials found on it are for demo purposes. You can use this to preview the content created on your Contentful account.", "imprintLabel": "Imprint", "contactUsLabel": "Contact us", diff --git a/lib/breadcrumb.js b/lib/breadcrumb.js index d39f51b..97fe9f0 100644 --- a/lib/breadcrumb.js +++ b/lib/breadcrumb.js @@ -1,5 +1,5 @@ const url = require('url') -const { translate } = require('../i18n/i18n') +const { translate, translationAvaliable } = require('../i18n/i18n') module.exports = (modifier) => { return (request, response, next) => { @@ -13,17 +13,26 @@ module.exports = (modifier) => { }) // Map components of the path to breadcrumbs with resolvable URLs let mappedComponents = urlComponents.map((component, i, array) => { + const currentLocale = response.locals.currentLocale const path = array.slice(0, i + 1).join('/') + + let label = component.replace(/-/g, ' ') + if (translationAvaliable(`${label}Label`, currentLocale.code)) { + label = translate(`${label}Label`, currentLocale.code) + } + return { - label: component.replace(/-/g, ' '), + label: label, url: url.resolve(baseUrl, path), path: path } }) + breadcrumbs = breadcrumbs.concat(mappedComponents) if (modifier) { breadcrumbs = breadcrumbs.map(modifier) } + // Make it global request.app.locals.breadcrumb = breadcrumbs next() diff --git a/lib/enhance-breadcrumb.js b/lib/enhance-breadcrumb.js new file mode 100644 index 0000000..d13bf9b --- /dev/null +++ b/lib/enhance-breadcrumb.js @@ -0,0 +1,13 @@ +module.exports = (request, resource) => { + const breadcrumbs = request.app.locals.breadcrumb + + let enhancedBreadcrumbs = breadcrumbs.map((breadcrumb) => { + if (breadcrumb.label.replace(/ /g, '-') == resource.fields.slug) { + breadcrumb.label = resource.fields.title + } + return breadcrumb + }) + + // We replace the breadcrumbs with the enhanced version + request.app.locals.breadcrumb = enhancedBreadcrumbs +} diff --git a/package.json b/package.json index cd4337b..3b912f2 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "start:watch": "nodemon ./bin/www --ignore public/", "start:dev": "node ./bin/www", + "debug": "node debug ./bin/www", "start": "NODE_ENV=production node ./bin/www", "start:production": "NODE_ENV=production node ./bin/www", "lint": "eslint ./app.js routes", diff --git a/public/locales/de-DE.json b/public/locales/de-DE.json deleted file mode 100644 index 9875605..0000000 --- a/public/locales/de-DE.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "defaultTitle": "Die Beispielanwendung", - "whatIsThisApp": "Was ist die Beispielanwendung?", - "metaDescription": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.", - "metaTwitterCard": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.", - "metaImageAlt": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.", - "metaImageDescription": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen.", - "viewOnGithub": "Auf GitHub ansehen", - "apiSwitcherHelp": "Ansehen des veröffentlichten und unveröffentlichten Inhalts durch Wechsel von Delivery und Preview APIs.", - "apiLabelHelpcda": "(veröffentlichter Inhalt)", - "apiLabelHelpcpa": "(unveröffentlichter Inhalt)", - "locale": "Sprache", - "localeQuestion": "Sie arbeiten mit verschiedenen Sprachen? Dann können Sie die Sprache für Anfragen an die Content Delivery API definieren.", - "settingsLabel": "Einstellungen", - "logoAlt": "Die Beispielanwendung für Contentful", - "homeLabel": "Home", - "coursesLabel": "Kurse", - "footerDisclaimer": "Powered by Contentful. Diese Website und deren Materialien existieren nur für Demonstrationszwecken. Sie können diese benutzen, um den Inhalt ihres Contentful Kontos anzusehen.", - "imprintLabel": "Impressum", - "contactUsLabel": "Uns Kontaktieren", - "modalTitle": "Ein referenzierbares Beispiel für Entwickler, die Contentful benutzen.", - "modalIntro": "Dies ist die Beispielanwendung, eine Anwendung die Ihnen hilft Ihre eigene Anwendung mit Contentful zu bauen. Sie stellt eine Plattform fürs online Lernen dar, die mithilfe von Contentful gebaut wurde. (sehr meta!)", - "modalCodeIntro": "Wenn Sie es bevorzugen, sich die Hände schmutzig zu machen, sehen sie sich die Anwendung hier an", - "modalCTALabel": "Gut, verstanden.", - "editorialFeaturesHint": "Bearbeiten Sie diesen Eintrag in unserer Web App. Sie müssen sich eingelogged haben und Zugang zum Space haben, um diese Funktion nutzen zu können.", - "draftLabel": "Entwurf", - "pendingChangesLabel": "Änderungen vorbehalten", - "lessonModuleErrorTitle": "⚠️ Ungültiges Lektionsmodul", - "lessonModuleErrorBody": "Konnte den Typ nicht erkennen: ", - "nextLessonLabel": "Nächste Lektion ansehen", - "imageErrorTitle": "⚠️ Bild fehlt", - "viewCourseLabel": "Kurs ansehen", - "categoriesWelcomeLabel": "Willkommen zur folgenden Kategorie: ", - "sitemapWelcomeLabel": "Willkommen zur folgenden Kategorie: ", - "tableOfContentsLabel": "Inhalt", - "courseOverviewLabel": "Kurs Übersicht", - "overviewLabel": "Übersicht", - "durationLabel": "Dauer", - "minutesLabel": "min", - "skillLevelLabel": "Komplexität", - "startCourseLabel": "Kurs beginnen", - "categoriesLabel": "Kategorien", - "allCoursesLabel": "Alle Kurse", - "somethingWentWrongLabel": "Hmm, etwas ging schief.", - "tryLabel": "Versuchen Sie", - "contentModelChangedErrorLabel": "Überprüfen Sie, ob das Content Model verändert wurde.", - "draftOrPublishedErrorLabel": "Überprüfen Sie, ob es nicht veröffentlichte Änderungen gibt.", - "localeContentErrorLabel": "Überprüfen Sie, ob es Inhalt für diese Sprache gibt.", - "verifyCredentialsErrorLabel": "Überprüfen Sie, ob die Zugangsdaten richtig und nicht abgelaufen sind.", - "stackTraceErrorLabel": "Schauen Sie sich den folgenden Stack Trace an", - "errorLabel": "Fehler", - "stackTraceLabel": "Stack Trace", - "companyLabel": "Firma", - "officeLabel": "Büro in Berlin", - "germanyLabel": "Deutschland", - "registrationCourtLabel": "Amtsgericht", - "managingDirectorLabel": "Verwalter", - "vatNumberLabel": "Steuernummer", - "settingsIntroLabel": "Um Inhalt von unseren APIs zu bekommen, müssen Anwendungen von Kunden sich authentifizieren, sowohl mit der Space ID als auch dem Access Token.", - "changesSavedLabel": "Änderungen erfolgreich gespeichert!", - "errorOcurredTitleLabel": "Fehler aufgetreten", - "errorOcurredMessageLabel": "Einige Fehler sind aufgetreten. Bitte schauen Sie sich die Fehlermeldungen neben den Feldern an.", - "connectedToSpaceLabel": "Mit einem Space verbinden.", - "spaceIdLabel": "Space ID", - "spaceIdHelpText": "Die Space Id ist eine eindeutige Identifizierung für Ihren Space.", - "accessTokenLabel": "Access Token", - "contentDeliveryApiHelpText": "Schauen Sie sich veröffentlichten Inhalt mit dieser API an.", - "contentPreviewApiHelpText": "Schauen Sie sich unveröffentlichten Inhalt mit dieser API an. (z.B. Inhalt im Zustand “Entwurf”).", - "enableEditorialFeaturesLabel": "Editoriale Funktionen aktivieren.", - "enableEditorialFeaturesHelpText": "Aktivieren, um Bearbeitung und weitere kontextabhängige Helferlein zu aktivieren. Damit dies funktioniert, müssen sie Zugang zu dem Space haben.", - "saveSettingsButtonLabel": "Einstellungen Speichern", - "fieldIsRequiredLabel": "Diese Feld ist notwendig.", - "deliveryKeyInvalidLabel": "Ihr Delivery API Zugangsschlüssel ist ungültig.", - "spaceOrTokenInvalid": "Dieser Space existiert nicht, oder Ihr Access Token kommt nicht von diesem Space.", - "somethingWentWrongLabel": "Irgendetwas lief falsch.", - "previewKeyInvalidLabel": "Ihr Preview API Zugangsschlüssel ist ungültig." -} diff --git a/public/locales/en-US.json b/public/locales/en-US.json deleted file mode 100644 index 1932523..0000000 --- a/public/locales/en-US.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "defaultTitle": "The Example App", - "whatIsThisApp": "What is this example app?", - "metaDescription": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.", - "metaTwitterCard": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.", - "metaImageAlt": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.", - "metaImageDescription": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful.", - "viewOnGithub": "View on Github", - "apiSwitcherHelp": "View the published or draft content by simply switching between the Deliver and Preview APIs.", - "apiLabelHelpcda": "(published content)", - "apiLabelHelpcpa": "(draft content)", - "locale": "Locale", - "localeQuestion": "Working with multiple languages? You can query the Content Delivery API for a specific locale.", - "settingsLabel": "Settings", - "logoAlt": "Contentful Example App", - "homeLabel": "Home", - "coursesLabel": "Courses", - "footerDisclaimer": "Powered by Contentful. This website and the materials found on it are for demo purposes. You can use this to preview the content created on your Contentful account.", - "imprintLabel": "Imprint", - "contactUsLabel": "Contact us", - "modalTitle": "A referenceable example for developers using Contentful", - "modalIntro": "This is The Example App, an application built to serve you as a reference while building your own applications using Contentful. This app is an online learning platform which teaches you how Contentful was used to build this app (so meta)!", - "modalCodeIntro": "If you prefer to start by getting your hands dirty with code, check out this app on", - "modalCTALabel": "Ok, got it.", - "editorialFeaturesHint": "Edit this entry in our web app. You have to be logged in and have access to the connected space to use this feature.", - "draftLabel": "draft", - "pendingChangesLabel": "pending changes", - "lessonModuleErrorTitle": "⚠️ Invalid lesson module", - "lessonModuleErrorBody": "Could not determine type of", - "nextLessonLabel": "View next lesson", - "imageErrorTitle": "⚠️ Image missing", - "viewCourseLabel": "view course", - "categoriesWelcomeLabel": "Welcome to", - "sitemapWelcomeLabel": "Welcome to", - "tableOfContentsLabel": "Table of contents", - "courseOverviewLabel": "Cource overview", - "overviewLabel": "Overview", - "durationLabel": "Duration", - "minutesLabel": "min", - "skillLevelLabel": "Skill level", - "startCourseLabel": "Start course", - "categoriesLabel": "Categories", - "allCoursesLabel": "All courses", - "somethingWentWrongLabel": "Oops Something went wrong", - "tryLabel": "Try", - "contentModelChangedErrorLabel": "Check if the content model has changed", - "draftOrPublishedErrorLabel": "Check the selection has content in draft or published state (for Preview or Delivery)", - "localeContentErrorLabel": "Check if there's any content for this locale", - "verifyCredentialsErrorLabel": "Verify credentials are correct and up to date", - "stackTraceErrorLabel": "Check the stack trace below", - "errorLabel": "Error", - "stackTraceLabel": "Stack trace", - "companyLabel": "Company", - "officeLabel": "Office Berlin", - "germanyLabel": "Germany", - "registrationCourtLabel": "Registration Court", - "managingDirectorLabel": "Managing Director", - "vatNumberLabel": "VAT Number", - "settingsIntroLabel": "To query and get content using the APIs, client applications need to authenticate with both the Space ID and an access token.", - "changesSavedLabel": "Changes saved successfully!", - "errorOcurredTitleLabel": "Error occurred", - "errorOcurredMessageLabel": "Some errors occurred. Please check the error messages next to the fields.", - "connectedToSpaceLabel": "Connected to space", - "spaceIdLabel": "Space ID", - "spaceIdHelpText": "The Space ID is a unique identifier for your space.", - "accessTokenLabel": "access token", - "contentDeliveryApiHelpText": "View published content using this API.", - "contentPreviewApiHelpText": "Preview unpublished content using this API (i.e. content with “Draft” status).", - "enableEditorialFeaturesLabel": "Enable editorial features", - "enableEditorialFeaturesHelpText": "Enable to display an edit link and other contextual helpers for authors. You need to have access to the connected space to make this work.", - "saveSettingsButtonLabel": "Save settings", - "fieldIsRequiredLabel": "This field is required", - "deliveryKeyInvalidLabel": "Your Delivery API key is invalid.", - "spaceOrTokenInvalid": "This space does not exist or your access token is not associated with your space.", - "somethingWentWrongLabel": "Something went wrong", - "previewKeyInvalidLabel": "Your Preview API key is invalid." -} diff --git a/routes/courses.js b/routes/courses.js index 4bf059a..b670ce2 100644 --- a/routes/courses.js +++ b/routes/courses.js @@ -10,6 +10,7 @@ const { } = require('./../services/contentful') const attachEntryState = require('../lib/entry-state') +const enhanceBreadcrumb = require('../lib/enhance-breadcrumb') const { updateCookie } = require('../lib/cookies') const { translate } = require('../i18n/i18n') @@ -69,6 +70,9 @@ module.exports.getCourse = async (request, response, next) => { course = await attachEntryState(course) } + // Enhance the breadcrumbs with the course + enhanceBreadcrumb(request, course) + response.render('course', {title: course.fields.title, course, lesson, lessons, lessonIndex, visitedLessons}) } @@ -93,6 +97,10 @@ module.exports.getCoursesByCategory = async (request, response, next) => { } catch (e) { console.log('Error ', e) } + + // Enhance the breadcrumbs with the active category + enhanceBreadcrumb(request, activeCategory) + response.render('courses', { title: `${activeCategory.fields.title} (${courses.length})`, categories, courses }) } @@ -123,6 +131,10 @@ module.exports.getLesson = async (request, response, next) => { lesson = await attachEntryState(lesson) } + // Enhance the breadcrumbs with the course and active lesson + enhanceBreadcrumb(request, course) + enhanceBreadcrumb(request, lesson) + response.render('course', { title: `${course.fields.title} | ${lesson.fields.title}`, course, diff --git a/test/unit/index.test.js b/test/unit/index.test.js index e634683..55caa0c 100644 --- a/test/unit/index.test.js +++ b/test/unit/index.test.js @@ -2,12 +2,18 @@ const { getCourses, getCourse, getCoursesByCategory, getLesson } = require('../../routes/courses') const { getSettings } = require('../../routes/settings') const { mockCourse, mockCategory } = require('./mocks/index') -const { translate, initializeTranslations } = require('../../i18n/i18n') +const { translate, translationAvaliable, initializeTranslations } = require('../../i18n/i18n') jest.mock('../../services/contentful') const contentful = require('../../services/contentful') -const request = {} +const request = { + app: { + locals: { + breadcrumb: [] + } + } +} const response = { locals: { settings: { @@ -104,4 +110,13 @@ describe('i18n', () => { test('It returns the translated string when symbol is found on locale file', () => { expect(translate('coursesLabel', 'en-US')).toBe('Courses') }) + test('It returns true if string is found for locale', () => { + expect(translationAvaliable('coursesLabel', 'en-US')).toBe(true) + }) + test('It returns false if string is not found for locale', () => { + expect(translationAvaliable('foo-symbol', 'en-US')).toBe(false) + }) + test('It returns false if locale is not found', () => { + expect(translationAvaliable('coursesLabel', 'foo-locale')).toBe(false) + }) }) diff --git a/test/unit/mocks/index.js b/test/unit/mocks/index.js index b6c75a9..b7ba9d7 100644 --- a/test/unit/mocks/index.js +++ b/test/unit/mocks/index.js @@ -1,6 +1,7 @@ const mockCourse = { sys: { id: 'courseId' }, fields: { + slug: 'courseSlug', title: 'Course title', lessons: [ { sys: {id: 'lessonId'}, fields: { slug: 'lessonSlug', title: 'Lesson title' } } From 0d7b4894923e0a0a668ffb8a7ace5cb9e8654ac7 Mon Sep 17 00:00:00 2001 From: JP Date: Wed, 8 Nov 2017 15:57:04 +0100 Subject: [PATCH 07/13] Hotfix/static text changes (#40) * Update text on next lesson button * Change language names in code snippets modules --- i18n/locales/en-US.json | 2 +- views/mixins/_lessonModuleCodeSnippet.pug | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/i18n/locales/en-US.json b/i18n/locales/en-US.json index 79ccea4..83e4f48 100644 --- a/i18n/locales/en-US.json +++ b/i18n/locales/en-US.json @@ -28,7 +28,7 @@ "pendingChangesLabel": "pending changes", "lessonModuleErrorTitle": "⚠️ Invalid lesson module", "lessonModuleErrorBody": "Could not determine type of", - "nextLessonLabel": "View next lesson", + "nextLessonLabel": "Go to next lesson", "imageErrorTitle": "⚠️ Image missing", "viewCourseLabel": "view course", "categoriesWelcomeLabel": "Welcome to", diff --git a/views/mixins/_lessonModuleCodeSnippet.pug b/views/mixins/_lessonModuleCodeSnippet.pug index bcb66f6..14cfde8 100644 --- a/views/mixins/_lessonModuleCodeSnippet.pug +++ b/views/mixins/_lessonModuleCodeSnippet.pug @@ -2,11 +2,11 @@ mixin lessonModuleCodeSnippet(module) .lesson-module.lesson-module-code .lesson-module-code__header if module.fields.curl - a.lesson-module-code__trigger(data-target=`${module.sys.id}-curl`) curl + a.lesson-module-code__trigger(data-target=`${module.sys.id}-curl`) cURL if module.fields.dotNet a.lesson-module-code__trigger(data-target=`${module.sys.id}-dotnet`) .NET if module.fields.javascript - a.lesson-module-code__trigger.lesson-module-code__trigger--active(data-target=`${module.sys.id}-javascript`) Javascript + a.lesson-module-code__trigger.lesson-module-code__trigger--active(data-target=`${module.sys.id}-javascript`) JavaScript if module.fields.java a.lesson-module-code__trigger(data-target=`${module.sys.id}-java`) Java if module.fields.javaAndroid From aa769dc02f2c1f045ca9438e806c0c0f4026275f Mon Sep 17 00:00:00 2001 From: David Litvak Bruno Date: Wed, 8 Nov 2017 15:59:16 +0100 Subject: [PATCH 08/13] chore(github): replace links with real ones (#41) --- README.md | 2 +- views/layout.pug | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 85c5c9b..edc0097 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ The Contentful example app, written in node.js. ## Installation ```bash -git clone https://github.com/contentful/example-contentful-university-js.git +git clone https://github.com/contentful/the-example-app.nodejs.git ``` ```bash diff --git a/views/layout.pug b/views/layout.pug index ce7c4f1..740d568 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -118,7 +118,7 @@ html a(href='https://twitter.com/contentful' target='_blank' rel='noopener') svg use(xlink:href='/icons/icons.svg#twitter') - a(href='https://images.contentful.com/82t39nctsu20/1JOkYZnY8YG0w88ImweME2/c8aef71dfe1ea633e16e17d99379416c/Github-repo_2x__1_.png' target='_blank' rel='noopener') + a(href='https://github.com/contentful' target='_blank' rel='noopener') svg use(xlink:href='/icons/icons.svg#github') section.modal#about-this-modal @@ -129,7 +129,7 @@ html p #{translate('modalIntro', currentLocale.code)} p | #{translate('modalCodeIntro', currentLocale.code)}  - a(href='https://images.contentful.com/82t39nctsu20/1JOkYZnY8YG0w88ImweME2/c8aef71dfe1ea633e16e17d99379416c/Github-repo_2x__1_.png' target='_blank' rel='noopener') Github + a(href='https://github.com/contentful/the-example-app.nodejs' target='_blank' rel='noopener') Github | . .modal__cta-wrapper a(href='#').modal__cta.close #{translate('modalCTALabel', currentLocale.code)} From 761a58594364d3aeda95a446c8fb83214394dedf Mon Sep 17 00:00:00 2001 From: David Litvak Bruno Date: Wed, 8 Nov 2017 16:01:17 +0100 Subject: [PATCH 09/13] chore(contactUs): contact us now opens in a new tab (#42) --- views/layout.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/layout.pug b/views/layout.pug index 740d568..bc428b4 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -108,7 +108,7 @@ html | .  a(href=`/imprint${queryString}` ) #{translate('imprintLabel', currentLocale.code)} | .  - a(href=`https://www.contentful.com/contact/` ) #{translate('contactUsLabel', currentLocale.code)} + a(href=`https://www.contentful.com/contact/` target='_blank') #{translate('contactUsLabel', currentLocale.code)} | . .footer__social p From 93608ce4bac9b7ee324f6c1dcbd79cb15a35f9a8 Mon Sep 17 00:00:00 2001 From: JP Wright Date: Tue, 7 Nov 2017 11:44:44 +0100 Subject: [PATCH 10/13] Write new readme --- README.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index edc0097..d1d16e2 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,24 @@ -# The Node.js example app -The Contentful example app, written in node.js. +## The node.js Example App + +The node.js Example App aims at getting across the very basics of how to work with our headless content management system and how to build apps using our officially supported JavaScript SDK. You’ll learn best practices for using the SDK to deliver content to your app and additionally learn some techniques for modelling your content in Contentful. We hope this app will give you a better understanding of how decoupling content from its presentation enables greater flexibility and facilitates shipping higher quality software more quickly. +Contentful is a content management platform for web applications, mobile apps and connected devices. It allows you to create, edit and manage content in the cloud and publish it anywhere via powerful API. Contentful offers tools for managing editorial teams and enabling cooperation between organizations. + +Screenshot of the example app ## Requirements * Node 8 * Git +Without any changes, this app is connected to a Contentful space that is not publicly accessible. The full end-to-end Contentful experience requires you to clone this space to your own Contentful account, and enables you to see how content editing in the Contentful web app and see those changes propagate to this running application. Signing up and getting started with our free plan is... free! -## Installation +You can clone the space for this example app to your own Contentful account by following the instructions [here](https://github.com/contentful/content-models/tree/master/the-example-app/README.md). Once you’ve created a space, you can change the credentials in the variables.env. If you don’t feel like changing code immediately, you can also inject credentials via url parameters like so: + +``` +https://localhost:3000?space_id=&delivery_token=&preview_token= +``` + +## Installing the Node.js app ```bash git clone https://github.com/contentful/the-example-app.nodejs.git @@ -17,19 +28,13 @@ git clone https://github.com/contentful/the-example-app.nodejs.git npm install ``` -## Usage +## Running the Node.js app -``` -npm start +To start the server, run the following + +```bash +npm run start:dev ``` -Open http://localhost:3000/?enable_editorial_features in your browser. - -## Deep links - -The following deep links are supported: - -* `?enable_editorial_features` - Shows `Edit in web app` button on every content type plus `Draft` and `Pending Changes` status pills -* `?space_id=xxx&delivery_token=xxx&preview_token=xxx` - Configure the connected space - +Open [https://localhost:3000](https://localhost:3000) and take a look around. If you have configured the app to connect to a space that you own, use [https://localhost:3000?enable_editorial_features](https://localhost:3000?enable_editorial_features). This URL flag adds an “Edit” button in the app on every editable piece of content which will take you back to Contentful web app where you can make changes. It also adds “Draft” and “Pending Changes” status indicators to all content if relevant. From f6c12658ceb6c2a7dd7a178ff16b6bcfc31ee833 Mon Sep 17 00:00:00 2001 From: Khaled Garbaya Date: Wed, 8 Nov 2017 17:03:38 +0100 Subject: [PATCH 11/13] fix: Fix courses ordering --- services/contentful.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/contentful.js b/services/contentful.js index ce00772..68c47ca 100644 --- a/services/contentful.js +++ b/services/contentful.js @@ -66,7 +66,7 @@ module.exports.getCourses = assert((locale = 'en-US', api = `cda`) => { return getClient(api).getEntries({ content_type: 'course', locale, - order: 'sys.createdAt', // Ordering the entries by creation date + order: '-sys.createdAt', // Ordering the entries by creation date include: 6 // We use include param to increase the link level, the include value goes from 1 to 6 }) .then((response) => response.items) From 20a1a0915f50ba4661447abdc1133aea5171f94b Mon Sep 17 00:00:00 2001 From: David Litvak Bruno Date: Wed, 8 Nov 2017 17:09:16 +0100 Subject: [PATCH 12/13] chore(localization): add localization for skill level tags (#43) * chore(localization): add localization for skill level tags * chore(localization): lowercase the skill level symbols --- i18n/locales/de-DE.json | 5 ++++- i18n/locales/en-US.json | 5 ++++- views/course.pug | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/i18n/locales/de-DE.json b/i18n/locales/de-DE.json index 1d9eb74..f953378 100644 --- a/i18n/locales/de-DE.json +++ b/i18n/locales/de-DE.json @@ -73,5 +73,8 @@ "deliveryKeyInvalidLabel": "Ihr Delivery API Zugangsschlüssel ist ungültig.", "spaceOrTokenInvalid": "Dieser Space existiert nicht, oder Ihr Access Token kommt nicht von diesem Space.", "somethingWentWrongLabel": "Irgendetwas lief falsch.", - "previewKeyInvalidLabel": "Ihr Preview API Zugangsschlüssel ist ungültig." + "previewKeyInvalidLabel": "Ihr Preview API Zugangsschlüssel ist ungültig.", + "beginnerLabel": "Anfänger", + "intermediateLabel": "Fortgeschrittener", + "advancedLabel": "Profi" } diff --git a/i18n/locales/en-US.json b/i18n/locales/en-US.json index 83e4f48..d8da0fe 100644 --- a/i18n/locales/en-US.json +++ b/i18n/locales/en-US.json @@ -74,5 +74,8 @@ "deliveryKeyInvalidLabel": "Your Delivery API key is invalid.", "spaceOrTokenInvalid": "This space does not exist or your access token is not associated with your space.", "somethingWentWrongLabel": "Something went wrong", - "previewKeyInvalidLabel": "Your Preview API key is invalid." + "previewKeyInvalidLabel": "Your Preview API key is invalid.", + "beginnerLabel": "Beginner", + "intermediateLabel": "Intermediate", + "advancedLabel": "Advanced" } diff --git a/views/course.pug b/views/course.pug index 262a938..2cd91c4 100644 --- a/views/course.pug +++ b/views/course.pug @@ -38,7 +38,7 @@ block content .course__overview-item svg.course__overview-icon use(xlink:href='/icons/icons.svg#skill-level') - .course__overview-value #{translate('skillLevelLabel', currentLocale.code)}: #{course.fields.skillLevel} + .course__overview-value #{translate('skillLevelLabel', currentLocale.code)}: #{translate(`${course.fields.skillLevel}Label`, currentLocale.code)} .course__overview-cta-wrapper a.course__overview-cta.cta(href=`/courses/${course.fields.slug}/lessons/${course.fields.lessons[0].fields.slug}${queryString}`) #{translate('startCourseLabel', currentLocale.code)} .course__description !{helpers.markdown(course.fields.description)} From f81d60b659a19b08b56dd7c3ea01b87d0b64eb58 Mon Sep 17 00:00:00 2001 From: Khaled Garbaya Date: Wed, 8 Nov 2017 17:15:36 +0100 Subject: [PATCH 13/13] chore: Change credentials --- variables.env | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variables.env b/variables.env index 39ceb8c..a6b5b33 100644 --- a/variables.env +++ b/variables.env @@ -1,5 +1,5 @@ NODE_ENV=development -CONTENTFUL_SPACE_ID=ft4tkuv7nwl0 -CONTENTFUL_DELIVERY_TOKEN=57459fe48bd2b1bef4855294455af52562dbc0c7f0eb84f8b2cd68692c186417 -CONTENTFUL_PREVIEW_TOKEN=a9972e3cd83528def2fc9d3428c67cd622eb26d0a24239718c6ac61fe0288f2f +CONTENTFUL_SPACE_ID=qz0n5cdakyl9 +CONTENTFUL_DELIVERY_TOKEN=580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8 +CONTENTFUL_PREVIEW_TOKEN=e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b PORT=3000