Commit 53fb7d77 authored by amenaafreen's avatar amenaafreen

initialcommit

parents
version: 2
jobs:
build:
docker:
- image: cypress/base:8
steps:
- checkout
- restore_cache: # special step to restore the dependency cache
key: dependency-cache-{{ checksum "package.json" }}
- run:
name: install-npm-wee
command: npm install
- save_cache: # special step to save the dependency cache
key: dependency-cache-{{ checksum "package.json" }}
paths:
- ./node_modules
- run:
name: clone-e2e-test
command: git clone https://github.com/contentful/the-example-app-e2e-tests.git ./test/e2e
- run:
name: install-e2e-test
command: cd ./test/e2e && npm install
- run:
name: test
command: npm run test
- store_artifacts:
path: /tmp/artifact-1
destination: artifact-file-cypress-result_`date +%Y-%m-%d_%H-%M-%S`
- store_artifacts:
path: /tmp/artifacts
node_modules
npm-debug.log
module.exports = {
'extends': 'standard',
'plugins': [
'standard',
'promise'
],
'env': {
'node': true
},
'rules': {
"capitalized-comments": [
"error",
"always"
],
"spaced-comment": ["error", "always"]
}
}
# cypress test result files
cypress
# e2e test directory
test/e2e
# Created by https://www.gitignore.io/api/vim,code,linux,macos,windows,sublimetext,node
### Code ###
# Visual Studio Code - https://code.visualstudio.com/
.settings/
.vscode/
tsconfig.json
jsconfig.json
### Linux ###
*~
# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*
# KDE directory preferences
.directory
# Linux trash folder which might appear on any partition or disk
.Trash-*
# .nfs files are created when an open file is removed but is still being accessed
.nfs*
### macOS ###
*.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
### SublimeText ###
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
# *.sublime-project
# sftp configuration file
sftp-config.json
# Package control specific files
Package Control.last-run
Package Control.ca-list
Package Control.ca-bundle
Package Control.system-ca-bundle
Package Control.cache/
Package Control.ca-certs/
Package Control.merged-ca-bundle
Package Control.user-ca-bundle
oscrypto-ca-bundle.crt
bh_unicode_properties.cache
# Sublime-github package stores a github token in this file
# https://packagecontrol.io/packages/sublime-github
GitHub.sublime-settings
### Vim ###
# swap
.sw[a-p]
.*.sw[a-p]
# session
Session.vim
# temporary
.netrwhist
# auto-generated tag files
tags
### Windows ###
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/vim,code,linux,macos,windows,sublimetext,node
println "========================"
println "Node Project Build Module from project folder"
MPLModule('Node Build', CFG)
println "========================"
MPLModule('NpmTest Build', CFG)
println "========================"
println "NPM test Project Specific Module"
println "========================"
FROM node:9
WORKDIR /app
RUN npm install -g contentful-cli
COPY package.json .
RUN npm install
COPY . .
USER node
EXPOSE 3000
CMD ["npm", "run", "start:dev"]
@Library('jenkins-shared-library') _
MPLPipeline {}
MIT License
Copyright (c) 2017 Contentful
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
## The node.js example app
[![CircleCI](https://img.shields.io/circleci/project/github/contentful/the-example-app.nodejs.svg)](https://circleci.com/gh/contentful/the-example-app.nodejs)
The node.js example app teaches the very basics of how to work with Contentful:
- consume content from the Contentful Delivery and Preview APIs
- model content
- edit content through the Contentful web app
The app demonstrates how decoupling content from its presentation enables greater flexibility and facilitates shipping higher quality software more quickly.
<a href="https://the-example-app-nodejs.herokuapp.com/" target="_blank"><img src="https://images.contentful.com/qz0n5cdakyl9/4GZmvrdodGM6CksMCkkAEq/700a527b8203d4d3ccd3c303c5b3e2aa/the-example-app.png" alt="Screenshot of the example app"/></a>
You can see a hosted version of `The node.js example app` on <a href="https://the-example-app-nodejs.contentful.com/" target="_blank">Heroku</a>.
## What is Contentful?
[Contentful](https://www.contentful.com) provides a content infrastructure for digital teams to power content in websites, apps, and devices. Unlike a CMS, Contentful was built to integrate with the modern software stack. It offers a central hub for structured content, powerful management and delivery APIs, and a customizable web app that enable developers and content creators to ship digital products faster.
## Requirements
* Node 8
* Git
* Contentful CLI (only for write access)
Without any changes, this app is connected to a Contentful space with read-only access. To experience the full end-to-end Contentful experience, you need to connect the app to a Contentful space with read _and_ write access. This enables you to see how content editing in the Contentful web app works and how content changes propagate to this app.
## Common setup
Clone the repo and install the dependencies.
```bash
git clone https://github.com/contentful/the-example-app.nodejs.git
cd the-example-app.nodejs
```
```bash
npm install
```
## Steps for read-only access
To start the express server, run the following
```bash
npm run start:dev
```
Open [http://localhost:3000](http://localhost:3000) and take a look around.
## Steps for read and write access (recommended)
Step 1: Install the [Contentful CLI](https://www.npmjs.com/package/contentful-cli)
Step 2: Login to Contentful through the CLI. It will help you to create a [free account](https://www.contentful.com/sign-up/) if you don't have one already.
```
contentful login
```
Step 3: Create a new space
```
contentful space create --name 'My space for the example app'
```
Step 4: [Seed](https://github.com/contentful/contentful-cli/tree/master/docs/space/seed) the new space with the example content model [`the-example-app`](https://github.com/contentful/content-models/tree/master/the-example-app). Replace the `SPACE_ID` with the id returned from the create command executed in step 3
```
contentful space seed -s '<SPACE_ID>' -t the-example-app
```
Step 5: Head to the Contentful web app's API section and grab `SPACE_ID`, `DELIVERY_ACCESS_TOKEN`, `PREVIEW_ACCESS_TOKEN`.
Step 6: Open `variables.env` and inject your credentials so it looks like this
```
NODE_ENV=development
CONTENTFUL_SPACE_ID=<SPACE_ID>
CONTENTFUL_DELIVERY_TOKEN=<DELIVERY_ACCESS_TOKEN>
CONTENTFUL_PREVIEW_TOKEN=<PREVIEW_ACCESS_TOKEN>
PORT=3000
```
Step 7: To start the express server, run the following
```bash
npm run start:dev
```
Final Step:
Open [http://localhost:3000?editorial_features=enabled](http://localhost:3000?editorial_features=enabled) and take a look around. 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.
## Deploy to Heroku
You can also deploy this app to Heroku:
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
## Use Docker
You can also run this app as a Docker container:
Step 1: Clone the repo
```bash
git clone https://github.com/contentful/the-example-app.nodejs.git
```
Step 2: Build the Docker image
```bash
docker build -t the-example-app.nodejs .
```
Step 3: Run the Docker container locally:
```bash
docker run -p 3000:3000 -d the-example-app.nodejs
```
If you created your own Contentful space, you can use it by overriding the following environment variables:
```bash
docker run -p 3000:3000 \
-e CONTENTFUL_SPACE_ID=<SPACE_ID> \
-e CONTENTFUL_DELIVERY_TOKEN=<DELIVERY_ACCESS_TOKEN> \
-e CONTENTFUL_PREVIEW_TOKEN=<PREVIEW_ACCESS_TOKEN> \
-d the-example-app.nodejs
```
const path = require('path')
const bodyParser = require('body-parser')
const cookieParser = require('cookie-parser')
const express = require('express')
const logger = require('morgan')
const querystring = require('querystring')
const helmet = require('helmet')
// Load environment variables using dotenv
require('dotenv').config({ path: 'variables.env' })
const helpers = require('./helpers')
const { translate, initializeTranslations, setFallbackLocale } = require('./i18n/i18n')
const breadcrumb = require('./lib/breadcrumb')
const { updateCookie } = require('./lib/cookies')
const settings = require('./lib/settings')
const routes = require('./routes/index')
const { getSpace, getLocales } = require('./services/contentful')
const { catchErrors } = require('./handlers/errorHandlers')
const SETTINGS_NAME = 'theExampleAppSettings'
const app = express()
// View engine setup
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'pug')
app.use(logger('dev'))
app.use(helmet())
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(cookieParser())
app.use(express.static(path.join(__dirname, 'public')))
// Force all requests on production to be served over https
app.use(function (req, res, next) {
if (req.headers['x-forwarded-proto'] !== 'https' && process.env.NODE_ENV === 'production') {
const secureUrl = 'https://' + req.hostname + req.originalUrl
res.redirect(302, secureUrl)
}
next()
})
// Set our application settings based on environment variables or query parameters
app.use(settings)
// Make data available for our views to consume
app.use(catchErrors(async function (request, response, next) {
response.locals.baseUrl = `${request.protocol}://${request.headers.host}`
// Get enabled locales from Contentful
response.locals.locales = [{code: 'en-US', name: 'U.S. English'}]
response.locals.currentLocale = response.locals.locales[0]
// Inject custom helpers
response.locals.helpers = helpers
// Make query string available in templates to render links properly
const cleanQuery = helpers.cleanupQueryParameters(request.query)
const qs = querystring.stringify(cleanQuery)
response.locals.queryString = qs ? `?${qs}` : ''
response.locals.queryStringSettings = response.locals.queryString
response.locals.query = request.query
response.locals.currentPath = request.path
// Initialize translations and include them on templates
initializeTranslations()
response.locals.translate = translate
// Set active api based on query parameter
const apis = [
{
id: 'cda',
label: translate('contentDeliveryApiLabel', response.locals.currentLocale.code)
},
{
id: 'cpa',
label: translate('contentPreviewApiLabel', response.locals.currentLocale.code)
}
]
// Set currently used api
response.locals.currentApi = apis
.find((api) => api.id === (request.query.api || 'cda'))
// Fall back to delivery api if an invalid API is passed
if (!response.locals.currentApi) {
response.locals.currentApi = apis.find((api) => api.id === 'cda')
}
next()
}))
// Test space connection and attach space related data for views if possible
app.use(catchErrors(async function (request, response, next) {
// Catch misconfigured space credentials and display settings page
try {
const space = await getSpace()
const locales = await getLocales()
// Update credentials in cookie when space connection is successful
updateCookie(response, SETTINGS_NAME, response.locals.settings)
// Get available locales from space
response.locals.locales = locales
const defaultLocale = response.locals.locales
.find((locale) => locale.default)
if (request.query.locale) {
response.locals.currentLocale = space.locales
.find((locale) => locale.code === request.query.locale)
}
if (!response.locals.currentLocale) {
response.locals.currentLocale = defaultLocale
}
if (response.locals.currentLocale.fallbackCode) {
setFallbackLocale(response.locals.currentLocale.fallbackCode)
}
// Creates a query string which adds the current credentials to links
// To other implementations of this app in the about modal
helpers.updateSettingsQuery(request, response, response.locals.settings)
} catch (error) {
if ([401, 404].includes(error.response.status)) {
// If we can't connect to the space, force the settings page to be shown.
response.locals.forceSettingsRoute = true
} else {
throw error
}
}
next()
}))
app.use(breadcrumb())
// Initialize the route handling
// Check ./routes/index.js to get a list of all implemented routes
app.use('/', routes)
// Catch 404 and forward to error handler
app.use(function (request, response, next) {
const err = new Error(translate('errorMessage404Route', response.locals.currentLocale.code))
err.status = 404
next(err)
})
// Error handler
app.use(function (err, request, response, next) {
// Set locals, only providing error in development
response.locals.error = err
response.locals.error.status = err.status || 500
if (request.app.get('env') !== 'development') {
delete err.stack
}
response.locals.title = 'Error'
// Render the error page
response.status(err.status || 500)
response.render('error')
})
module.exports = app
{
"name": "The Example App",
"description": "This is \"The Example App\", a reference for building your own applications using Contentful.",
"keywords": [
"contentful",
"example"
],
"website": "https://www.contentful.com/",
"repository": "https://github.com/contentful/the-example-app.nodejs",
"logo": "https://the-example-app-nodejs.herokuapp.com/images/logo-node.svg",
"success_url": "/"
}
#!/usr/bin/env node
/**
* Module dependencies
*/
const app = require('../app')
const http = require('http')
/**
* Get port from environment and store in Express
*/
const port = normalizePort(process.env.PORT || '3000')
app.set('port', port)
/**
* Create HTTP server
*/
const server = http.createServer(app)
/**
* Listen on provided port, on all network interfaces
*/
server.listen(port)
server.on('error', onError)
server.on('listening', onListening)
/**
* Normalize a port into a number, string, or false
*/
function normalizePort (val) {
const port = parseInt(val, 10)
if (isNaN(port)) {
// Named pipe
return val
}
if (port >= 0) {
// Port number
return port
}
return false
}
/**
* Event listener for HTTP server "error" event
*/
function onError (error) {
if (error.syscall !== 'listen') {
throw error
}
const bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port
// Handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges')
process.exit(1)
break
case 'EADDRINUSE':
console.error(bind + ' is already in use')
process.exit(1)
break
default:
throw error
}
}
/**
* Event listener for HTTP server "listening" event
*/
function onListening () {
const addr = server.address()
const uri = typeof addr === 'string' ? addr : `http://localhost:${addr.port}`
console.log(`Listening on ${uri}`)
}
{
"baseUrl": "http://localhost:3007",
"fixturesFolder": false,
"integrationFolder": "./test/e2e/specs",
"supportFile": false
}
/**
* Catch Errors Handler
* Instead of using try{} catch(e) {} in each controller, we wrap the function in
* catchErrors(), catch any errors they throw, and pass it along to our express middleware with next().
*/
module.exports.catchErrors = (fn) => {
return function (request, response, next) {
return fn(request, response, next).catch((e) => {
if (e.response) {
e.status = e.response.status
}
next(e)
})
}
}
const marked = require('marked')
const querystring = require('querystring')
const { translate } = require('./i18n/i18n')
// Parse markdown text
module.exports.markdown = (content = '') => {
if (!content.trim()) {
return ''
}
return marked(removeInvalidDataURL(content), {sanitize: true})
}
// A handy debugging function we can use to sort of "console.log" our data
module.exports.dump = (obj) => JSON.stringify(obj, null, 2)
module.exports.formatMetaTitle = (title, localeCode = 'en-US') => {
if (!title) {
return translate('defaultTitle', localeCode)
}
return `${title.charAt(0).toUpperCase()}${title.slice(1)}${translate('defaultTitle', localeCode)}`
}
function isCustomCredentials (settings) {
const spaceId = process.env.CONTENTFUL_SPACE_ID
const deliveryToken = process.env.CONTENTFUL_DELIVERY_TOKEN
const previewToken = process.env.CONTENTFUL_PREVIEW_TOKEN
return settings.spaceId !== spaceId ||
settings.deliveryToken !== deliveryToken ||
settings.previewToken !== previewToken
}
function cleanupQueryParameters (query) {
const cleanQuery = Object.assign({}, query)
delete cleanQuery.space_id
delete cleanQuery.delivery_token
delete cleanQuery.preview_token
delete cleanQuery.reset
return cleanQuery
}
function updateSettingsQuery (request, response, settings) {
const cleanQuery = cleanupQueryParameters(request.query)
let settingsQuery = Object.assign({}, cleanQuery, {
editorial_features: settings.editorialFeatures ? 'enabled' : 'disabled'
})
if (isCustomCredentials(settings)) {
settingsQuery = Object.assign(settingsQuery, {
space_id: settings.spaceId,
delivery_token: settings.deliveryToken,
preview_token: settings.previewToken
})
}
const settingsQs = querystring.stringify(settingsQuery)
response.locals.queryStringSettings = settingsQs ? `?${settingsQs}` : ''
}
module.exports.isCustomCredentials = isCustomCredentials
module.exports.cleanupQueryParameters = cleanupQueryParameters
module.exports.updateSettingsQuery = updateSettingsQuery
/**
* Evil users might try to add base64 url data to execute js code
* so we should purge any potentially harmful data to mitigate risk
*/
function removeInvalidDataURL (content) {
let regex = /data:\S+;base64\S*/gm
return content.replace(regex, '#')
}
const fs = require('fs')
const path = require('path')
let translations = null
let fallbackLocale = null
/**
* Initializes translation dictionary with contents from /public/locales
*/
function initializeTranslations () {
if (translations) {
return
}
// Default fallbock locale is english
setFallbackLocale('en-US')
translations = {}
const localesPath = path.join(__dirname, 'locales')
try {
const files = fs.readdirSync(localesPath)
.filter((filename) => filename.endsWith('.json'))
files.forEach((file) => {
const localeDict = require(path.join(localesPath, file))
translations[file.replace('.json', '')] = localeDict
})
} catch (error) {
console.error('Error loading localization files:')
console.error(error)
}
}
/**
* Sets the fallback locale
* @param locale string Locale code
*/
function setFallbackLocale (locale) {
fallbackLocale = locale
}
/**
* Translate a static string
* @param symbol string Identifier for static text
* @param locale string Locale code
*
* @returns string
*/
function translate (symbol, locale = 'en-US') {
const localeDict = translations[locale]
let translatedValue
if (localeDict) {
translatedValue = localeDict[symbol]
}
if (!translatedValue) {
translatedValue = translations[fallbackLocale][symbol]
}
if (!translatedValue) {
return `Translation not found for ${symbol}`
}
return translatedValue
}
/**
* Checks if string is translatable
* @param symbol string Identifier for static text
* @param locale string Locale code
*
* @returns boolean
*/
function translationAvaliable (symbol, locale = 'en-US') {
return !!(translations[locale] || translations[fallbackLocale] || {})[symbol]
}
module.exports.initializeTranslations = initializeTranslations
module.exports.setFallbackLocale = setFallbackLocale
module.exports.translate = translate
module.exports.translationAvaliable = translationAvaliable
{
"defaultTitle": "The Example App",
"whatIsThisApp": "Hilfe",
"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.",
"contentDeliveryApiLabel": "Content Delivery API",
"contentDeliveryApiHelp": "Diese API zeigt veröffentlichte Inhalte",
"contentPreviewApiLabel": "Content Preview API",
"contentPreviewApiHelp": "Diese API zeigt unveröffentlichte Inhalte und Änderungen",
"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": "Startseite",
"coursesLabel": "Kurse",
"lessonsLabel": "Lektionen",
"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": "Kontakt",
"modalTitle": "Ein Beispiel für Entwickler, die Contentful benutzen",
"modalTitleDotnet": "Ein Beispiel für .NET Entwickler, die Contentful benutzen",
"modalTitleRuby": "Ein Beispiel für Ruby Entwickler, die Contentful benutzen",
"modalTitlePhp": "Ein Beispiel für PHP Entwickler, die Contentful benutzen",
"modalTitlePython": "Ein Beispiel für Python Entwickler, die Contentful benutzen",
"modalTitleSwift": "Ein Beispiel für Swift Entwickler, die Contentful benutzen",
"modalTitleAndroid": "Ein Beispiel für Android Entwickler, die Contentful benutzen",
"modalTitleJava": "Ein Beispiel für Java Entwickler, die Contentful benutzen",
"modalIntro": "Dies ist \"The Node.js Example App\". Während Sie Ihre eigenen Anwendungen mit Contentful bauen, können Sie dieses Beispiel als Referenz verwenden. Den Quellcode finden Sie auf",
"modalIntroDotnet": "Dies ist \"The .NET Example App\". Während Sie Ihre eigenen Anwendungen mit Contentful bauen, können Sie dieses Beispiel als Referenz verwenden. Den Quellcode finden Sie auf",
"modalIntroRuby": "Dies ist \"The Ruby Example App\". Während Sie Ihre eigenen Anwendungen mit Contentful bauen, können Sie dieses Beispiel als Referenz verwenden. Den Quellcode finden Sie auf",
"modalIntroPhp": "Dies ist \"The PHP Example App\". Während Sie Ihre eigenen Anwendungen mit Contentful bauen, können Sie dieses Beispiel als Referenz verwenden. Den Quellcode finden Sie auf",
"modalIntroPython": "Dies ist \"The Python Example App\". Während Sie Ihre eigenen Anwendungen mit Contentful bauen, können Sie dieses Beispiel als Referenz verwenden. Den Quellcode finden Sie auf",
"modalIntroSwift": "Dies ist \"The Swift Example App\". Während Sie Ihre eigenen Anwendungen mit Contentful bauen, können Sie dieses Beispiel als Referenz verwenden. Den Quellcode finden Sie auf",
"modalIntroAndroid": "Dies ist \"The Android Example App\". Während Sie Ihre eigenen Anwendungen mit Contentful bauen, können Sie dieses Beispiel als Referenz verwenden. Den Quellcode finden Sie auf",
"modalIntroJava": "Dies ist \"The Java Example App\". Während Sie Ihre eigenen Anwendungen mit Contentful bauen, können Sie dieses Beispiel als Referenz verwenden. Den Quellcode finden Sie auf",
"modalSpaceIntro": "Den Inhalt dieser Anwendung können Sie selbst bearbeiten. Dafür müssen Sie den \"The Example App\" space in Ihren Contentful Account importieren. Anleitung hierzu gibt es",
"modalPlatforms": "Diese App ist auch in den folgenden Plattformen und Sprachen verfügbar:",
"modalSpaceLinkLabel": "hier",
"modalCTALabel": "Gut, verstanden.",
"editorialFeaturesHint": "Bearbeiten Sie diesen Eintrag in der Contentful Web App. Sie müssen sich eingelogged haben und Zugang zum Space haben, um diese Funktion nutzen zu können.",
"draftLabel": "Entwurf",
"pendingChangesLabel": "Änderungen verfügbar",
"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": "Schwierigkeit",
"startCourseLabel": "Kurs beginnen",
"categoriesLabel": "Kategorien",
"allCoursesLabel": "Alle Kurse",
"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!",
"errorOccurredTitleLabel": "Fehler aufgetreten",
"errorOccurredMessageLabel": "Einige Fehler sind aufgetreten. Bitte schauen Sie sich die Fehlermeldungen neben den Feldern an.",
"connectedToSpaceLabel": "Verbundener Space ",
"spaceIdLabel": "Space ID",
"spaceIdHelpText": "Die Space Id ist eine eindeutige Identifizierung für Ihren Space.",
"accessTokenLabel": "Access Token",
"cdaAccessTokenLabel": "Content Delivery API Access Token",
"cpaAccessTokenLabel": "Content Preview API Access Token",
"contentDeliveryApiHelpText": "Schauen Sie sich veröffentlichten Inhalt mit diesem Token an.",
"contentPreviewApiHelpText": "Schauen Sie sich veröffentlichten Inhalt mit diesem Token an. (z.B. Inhalt im Zustand “Entwurf”).",
"enableEditorialFeaturesLabel": "Editoriale Funktionen aktivieren",
"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.",
"spaceOrTokenInvalid": "Dieser Space existiert nicht, oder Ihr Access Token kommt nicht von diesem Space.",
"previewKeyInvalidLabel": "Ihr Preview API Zugangsschlüssel ist ungültig.",
"beginnerLabel": "Anfänger",
"intermediateLabel": "Fortgeschrittener",
"advancedLabel": "Experte",
"editInTheWebAppLabel": "In der Contentful web app bearbeiten",
"currentLocaleLabel": "Deutsch (Deutschland)",
"hostedLabel": "Gehostet",
"comingSoonLabel": "Bald verfügbar",
"credentialSourceLabel": "Ursprung der Zugangsdaten:",
"readMoreLabel": "Weiterlesen",
"credentialsLocationLabel": "Ihre Zugangsdaten werden derzeit aus",
"overwriteCredentialsLabel": "Sie können überschrieben werden, indem Sie das Formular unten oder einen Querystring Parameter verwenden.",
"copyLinkLabel": "Einen Link zum Verteilen kopieren",
"resetCredentialsLabel": "Zugangsdaten zurücksetzen",
"resetAboveLabel": "Sie können auf voreingestellten Zugangsdaten zurücksetzen.",
"closeLabel": "Schließen",
"overrideConfigLabel": "Diese Konfiguration kann durch das folgende Formular oder durch Verwendung von Querystring Parameter überschrieben werden.",
"loadedFromLocalFileLabel": "Geladen von der lokalen Datei",
"usingServerCredentialsLabel": "Die Beispielanwendung verwendet derzeit serverseitig gespeicherte Anmeldeinformationen zum Herstellen einer Verbindung mit einem Contentful-Space.",
"usingSessionCredentialsLabel": "Die Beispielanwendung verwendet derzeit Anmeldeinformationen von der Anwendungssitzung, um eine Verbindung zu einem Contentful-Space herzustellen.",
"applicationCredentialsLabel": "Anmeldeinformationen für die Anwendungssitzung",
"noContentLabel": "Keinen Inhalt gefunden.",
"errorHighlightedCourse": "⚠️ Dieser Kurs wurde nicht veröffentlicht oder existiert nicht.",
"somethingWentWrongLabel": "Hmm, etwas ging schief.",
"errorMessage404Route": "Diese Seite existiert nicht.",
"errorMessage404Lesson": "Diese Lektion existiert nicht.",
"errorMessage404Course": "Dieser Kurs existiert nicht.",
"errorMessage404Category": "Diese Kategorie existiert nicht.",
"hintsLabel": "Versuchen Sie Folgendes um die Probleme zu lösen:",
"notFoundErrorHint": "Überprüfen Sie, ob dieser Inhalt existiert und veröffentlicht wurde.",
"contentModelChangedErrorHint": "Überprüfen Sie, ob die Struktur der Inhalte geändert wurde. Haben Sie einen Inhaltstyp oder ein benötigtes Feld gelöscht?",
"draftOrPublishedErrorHint": "Überprüfen Sie, ob der Inhalt veröffentlicht wurde, Änderungen enthält (Content Delivery API) oder gelöscht wurde (Content Preview API & Content Delivery API).",
"localeContentErrorHint": "Überprüfen Sie, ob alle benötigten Felder dieser Sprache befüllt wurden.",
"verifyCredentialsErrorHint": "Überprüfen Sie, ob die Space ID und die Access Tokens richtig und nicht abgelaufen sind.",
"stackTraceErrorHint": "Schauen Sie sich den folgenden Stack Trace an.",
"errorLabel": "Fehler von Contentful:",
"stackTraceLabel": "Stack trace:"
}
\ No newline at end of file
{
"defaultTitle": "The Example App",
"whatIsThisApp": "Help",
"metaDescription": "This is \"The Example App\", a reference for building your own applications using Contentful.",
"metaTwitterCard": "This is \"The Example App\", a reference for building your own applications using Contentful.",
"metaImageAlt": "This is \"The Example App\", a reference for building your own applications using Contentful.",
"metaImageDescription": "This is \"The Example App\", a reference for 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.",
"contentDeliveryApiLabel": "Content Delivery API",
"contentDeliveryApiHelp": "This API fetches published content from the Content Delivery API",
"contentPreviewApiLabel": "Content Preview API",
"contentPreviewApiHelp": "This API fetches un-published content from the Content Preview API",
"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",
"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",
"modalTitle": "A reference for Node.js developers using Contentful",
"modalTitleDotnet": "A reference for .NET developers using Contentful",
"modalTitleRuby": "A reference for Ruby developers using Contentful",
"modalTitlePhp": "A reference for PHP developers using Contentful",
"modalTitlePython": "A reference for Python developers using Contentful",
"modalTitleSwift": "A reference for Swift developers using Contentful",
"modalTitleAndroid": "A reference for Android developers using Contentful",
"modalTitleJava": "A reference for Java developers using Contentful",
"modalIntro": "This is \"The Node.js Example App\". While building your own apps with Contentful, you can reference this app's code, found on",
"modalIntroDotnet": "This is \"The .NET Example App\". While building your own apps with Contentful, you can reference this app's code, found on",
"modalIntroRuby": "This is \"The Ruby Example App\". While building your own apps with Contentful, you can reference this app's code, found on",
"modalIntroPhp": "This is \"The PHP Example App\". While building your own apps with Contentful, you can reference this app's code, found on",
"modalIntroPython": "This is \"The Python Example App\". While building your own apps with Contentful, you can reference this app's code, found on",
"modalIntroSwift": "This is \"The Swift Example App\". While building your own apps with Contentful, you can reference this app's code, found on",
"modalIntroAndroid": "This is \"The Android Example App\". While building your own apps with Contentful, you can reference this app's code, found on",
"modalIntroJava": "This is \"The Java Example App\". While building your own apps with Contentful, you can reference this app's code, found on",
"modalSpaceIntro": "You can also edit the content in the app by cloning the Contentful space to your own Contentful account by following the instructions",
"modalPlatforms": "This app is also available in the following platforms and languages:",
"modalSpaceLinkLabel": "here",
"modalCTALabel": "Ok, got it.",
"editorialFeaturesHint": "Edit this entry in the Contentful 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": "Go to the next lesson",
"imageErrorTitle": "⚠️ Image missing",
"viewCourseLabel": "view course",
"categoriesWelcomeLabel": "Welcome to",
"sitemapWelcomeLabel": "Welcome to",
"tableOfContentsLabel": "Table of contents",
"courseOverviewLabel": "Course overview",
"overviewLabel": "Overview",
"durationLabel": "Duration",
"minutesLabel": "min",
"skillLevelLabel": "Skill level",
"startCourseLabel": "Start course",
"categoriesLabel": "Categories",
"allCoursesLabel": "All courses",
"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!",
"errorOccurredTitleLabel": "Error occurred",
"errorOccurredMessageLabel": "Some errors occurred. Please check the error messages next to the fields.",
"connectedToSpaceLabel": "Connected space",
"spaceIdLabel": "Space ID",
"spaceIdHelpText": "The Space ID is a unique identifier for your space.",
"accessTokenLabel": "access token",
"cdaAccessTokenLabel": "Content Delivery API access token",
"cpaAccessTokenLabel": "Content Preview API access token",
"contentDeliveryApiHelpText": "View published content using this token.",
"contentPreviewApiHelpText": "Preview unpublished content using this token (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.",
"previewKeyInvalidLabel": "Your Preview API key is invalid.",
"beginnerLabel": "Beginner",
"intermediateLabel": "Intermediate",
"advancedLabel": "Advanced",
"editInTheWebAppLabel": "Edit in the Contentful web app",
"currentLocaleLabel": "English (United States)",
"hostedLabel": "Hosted",
"comingSoonLabel": "Coming soon",
"credentialSourceLabel": "Credentials source",
"readMoreLabel": "Read more",
"credentialsLocationLabel": "Your credentials are currently loaded from",
"overwriteCredentialsLabel": "They can be overwritten by changing them below or using query string parameters.",
"copyLinkLabel": "Copy session deeplink to clipboard",
"resetCredentialsLabel": "Reset credentials to default",
"resetAboveLabel": "You can reset to the default credentials above.",
"closeLabel": "Close",
"overrideConfigLabel": "This configuration can be overridden through the form below or by using query string parameters.",
"loadedFromLocalFileLabel": "Loaded from local file",
"usingServerCredentialsLabel": "The example app is currently using server side stored credentials to connect to a Contentful space.",
"usingSessionCredentialsLabel": "The example app is currently using application session stored credentials to connect to a Contentful space.",
"applicationCredentialsLabel": "Application session credentials",
"noContentLabel": "No content found.",
"errorHighlightedCourse": "⚠️ The course is not published or does not exist.",
"somethingWentWrongLabel": "Oops, something went wrong",
"errorMessage404Route": "The page you are trying to open does not exist.",
"errorMessage404Lesson": "The lesson you are trying to open does not exist.",
"errorMessage404Course": "The course you are trying to open does not exist.",
"errorMessage404Category": "The category you are trying to open does not exist.",
"hintsLabel": "Try the following to fix the issue(s):",
"notFoundErrorHint": "Make sure the content you are trying to access exists and is published.",
"contentModelChangedErrorHint": "Check if the content model has changed. Did you delete a content type or a required field?",
"draftOrPublishedErrorHint": "Check if the content is in a draft or pending changes state (Content Delivery API), or if it has been deleted (Content Preview API & Content Delivery API).",
"localeContentErrorHint": "Check if all required fields have content for this locale.",
"verifyCredentialsErrorHint": "Verify that your space id and access tokens are correct and up to date.",
"stackTraceErrorHint": "Check the stack trace below.",
"errorLabel": "Error from Contentful:",
"stackTraceLabel": "Stack trace:"
}
\ No newline at end of file
const url = require('url')
const { translate, translationAvaliable } = require('../i18n/i18n')
module.exports = (modifier) => {
return (request, response, next) => {
const baseUrl = url.format({ protocol: request.protocol, host: request.get('host') })
const urlComponents = url.parse(request.url).pathname.split('/').filter(Boolean)
let breadcrumbs = []
breadcrumbs.push({
label: translate('homeLabel', response.locals.currentLocale.code),
url: baseUrl
})
// 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: 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()
}
}
const TWO_DAYS_IN_MILLISECONDS = 172800000 // 1000 * 60 * 60 * 24 * 2
module.exports.updateCookie = (response, cookieName, value) => {
response.cookie(cookieName, value, { maxAge: TWO_DAYS_IN_MILLISECONDS, httpOnly: true })
}
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
}
const { getEntry } = require('./../services/contentful')
const { isObject, isArray } = require('lodash')
async function getPublishedEntry (entry) {
try {
return await getEntry(entry.sys.id, entry.sys.contentType.sys.id)
} catch (err) {
return null
}
}
module.exports = async function attachEntryState (entry) {
const publishedEntry = await getPublishedEntry(entry)
entry.draft = false
entry.pendingChanges = false
// If there is no published version, it is a draft and can't have pending changes
if (!publishedEntry) {
entry.draft = true
return entry
}
// We group fields of type link (i.e. Only objects/array) from the same entry in preview and delivery
const entriesToCompare = Object.keys(entry.fields).map((key) => {
const field = entry.fields[key]
if (isObject(field)) {
return [entry.fields[key], publishedEntry.fields[key]]
}
}).filter(Boolean)
entriesToCompare.forEach((item) => {
const originalItem = item[0]
const publishedItem = item[1]
// If the field is an array of reference we need to check its item if they have pending changes
if (isArray(originalItem)) {
originalItem.forEach((innerItem, index) => {
if (!isArray(innerItem)) {
setEntryState(entry, innerItem, publishedItem[index])
return
}
})
} else {
// If the field is a single reference we just check if it has pending changes
setEntryState(entry, originalItem, publishedItem)
}
})
// We check if the root element has pending changes
if (entry && publishedEntry) {
setEntryState(entry, entry, publishedEntry)
}
return entry
}
function isLinkDraft (previewLink, deliveryLink) {
return previewLink && !deliveryLink
}
function isLinkPendingChanges (previewLink, deliveryLink) {
if (previewLink && deliveryLink) {
return !isDateEqual(previewLink.sys.updatedAt, deliveryLink.sys.updatedAt)
}
return false
}
function isDateEqual (lhs, rhs) {
const lhsDate = new Date(lhs)
const rhsDate = new Date(rhs)
return lhsDate.setMilliseconds(0) === rhsDate.setMilliseconds(0)
}
function setEntryState (entry, originalItem, publishedItem) {
if (isLinkDraft(originalItem, publishedItem)) {
entry.draft = true
}
if (isLinkPendingChanges(originalItem, publishedItem)) {
entry.pendingChanges = true
}
return entry
}
/**
* The purpose of this middleware is to set our application settings based
* on environment variables or query parameters
*/
const { initClients } = require('../services/contentful')
module.exports = async function settingsMiddleware (request, response, next) {
// Set default settings based on environment variables
let settings = {
spaceId: process.env.CONTENTFUL_SPACE_ID,
deliveryToken: process.env.CONTENTFUL_DELIVERY_TOKEN,
previewToken: process.env.CONTENTFUL_PREVIEW_TOKEN,
editorialFeatures: false,
// Overwrite default settings using those stored in cookie, if present
...request.cookies.theExampleAppSettings
}
// Allow setting of API credentials via query parameters
const { space_id, preview_token, delivery_token } = request.query
if (space_id && preview_token && delivery_token) { // eslint-disable-line camelcase
settings = {
...settings,
spaceId: space_id,
deliveryToken: delivery_token,
previewToken: preview_token
}
}
// Allow enabling and disabling of editorial features via query parameters
/* eslint-disable camelcase */
const { editorial_features } = request.query
if (typeof editorial_features !== 'undefined') {
delete request.query.editorial_features
settings.editorialFeatures = editorial_features === 'enabled'
}
/* eslint-enable camelcase */
initClients(settings)
response.locals.settings = settings
next()
}
module.exports = (response) => {
return response.locals.settings.editorialFeatures && response.locals.currentApi.id === 'cpa'
}
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
apiVersion: v1
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: node-app-chart
version: 0.3.0
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
labels:
"app.kubernetes.io/name": {{ .Release.Name }}
"helm.sh/chart": "{{ .Chart.Name }}-{{ .Chart.Version }}"
"app.kubernetes.io/managed-by": "{{ .Release.Service }}"
"app.kubernetes.io/instance": "{{ .Release.Name }}"
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
"app.kubernetes.io/name": {{ .Release.Name }}
"app.kubernetes.io/instance": "{{ .Release.Name }}"
template:
metadata:
labels:
"app.kubernetes.io/name": {{ .Release.Name }}
"helm.sh/chart": "{{ .Chart.Name }}-{{ .Chart.Version }}"
"app.kubernetes.io/managed-by": "{{ .Release.Service }}"
"app.kubernetes.io/instance": "{{ .Release.Name }}"
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.imagePullRepository }}:{{ .Values.imagePullTag }}"
imagePullPolicy: {{ .Values.imagePullPolicy }}
ports:
- name: http
containerPort: 3000
protocol: TCP
{{- if .Values.imagePullSecretName }}
imagePullSecrets:
- name: {{ .Values.imagePullSecretName }}
{{- end -}}
{{- if .Values.ingress.enabled }}
apiVersion: {{ .Values.ingress.apiVersion }}
kind: Ingress
metadata:
namespace: {{ .Release.Namespace }}
labels:
"app.kubernetes.io/name": {{ .Release.Name }}
"helm.sh/chart": "{{ .Chart.Name }}-{{ .Chart.Version }}"
"app.kubernetes.io/managed-by": "{{ .Release.Service }}"
"app.kubernetes.io/instance": "{{ .Release.Name }}"
{{- if .Values.ingress.labels }}
{{ toYaml .Values.ingress.labels | indent 4 }}
{{- end }}
{{- if .Values.ingress.annotations }}
annotations:
{{ toYaml .Values.ingress.annotations | indent 4 }}
{{- end }}
name: {{ .Release.Name }}
spec:
rules:
- http:
paths:
- backend:
serviceName: {{ .Release.Name }}
servicePort: {{ .Values.servicePort }}
{{- if .Values.ingress.path }}
path: {{ .Values.ingress.path }}
{{- end -}}
{{- if .Values.ingress.hostName }}
host: {{ .Values.ingress.hostName | quote }}
{{- end }}
{{- if .Values.ingress.tls }}
tls:
{{ toYaml .Values.ingress.tls | indent 4 }}
{{- end -}}
{{- end }}
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
labels:
"app.kubernetes.io/name": {{ .Release.Name }}
"helm.sh/chart": "{{ .Chart.Name }}-{{ .Chart.Version }}"
"app.kubernetes.io/managed-by": "{{ .Release.Service }}"
"app.kubernetes.io/instance": "{{ .Release.Name }}"
spec:
type: {{ .Values.serviceType }}
ports:
- port: {{ .Values.servicePort }}
targetPort: {{ .Values.targetPort }}
name: http
selector:
app.kubernetes.io/name: {{ .Release.Name }}
app.kubernetes.io/instance: "{{ .Release.Name }}"
#Default Values for application
replicaCount: 1
#define the repository path for the image
imagePullRepository: nexus.mynisum.com:2376/nisum/node-app
imagePullTag: 0.0.1
imagePullPolicy: Always
imagePullSecretName: registry-cred
#Define service type and servicePort
serviceType: ClusterIP
servicePort: 3000
targetPort: 3000
#Ingress
ingress:
enabled: false
# For Kubernetes v1.14+, use 'networking.k8s.io/v1beta1'
apiVersion: "extensions/v1beta1"
labels: {}
annotations:
kubernetes.io/ingress.class: nginx
kubernetes.io/tls-acme: "true"
# configures the hostname e.g. web-app-gradle.mynisum.com
hostName: node-app.mynisum.com
tls:
- hosts:
- node-app.mynisum.com
secretName: mynisum-tls
rules:
- host: node-app.mynisum.com
http:
paths:
- backend:
serviceName: node-app
servicePort: 3000
path: /
This diff is collapsed.
{
"name": "example-contentful-theExampleApp-js",
"version": "0.0.3",
"private": true,
"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",
"format": "eslint --fix . bin --ignore public node_modules",
"pretest": "npm run lint",
"test": "npm run test:unit && npm run test:integration && npm run test:e2e",
"test:e2e": "node test/run-e2e-test.js",
"test:e2e:dev": "node test/run-e2e-test.js --dev",
"test:integration": "jest test/integration",
"test:integration:watch": "jest test/integration --watch",
"test:unit": "jest test/unit",
"test:unit:watch": "jest test/unit --watch"
},
"engines": {
"node": ">=8.9.3"
},
"dependencies": {
"body-parser": "^1.18.2",
"contentful": "^6.0.0",
"cookie-parser": "~1.4.3",
"dotenv": "^5.0.0",
"execa": "^0.9.0",
"express": "^4.16.2",
"helmet": "^3.11.0",
"lodash": "^4.17.5",
"marked": "^0.3.16",
"morgan": "^1.9.1",
"pug": "~2.0.0-beta6"
},
"devDependencies": {
"cheerio": "^1.0.0-rc.2",
"cookie": "^0.3.1",
"eslint": "^4.18.1",
"eslint-config-standard": "^11.0.0",
"eslint-plugin-import": "^2.8.0",
"eslint-plugin-node": "^6.0.0",
"eslint-plugin-promise": "^3.6.0",
"eslint-plugin-standard": "^3.0.1",
"jest": "^22.4.0",
"nodemon": "^1.18.9",
"supertest": "^3.0.0",
"yargs": "^11.0.0"
}
}
build:
buildType: npm
arguments: install -g contentful-cli
testArguments: test
docker:
dockerArguments:
artifactId: node-web
dockerImageName: node-app
clusterConfig:
credentialsId: KUBECONFIG-ID
namespace: dev-ns
helmConfig:
releaseName: node-app
helmChartName: node-app-chart
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
<?xml version="1.0" encoding="utf-8"?><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><symbol viewBox="0 0 5 8" id="arrow" xmlns="http://www.w3.org/2000/svg"><path fill="none" stroke="currentColor" stroke-width="1.5" d="M1 7l3-2.89L1 1"/></symbol><symbol viewBox="0 0 10 5" id="arrow-filled" xmlns="http://www.w3.org/2000/svg"><path d="M0 0l5 5 5-5z"/></symbol><symbol viewBox="0 0 24 24" id="cross" xmlns="http://www.w3.org/2000/svg"><path d="M23.707 22.293L13.414 12 23.706 1.707A1 1 0 1 0 22.292.293L12 10.586 1.706.292A1 1 0 0 0 .292 1.706L10.586 12 .292 22.294a1 1 0 1 0 1.414 1.414L12 13.414l10.293 10.292a1 1 0 1 0 1.414-1.413z" fill-rule="nonzero" fill="currentColor"/></symbol><symbol viewBox="0 0 20 20" id="done" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M0 0h20v20H0z"/><path fill="currentColor" 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"/><path stroke="currentColor" stroke-width="1.333" d="M13.333 7.5l-5 5-1.666-1.786"/></g></symbol><symbol viewBox="0 0 24 24" id="duration" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="currentColor" fill-rule="nonzero" d="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"/><path d="M0 0h24v24H0z"/><path fill="currentColor" fill-rule="nonzero" d="M12.5 7H11v6l5.25 3.15.75-1.23-4.5-2.67z"/></g></symbol><symbol viewBox="0 0 20 20" id="error" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M-2-2h24v24H-2z"/><path fill="currentColor" fill-rule="nonzero" d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z"/></g></symbol><symbol viewBox="0 0 24 24" id="facebook" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="currentColor" fill-rule="nonzero" d="M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12 12-5.383 12-12S18.617 0 12 0zm3.595 11.5H13.5v7h-3v-7h-2v-2h2V8.336c0-1.1.352-2.819 2.649-2.819l2.351.007V7.83h-1.408c-.244 0-.592.123-.592.647V9.5h2.339l-.244 2z"/><path d="M0 0h24v24H0z"/></g></symbol><symbol viewBox="0 0 33 33" id="github" xmlns="http://www.w3.org/2000/svg"><path d="M16.608.455C7.614.455.32 7.748.32 16.745c0 7.197 4.667 13.302 11.14 15.456.815.15 1.112-.353 1.112-.785 0-.386-.014-1.411-.022-2.77-4.531.984-5.487-2.184-5.487-2.184-.741-1.882-1.809-2.383-1.809-2.383-1.479-1.01.112-.99.112-.99 1.635.115 2.495 1.679 2.495 1.679 1.453 2.489 3.813 1.77 4.741 1.353.148-1.052.569-1.77 1.034-2.177-3.617-.411-7.42-1.809-7.42-8.051 0-1.778.635-3.233 1.677-4.371-.168-.412-.727-2.069.16-4.311 0 0 1.367-.438 4.479 1.67a15.602 15.602 0 0 1 4.078-.549 15.62 15.62 0 0 1 4.078.549c3.11-2.108 4.475-1.67 4.475-1.67.889 2.242.33 3.899.163 4.311C26.37 12.66 27 14.115 27 15.893c0 6.258-3.809 7.635-7.437 8.038.584.503 1.105 1.497 1.105 3.017 0 2.177-.02 3.934-.02 4.468 0 .436.294.943 1.12.784 6.468-2.159 11.131-8.26 11.131-15.455 0-8.997-7.294-16.29-16.291-16.29" fill="currentColor" fill-rule="evenodd"/></symbol><symbol viewBox="0 0 20 20" id="info" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M-2 22h24V-2H-2z"/><path fill="currentColor" fill-rule="nonzero" d="M10 20C4.48 20 0 15.52 0 10S4.48 0 10 0s10 4.48 10 10-4.48 10-10 10zm1-15H9v2h2V5zm0 4H9v6h2V9z" opacity=".6"/></g></symbol><symbol viewBox="0 0 24 24" id="menu-hamburger" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M0 0h24v24H0z"/><path fill="currentColor" fill-rule="nonzero" d="M3 18h18v-2H3v2zm0-5h18v-2H3v2zm0-7v2h18V6H3z"/></g></symbol><symbol viewBox="0 0 24 24" id="skill-level" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="currentColor" fill-rule="nonzero" d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM9 17H7v-7h2v7zm4 0h-2V7h2v10zm4 0h-2v-4h2v4z"/><path d="M0 0h24v24H0z"/></g></symbol><symbol viewBox="0 0 20 20" id="success" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M-1-1h582v402H-1z"/><path fill="currentColor" fill-rule="evenodd" d="M10 15.5c-2.33 0-4.31-1.46-5.11-3.5h10.22c-.8 2.04-2.78 3.5-5.11 3.5M13.5 9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 1 1 0 3m-7 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 1 1 0 3m3.5 9c4.42 0 8-3.58 8-8s-3.58-8-8-8-8 3.58-8 8 3.58 8 8 8m.01-18C15.53 0 20 4.48 20 10s-4.47 10-9.99 10C4.48 20 0 15.52 0 10S4.48 0 10.01 0"/></symbol><symbol viewBox="0 0 24 24" id="twitter" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path fill="currentColor" fill-rule="nonzero" d="M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12 12-5.383 12-12S18.617 0 12 0zm5.262 9.385c.006.113.008.226.008.34 0 3.477-2.647 7.489-7.488 7.489a7.452 7.452 0 0 1-4.035-1.184 5.294 5.294 0 0 0 3.897-1.089 2.637 2.637 0 0 1-2.458-1.829c.397.075.803.061 1.188-.046a2.631 2.631 0 0 1-2.111-2.579v-.035c.355.197.762.315 1.192.33a2.632 2.632 0 0 1-.815-3.514 7.463 7.463 0 0 0 5.425 2.75 2.632 2.632 0 0 1 4.486-2.399c.6-.119 1.95-.266 1.95-.266-.354.529-.725 1.66-1.239 2.032z"/><path d="M0 0h24v24H0z"/></g></symbol></svg>
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="138" height="44" viewBox="0 0 138 44">
<defs>
<path id="a" d="M0 0h138v44H0z"/>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="b" fill="#fff">
<use xlink:href="#a"/>
</mask>
<g fill-rule="nonzero" mask="url(#b)">
<path fill="#000" d="M3.075 0A3.072 3.072 0 0 0 0 3.07v37.86A3.072 3.072 0 0 0 3.075 44h131.711a3.073 3.073 0 0 0 3.075-3.07V3.07A3.073 3.073 0 0 0 134.786 0H3.075z"/>
<path d="M40.639 8.24v6.821h2.292c2.232 0 3.018-1.449 3.018-3.418 0-1.998-.816-3.402-3.018-3.402H40.64zm1.031.935h1.166c1.507 0 2.052 1.005 2.052 2.483 0 1.69-.666 2.464-2.052 2.464H41.67V9.175zm7.843.52c-1.352 0-2.318.934-2.318 2.373v.695c0 1.534.951 2.388 2.318 2.388 1.346 0 2.312-.864 2.312-2.388v-.695c0-1.484-.966-2.373-2.312-2.373zm0 .874c.815 0 1.29.66 1.29 1.554v.595c0 .899-.45 1.549-1.29 1.549-.851 0-1.307-.65-1.307-1.55v-.594c0-.894.49-1.554 1.307-1.554zm10.354-.784h-1.01l-.906 4.037h-.045l-1.137-4.037h-.95l-1.112 4.037h-.04l-.89-4.037h-1.101l1.461 5.276h1.026l1.116-3.912h.03l1.146 3.912h1.016zm1.132 5.276h1.01v-2.943c0-.864.441-1.504 1.317-1.504.666 0 1.086.375 1.086 1.184v3.263h1.016v-3.572c0-1.2-.72-1.784-1.787-1.784-1.086 0-1.456.614-1.591 1.024H62v-.944h-1.001v5.276zm6.161 0h1.01v-7.18h-1.01zm4.834-5.366c-1.351 0-2.317.934-2.317 2.373v.695c0 1.534.95 2.388 2.317 2.388 1.346 0 2.312-.864 2.312-2.388v-.695c0-1.484-.966-2.373-2.312-2.373zm0 .874c.816 0 1.291.66 1.291 1.554v.595c0 .899-.45 1.549-1.29 1.549-.852 0-1.307-.65-1.307-1.55v-.594c0-.894.49-1.554 1.306-1.554zm5.226 4.577c.78 0 1.245-.365 1.49-.77h.046v.685h.966v-3.577c0-1.314-.916-1.78-2.027-1.78-1.247 0-1.932.585-2.002 1.465h.966c.095-.35.37-.59 1.006-.59.64 0 1.046.275 1.046.89v.5h-1.357c-1.216 0-1.861.629-1.861 1.558 0 .984.705 1.619 1.726 1.619zm.3-.84c-.526 0-1.006-.274-1.006-.834 0-.44.29-.77.965-.77h1.232v.58c0 .585-.52 1.025-1.191 1.025zm5.685-4.601c-1.241 0-2.032.88-2.032 2.253v.925c0 1.399.8 2.248 2.012 2.248.89 0 1.441-.455 1.576-.91h.05v.84h.976v-7.18h-1.01v2.688h-.05c-.176-.454-.671-.864-1.522-.864zm.245.9c.776 0 1.327.554 1.327 1.368v.815c0 .904-.516 1.454-1.302 1.454-.78 0-1.281-.54-1.281-1.524v-.595c0-.97.485-1.519 1.256-1.519zm8.463-.91c-1.351 0-2.317.934-2.317 2.373v.695c0 1.534.951 2.388 2.317 2.388 1.347 0 2.313-.864 2.313-2.388v-.695c0-1.484-.966-2.373-2.313-2.373zm0 .874c.816 0 1.292.66 1.292 1.554v.595c0 .899-.45 1.549-1.292 1.549-.85 0-1.306-.65-1.306-1.55v-.594c0-.894.49-1.554 1.306-1.554zm3.789 4.492h1.01v-2.943c0-.864.441-1.504 1.317-1.504.666 0 1.086.375 1.086 1.184v3.263h1.016v-3.572c0-1.2-.72-1.784-1.786-1.784-1.086 0-1.457.614-1.592 1.024h-.05v-.944h-1.001v5.276zm8.763-5.246h-.815v.864h.805v2.898c0 1.06.31 1.524 1.582 1.524.225 0 .596-.03.676-.05v-.834c-.085.01-.286.02-.426.02-.615 0-.826-.16-.826-.74V10.68h1.187v-.864h-1.187v-1.32h-.996v1.32zm3.629 5.246h1.006v-2.943c0-.864.465-1.504 1.311-1.504.666 0 1.116.385 1.116 1.194v3.253h1.011v-3.562c0-1.205-.746-1.794-1.767-1.794-1.08 0-1.511.654-1.616 1.024h-.055V7.881h-1.006v7.18zm8.193.089c1.376 0 2.017-.873 2.092-1.478h-1.011c-.085.325-.49.6-1.091.6-.826 0-1.286-.57-1.286-1.334v-.255h3.453v-.625c0-1.449-.926-2.363-2.227-2.363-1.276 0-2.227.874-2.227 2.363v.725c0 1.484.865 2.368 2.297 2.368zm-1.297-3.182v-.075c0-.804.491-1.344 1.232-1.344.74 0 1.226.54 1.226 1.344v.075h-2.457zM41.673 33.512l1.161-3.678h4.715l1.161 3.678h2.593l-4.655-13.64h-2.753l-4.664 13.64h2.442zm3.574-11.192l1.781 5.676h-3.663l1.792-5.676h.09zM58.48 33.652c2.432 0 4.023-1.72 4.023-4.787v-1.269c0-3.078-1.611-4.807-3.983-4.807-1.652 0-2.723.7-3.144 1.67h-.09v-1.53h-2.312v14.15h2.363v-5.056h.1c.44.9 1.4 1.629 3.043 1.629zm-.721-1.919c-1.522 0-2.422-1.3-2.422-2.998v-1.08c0-1.778.91-2.947 2.382-2.947 1.461 0 2.372 1.07 2.372 3.068v.89c0 2.058-.96 3.067-2.332 3.067zm12.531 1.919c2.433 0 4.025-1.72 4.025-4.787v-1.269c0-3.078-1.612-4.807-3.984-4.807-1.652 0-2.723.7-3.143 1.67h-.09v-1.53h-2.312v14.15h2.362v-5.056h.1c.44.9 1.401 1.629 3.043 1.629zm-.72-1.919c-1.521 0-2.422-1.3-2.422-2.998v-1.08c0-1.778.91-2.947 2.382-2.947 1.462 0 2.372 1.07 2.372 3.068v.89c0 2.058-.96 3.067-2.332 3.067zm10.31-2.039c.04 1.86 1.321 4.088 5.175 4.088 3.163 0 5.325-1.54 5.325-4.217 0-2.369-1.591-3.338-3.633-3.808l-2.202-.53c-1.252-.29-1.882-.899-1.882-1.768 0-1.21 1.02-1.929 2.542-1.929 1.722 0 2.593.94 2.683 2.049h2.302c-.04-2.508-2.212-4.007-4.965-4.007-2.702 0-4.995 1.449-4.995 4.077 0 2.238 1.592 3.297 3.394 3.707l2.192.53c1.441.35 2.142.88 2.142 1.888 0 1.24-1.001 2.039-2.733 2.039-1.892 0-2.892-.98-2.993-2.119H79.88zm13.523-6.674h-1.521v1.848h1.481v5.666c0 2.128.76 3.048 3.363 3.048.48 0 .911-.05 1.101-.08v-1.789c-.13.02-.32.02-.52.02-1.161 0-1.602-.32-1.602-1.439v-5.426h2.062v-1.849h-2.062v-2.638h-2.302v2.638zm10.7-.26c-3.183 0-4.764 2.168-4.764 5.076v.75c0 3.107 1.581 5.106 4.764 5.106 3.183 0 4.755-2.019 4.755-5.107v-.76c0-2.987-1.572-5.066-4.755-5.066zm0 1.858c1.832 0 2.363 1.579 2.363 3.298v.61c0 1.708-.52 3.287-2.363 3.287-1.851 0-2.392-1.589-2.392-3.288v-.61c0-1.708.55-3.297 2.392-3.297zm7.127 8.894h2.403v-6.216c0-1.419.6-2.458 2.362-2.458.39 0 .79.02.98.08v-2.069c-.21-.04-.46-.07-.78-.07-1.672 0-2.252.87-2.512 1.53h-.09v-1.37h-2.363v10.573zm11.541.179c2.993 0 4.164-1.858 4.334-3.277h-2.282c-.14.74-.77 1.409-2.062 1.409-1.611 0-2.392-1.28-2.392-2.748v-.33h6.847v-.95c0-2.997-1.602-5.036-4.585-5.036-2.893 0-4.594 1.979-4.594 5.047v.89c0 3.067 1.681 4.995 4.734 4.995zm-2.402-6.535v-.02c0-1.479.86-2.558 2.282-2.558 1.412 0 2.262 1.07 2.262 2.558v.02h-4.544zM25.213 22.811c-.03-3.282 2.683-4.88 2.806-4.953-1.535-2.244-3.915-2.55-4.752-2.575-2-.21-3.938 1.199-4.957 1.199-1.038 0-2.607-1.179-4.297-1.144-2.175.034-4.21 1.295-5.325 3.255-2.303 3.995-.586 9.866 1.62 13.095 1.104 1.582 2.394 3.348 4.082 3.286 1.652-.068 2.269-1.055 4.262-1.055 1.975 0 2.554 1.055 4.276 1.015 1.772-.028 2.888-1.589 3.953-3.184 1.276-1.813 1.788-3.598 1.808-3.69-.041-.014-3.442-1.315-3.476-5.249zm-3.252-9.652c.888-1.113 1.496-2.628 1.327-4.165-1.285.058-2.893.892-3.819 1.98-.82.96-1.551 2.532-1.362 4.01 1.444.109 2.927-.73 3.854-1.825z" fill="#FFF"/>
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="138" height="44" viewBox="0 0 138 44">
<defs>
<path id="a" d="M0 0h138v44H0z"/>
<linearGradient id="c" x1="60.158%" x2="33.558%" y1="91.847%" y2="-59.589%">
<stop offset="0%" stop-color="#D6FFA1"/>
<stop offset="32.64%" stop-color="#93E2A7"/>
<stop offset="100%" stop-color="#00A3B5"/>
</linearGradient>
<linearGradient id="d" x1="-32.172%" x2="80.167%" y1="-129.736%" y2="113.998%">
<stop offset="0%" stop-color="#FF177B"/>
<stop offset="67.21%" stop-color="#FFA976"/>
<stop offset="100%" stop-color="#FFEC73"/>
</linearGradient>
<linearGradient id="e" x1="50.82%" x2="-.029%" y1="74.664%" y2="-29.336%">
<stop offset="0%" stop-color="#63FFD4"/>
<stop offset="48.54%" stop-color="#32A0BA"/>
<stop offset="83.49%" stop-color="#1262A9"/>
<stop offset="100%" stop-color="#064AA2"/>
</linearGradient>
<linearGradient id="f" x1="119.509%" x2="-23.112%" y1="35.535%" y2="61.013%">
<stop offset="0%" stop-color="#FF4521"/>
<stop offset="34.11%" stop-color="#D43E65"/>
<stop offset="79.35%" stop-color="#9F36B9"/>
<stop offset="100%" stop-color="#8A33DB"/>
</linearGradient>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="b" fill="#fff">
<use xlink:href="#a"/>
</mask>
<g fill-rule="nonzero" mask="url(#b)">
<path fill="#000" d="M3.067 0A3.068 3.068 0 0 0 0 3.07v37.86A3.068 3.068 0 0 0 3.067 44h131.866A3.068 3.068 0 0 0 138 40.93V3.07A3.068 3.068 0 0 0 134.933 0H3.067z"/>
<path d="M37.834 13.226h-2.252l-.471 1.355h-1.05l2.196-5.82h.906l2.2 5.82H38.31l-.475-1.355zm-1.968-.815h1.685l-.843-2.414-.842 2.414zm8.772 2.17h-1.01l-2.591-4.129v4.13h-1.01v-5.82h1.01l2.6 4.145V8.762h1.001zm1.194 0v-5.82h1.717c.514 0 .97.115 1.366.344.4.23.708.555.926.976.219.42.328.903.328 1.447v.291c0 .552-.11 1.037-.332 1.455-.218.419-.53.741-.938.968-.405.226-.87.34-1.394.34h-1.673zm1.01-5.004v4.197h.66c.53 0 .935-.165 1.217-.496.285-.333.43-.81.436-1.43v-.324c0-.632-.137-1.114-.412-1.447-.274-.333-.672-.5-1.194-.5h-.706zm6.469 2.762h-1.126v2.242h-1.01v-5.82h2.045c.67 0 1.188.151 1.553.452.365.301.547.737.547 1.307 0 .39-.095.716-.284.98a1.783 1.783 0 0 1-.782.603l1.305 2.426v.052h-1.082l-1.166-2.242zm-1.126-.811h1.039c.34 0 .606-.086.798-.256.192-.173.288-.41.288-.708 0-.311-.09-.553-.268-.723-.176-.17-.44-.259-.79-.264h-1.067v1.95zm8.733.295c0 .57-.098 1.072-.295 1.503-.197.43-.48.76-.847.992a2.322 2.322 0 0 1-1.262.343c-.47 0-.891-.114-1.261-.343a2.3 2.3 0 0 1-.855-.988c-.2-.426-.3-.918-.303-1.475v-.327c0-.568.1-1.069.299-1.503.202-.435.486-.766.85-.996a2.314 2.314 0 0 1 1.262-.347c.474 0 .893.114 1.258.343.368.227.651.555.85.984.2.426.301.923.304 1.49v.324zm-1.01-.303c0-.645-.123-1.14-.367-1.483-.243-.344-.587-.516-1.035-.516-.436 0-.778.172-1.026.516-.245.34-.37.824-.375 1.45v.336c0 .64.124 1.134.371 1.483.25.35.596.524 1.038.524.448 0 .79-.17 1.03-.512.243-.34.364-.84.364-1.495v-.303zm3.082 3.061h-1.006v-5.82h1.006zm1.242 0v-5.82h1.717c.514 0 .97.115 1.366.344.4.23.708.555.926.976.219.42.328.903.328 1.447v.291c0 .552-.11 1.037-.332 1.455-.218.419-.53.741-.938.968-.405.226-.87.34-1.394.34h-1.673zm1.01-5.004v4.197h.66c.53 0 .935-.165 1.217-.496.285-.333.43-.81.435-1.43v-.324c0-.632-.137-1.114-.41-1.447-.275-.333-.673-.5-1.195-.5h-.706zm9.624 3.649h-2.252l-.471 1.355h-1.05l2.196-5.82h.906l2.2 5.82h-1.054l-.475-1.355zm-1.969-.815h1.685l-.842-2.414-.843 2.414zm5.171.008v2.162h-1.01v-5.82h2.224c.65 0 1.165.17 1.545.508.384.339.575.786.575 1.343 0 .57-.187 1.014-.563 1.331-.372.317-.895.476-1.569.476h-1.202zm0-.811h1.214c.36 0 .634-.084.823-.252.189-.17.283-.416.283-.736 0-.314-.096-.565-.287-.751-.192-.19-.455-.287-.79-.292h-1.243v2.03zm5.232.811v2.162h-1.011v-5.82h2.224c.65 0 1.165.17 1.545.508.384.339.575.786.575 1.343 0 .57-.187 1.014-.563 1.331-.372.317-.895.476-1.569.476H83.3zm0-.811h1.213c.36 0 .634-.084.823-.252.189-.17.283-.416.283-.736 0-.314-.096-.565-.287-.751-.192-.19-.456-.287-.791-.292h-1.242v2.03zm10.896.215c0 .57-.098 1.072-.295 1.503-.197.43-.48.76-.847.992a2.322 2.322 0 0 1-1.262.343c-.47 0-.891-.114-1.261-.343a2.3 2.3 0 0 1-.855-.988c-.2-.426-.3-.918-.303-1.475v-.327c0-.568.1-1.069.3-1.503.201-.435.485-.766.85-.996a2.314 2.314 0 0 1 1.261-.347c.474 0 .893.114 1.258.343.368.227.651.555.85.984.2.426.301.923.304 1.49v.324zm-1.01-.303c0-.645-.123-1.14-.367-1.483-.243-.344-.587-.516-1.035-.516-.436 0-.778.172-1.026.516-.245.34-.37.824-.375 1.45v.336c0 .64.124 1.134.371 1.483.25.35.596.524 1.038.524.448 0 .791-.17 1.03-.512.243-.34.364-.84.364-1.495v-.303zm6.628 3.061h-1.01l-2.591-4.129v4.13h-1.01v-5.82h1.01l2.6 4.145V8.762h1.001z" fill="#FFF"/>
<path fill="url(#c)" d="M14.464 7.572L1.022.205C.664 0 .307-.051 0 .05l11.04 11 3.424-3.479z" transform="translate(8.331 16.577)"/>
<path fill="url(#d)" d="M3.476 6.907l4.6-2.507c.92-.512.92-1.33 0-1.842L3.476 0 0 3.428l3.476 3.479z" transform="translate(19.371 24.2)"/>
<path fill="url(#e)" d="M.664 0C.256.153 0 .563 0 1.177v19.749c0 .614.256 1.023.664 1.176l11.04-11.05L.664 0z" transform="translate(7.667 16.577)"/>
<path fill="url(#f)" d="M0 11.051c.307.102.664.051 1.022-.153l13.442-7.419L11.04 0 0 11.051z" transform="translate(8.331 27.628)"/>
<path d="M119.969 37.86l5.764-13.302h-1.877l-2.666 6.632h-.038l-2.741-6.632h-1.878l3.793 8.649-2.14 4.653h1.783M108.2 26.774c.32-.838 1.426-2.216 3.733-2.216 2.25 0 4.089 1.341 4.089 3.91v5.513h-1.65v-1.266h-.076c-.506.763-1.444 1.564-3.02 1.564-1.875 0-3.432-1.154-3.432-3.054 0-2.067 1.875-3.185 4.033-3.185 1.238 0 2.138.354 2.494.56v-.262c0-1.378-1.106-2.216-2.4-2.216-.957 0-1.876.466-2.195 1.322l-1.575-.67zm1.445 4.488c0 .969 1.013 1.453 1.913 1.453 1.369 0 2.813-1.174 2.813-2.664 0 0-.731-.595-2.194-.595-1.82 0-2.532.968-2.532 1.806zm-4.356-10.285h1.533v13.302h-1.533zm-10.222 0h4.53c2.21 0 4.159 1.635 4.159 3.957s-1.95 3.957-4.159 3.957h-2.822v5.388h-1.708V20.977zm1.708 6.28h2.86c1.521 0 2.376-1.264 2.376-2.323s-.855-2.322-2.377-2.322h-2.859v4.644zm-7.417 5.118c-.498.728-1.702 1.982-3.782 1.982-2.58 0-4.505-1.965-4.505-4.472 0-2.66 1.943-4.473 4.281-4.473 2.356 0 3.508 1.847 3.886 2.846l.206.509-6.069 2.473c.464.898 1.186 1.355 2.2 1.355 1.015 0 1.72-.49 2.236-1.236l1.547 1.016zm-3.937-5.235c-1.015 0-2.424.881-2.373 2.61l4.058-1.661c-.224-.559-.894-.949-1.685-.949zm-7.308 6.946h1.995V20.94h-1.995zm-1.375-.373c0 3.304-1.978 4.66-4.316 4.66-2.201 0-3.525-1.458-4.024-2.644l1.737-.711c.31.728 1.066 1.592 2.287 1.592 1.495 0 2.424-.915 2.424-2.626v-.643h-.069c-.447.542-1.307 1.016-2.39 1.016-2.27 0-4.35-1.948-4.35-4.455 0-2.524 2.08-4.49 4.35-4.49 1.083 0 1.943.474 2.39 1h.07v-.729h1.89v8.03zm-6.706-3.811c0 1.558 1.152 2.693 2.527 2.693 1.359 0 2.425-1.135 2.425-2.693 0-1.576-1.066-2.728-2.425-2.728-1.375 0-2.527 1.152-2.527 2.728zm-2.82-.017c0 2.575-2.046 4.472-4.557 4.472-2.51 0-4.556-1.897-4.556-4.472 0-2.592 2.046-4.473 4.556-4.473 2.51 0 4.557 1.88 4.557 4.473zm-7.119 0c0 1.592 1.186 2.71 2.562 2.71s2.562-1.118 2.562-2.71c0-1.61-1.186-2.71-2.562-2.71s-2.562 1.1-2.562 2.71zm-2.82 0c0 2.575-2.046 4.472-4.556 4.472-2.51 0-4.557-1.897-4.557-4.472 0-2.592 2.046-4.473 4.557-4.473 2.51 0 4.556 1.88 4.556 4.473zm-7.118 0c0 1.592 1.186 2.71 2.562 2.71 1.375 0 2.562-1.118 2.562-2.71 0-1.61-1.187-2.71-2.562-2.71-1.376 0-2.562 1.1-2.562 2.71zm-9.269 4.472c2.098 0 3.68-.678 4.918-1.948 1.273-1.254 1.668-3.016 1.668-4.439a6.1 6.1 0 0 0-.103-1.186h-6.483v1.898h4.608c-.137 1.067-.498 1.846-1.048 2.388-.671.661-1.72 1.39-3.56 1.39a5.019 5.019 0 0 1-5.055-5.049 5.019 5.019 0 0 1 5.055-5.048 4.95 4.95 0 0 1 3.474 1.355l1.358-1.338c-1.152-1.085-2.682-1.915-4.832-1.915-3.886 0-7.153 3.117-7.153 6.946 0 3.829 3.267 6.946 7.153 6.946z" fill="#FFF"/>
</g>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="140" height="29" viewBox="0 0 140 29">
<defs>
<path id="a" d="M.012.054h139.921v28.362H.013"/>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="b" fill="#fff">
<use xlink:href="#a"/>
</mask>
<path fill="#F8E419" d="M8.635 19.85l.003-.003a7.89 7.89 0 0 1-2.346-5.611c0-2.191.898-4.177 2.348-5.613l-.004-.004a3.1 3.1 0 0 0 0-4.41 3.163 3.163 0 0 0-4.448 0A14.074 14.074 0 0 0 0 14.235c0 3.916 1.6 7.46 4.188 10.025a3.163 3.163 0 0 0 4.448 0 3.1 3.1 0 0 0 0-4.41l-.001-.001" mask="url(#b)"/>
<path fill="#56AED2" d="M8.636 8.619l.003.003a8.018 8.018 0 0 1 5.657-2.328c2.208 0 4.21.89 5.658 2.33l.004-.004a3.16 3.16 0 0 0 4.446 0 3.102 3.102 0 0 0 0-4.413A14.303 14.303 0 0 0 14.296.053c-3.948 0-7.52 1.587-10.107 4.154a3.102 3.102 0 0 0 0 4.413 3.16 3.16 0 0 0 4.446 0l.001-.001"/>
<path fill="#E0534E" d="M19.957 19.573l-.003-.003a8.018 8.018 0 0 1-5.656 2.327c-2.21 0-4.21-.89-5.659-2.329l-.004.004a3.16 3.16 0 0 0-4.446 0 3.102 3.102 0 0 0 0 4.412 14.303 14.303 0 0 0 10.109 4.155c3.947 0 7.52-1.587 10.106-4.155a3.102 3.102 0 0 0 0-4.412 3.16 3.16 0 0 0-4.446 0v.001"/>
<path fill="#1E78A4" d="M8.636 8.57a3.163 3.163 0 0 1-4.448 0 3.1 3.1 0 0 1 0-4.41 3.163 3.163 0 0 1 4.448 0 3.1 3.1 0 0 1 0 4.41"/>
<path fill="#BE433B" d="M8.636 23.984a3.163 3.163 0 0 1-4.448 0 3.1 3.1 0 0 1 0-4.41 3.163 3.163 0 0 1 4.448 0 3.1 3.1 0 0 1 0 4.41"/>
<path fill="#272B30" d="M80.834 12.619h6.7c-.255-1.654-1.637-2.308-3.229-2.308-1.615 0-2.98.745-3.471 2.308zm-32.738-2.103c2.03 0 3.397 1.758 3.397 3.679 0 2.026-1.254 3.651-3.397 3.651-2.144 0-3.396-1.626-3.396-3.651 0-1.998 1.275-3.68 3.396-3.68zm32.67 4.868c.293 1.798 2.02 2.54 3.72 2.54a6.12 6.12 0 0 0 2.452-.543c.125-.059.242-.11.361-.179.403-.23.772-.367 1.133-.002.194.198.389.394.587.588.187.184.356.368.52.573.546.682.163.844-.454 1.258-1.325.887-3.061 1.328-4.65 1.328-4.275 0-7.119-2.565-7.119-6.856 0-4.045 2.827-6.726 6.886-6.726 4.155 0 6.528 2.42 6.604 6.534.02 1.126.07 1.485-1.113 1.485h-8.927zM48.07 7.494c-3.935 0-6.703 2.832-6.703 6.7 0 3.922 2.745 6.701 6.729 6.701 3.978 0 6.756-2.775 6.756-6.7 0-3.878-2.86-6.702-6.782-6.702zm46.918 1.86c1.183-1.114 2.464-1.809 4.138-1.809 3.623 0 5.51 2.945 5.51 6.314v5.676c0 .592-.49 1.077-1.086 1.077h-1.162a1.084 1.084 0 0 1-1.086-1.077v-5.65c0-1.867-1.013-3.317-3.006-3.317-1.947 0-3.19 1.637-3.19 3.471v5.505c0 .586-.483 1.068-1.075 1.068h-1.154a1.076 1.076 0 0 1-1.077-1.068V8.794c0-.588.486-1.068 1.077-1.068h.984c.6 0 1.025.49 1.076 1.06l.051.568zm-35.924 0c1.183-1.114 2.464-1.809 4.138-1.809 3.622 0 5.51 2.945 5.51 6.314v5.677c0 .592-.49 1.076-1.086 1.076h-1.163a1.084 1.084 0 0 1-1.085-1.076v-5.651c0-1.868-1.014-3.317-3.007-3.317-1.946 0-3.188 1.638-3.188 3.472v5.504c0 .587-.485 1.068-1.077 1.068h-1.153c-.593 0-1.077-.48-1.077-1.068V8.793c0-.587.484-1.067 1.077-1.067h.983c.6 0 1.026.49 1.077 1.06l.05.568zm14.554-1.578h2.48c.592 0 1.076.482 1.076 1.07v.73c0 .588-.484 1.069-1.076 1.069h-2.48v5.404c0 1.039.502 1.64 1.565 1.64h.504c.593 0 1.078.478 1.078 1.067v.732c0 .588-.486 1.068-1.078 1.068h-.746c-2.923 0-4.63-1.241-4.63-4.507V5.573c0-.588.485-1.07 1.078-1.07h1.152c.593 0 1.077.482 1.077 1.07v2.203zm35.87 0h2.48c.592 0 1.076.482 1.076 1.07v.73c0 .588-.484 1.069-1.076 1.069h-2.48v5.404c0 1.039.502 1.638 1.565 1.638h.503c.593 0 1.078.48 1.078 1.07v.73c0 .589-.486 1.07-1.078 1.07h-.958a7.578 7.578 0 0 1-.531-.012c-2.685-.164-3.887-1.89-3.887-4.496V5.573c0-.588.485-1.07 1.077-1.07h1.153c.594 0 1.077.482 1.077 1.07v2.203zm8.099.008h2.267c.6 0 1.077.497 1.077 1.087v.746c0 .59-.478 1.087-1.077 1.087h-2.267v8.832c0 .592-.488 1.076-1.086 1.076h-1.162a1.084 1.084 0 0 1-1.086-1.076V7.335c0-2.668 1.51-4.765 4.255-5.02.075-.007.15-.012.222-.015.162-.015.328-.01.487-.006h1.059c.599 0 1.076.498 1.076 1.088v.678c0 .59-.477 1.087-1.076 1.087h-.776a.48.48 0 0 1-.076.004c-1.297 0-1.837.995-1.837 2.184v.449zm13.973 11.222c-1.26 1.192-2.476 1.812-4.27 1.812-3.638 0-5.38-2.98-5.38-6.315V8.828c0-.592.489-1.077 1.086-1.077h1.163c.596 0 1.085.485 1.085 1.077v5.65c0 1.867 1.012 3.317 3.005 3.317 1.95 0 3.19-1.643 3.19-3.485l.001-2.029a1.016 1.016 0 0 1-.027-.238V8.828c0-.592.488-1.077 1.085-1.077h1.161c.598 0 1.087.484 1.087 1.077v10.716c0 .587-.486 1.068-1.078 1.068h-.983c-.6 0-1.026-.49-1.077-1.061l-.048-.545zm6.125 1.606a1.084 1.084 0 0 1-1.085-1.076V5.564c0-.592.489-1.077 1.085-1.077h1.163c.596 0 1.085.485 1.085 1.077v13.972c0 .592-.49 1.076-1.085 1.076h-1.163zm-99.234-3.475c.899-.692 1.029-.295 1.8.462.392.383 1.437 1.036.745 1.604-1.425 1.17-2.708 1.7-4.556 1.7-4.14 0-7.071-3.06-6.851-7.113.098-1.824.81-3.543 2.239-4.737 1.311-1.096 2.939-1.582 4.643-1.55.868.017 1.64.143 2.446.463.52.206 1.029.445 1.476.784.908.688.49 1.096-.22 1.8-.159.158-.312.322-.469.481-.354.359-.606.618-1.095.285-.926-.628-2.1-.966-3.21-.682-3.567.916-3.377 7.22.988 7.22.726 0 1.491-.276 2.064-.717z"/>
</g>
</svg>
<svg width="256" height="301" viewBox="0 0 256 301" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<path d="M78.389.858a8.824 8.824 0 0 0-4.19 1.089c-4.218 2.338-5.765 7.757-3.428 11.972l9.523 17.196C57.35 45.31 42.147 69.558 42.147 97.233v5.142c-4.407-5.07-10.884-8.271-18.121-8.271C10.766 94.104 0 104.87 0 118.129v74.009c0 13.258 10.767 24.025 24.026 24.025 7.237 0 13.714-3.202 18.12-8.272v10.367c0 13.977 11.437 25.413 25.414 25.413h6.448v32.923c0 13.26 10.767 24.026 24.026 24.026s24.025-10.767 24.025-24.026v-32.923h11.156v32.923c0 13.26 10.767 24.026 24.026 24.026s24.025-10.767 24.025-24.026v-32.923h6.449c13.976 0 25.413-11.436 25.413-25.413v-10.367c4.406 5.07 10.884 8.272 18.121 8.272 13.259 0 24.025-10.767 24.025-24.025v-74.009c0-13.259-10.766-24.025-24.025-24.025-7.237 0-13.715 3.201-18.121 8.271v-4.272-.245c.002-.26 0-.468 0-.625 0-27.67-15.238-51.894-38.174-66.09l9.55-17.224c2.336-4.215.79-9.634-3.428-11.972a8.824 8.824 0 0 0-4.19-1.089c-3.115-.03-6.172 1.612-7.782 4.517l-9.986 18.04c-9.764-3.603-20.388-5.578-31.48-5.578-11.082 0-21.726 1.954-31.482 5.55L86.171 5.375C84.56 2.47 81.504.83 78.389.858z" fill="#FFF"/>
<path d="M24.026 100.362c-9.894 0-17.767 7.873-17.767 17.767v74.009c0 9.894 7.873 17.767 17.767 17.767 9.894 0 17.768-7.873 17.768-17.767v-74.009c0-9.894-7.874-17.767-17.768-17.767zm207.224 0c-9.895 0-17.768 7.873-17.768 17.767v74.009c0 9.894 7.873 17.767 17.768 17.767 9.894 0 17.767-7.873 17.767-17.767v-74.009c0-9.894-7.873-17.767-17.767-17.767zM98.034 184.818c-9.894 0-17.768 7.873-17.768 17.768v74.008c0 9.894 7.874 17.767 17.768 17.767 9.894 0 17.767-7.873 17.767-17.767v-74.008c0-9.895-7.873-17.768-17.767-17.768zm59.207 0c-9.895 0-17.768 7.873-17.768 17.768v74.008c0 9.894 7.873 17.767 17.768 17.767 9.894 0 17.767-7.873 17.767-17.767v-74.008c0-9.895-7.873-17.768-17.767-17.768z" fill="#A4C639"/>
<path d="M78.443 7.117a2.47 2.47 0 0 0-1.197.326c-1.267.702-1.683 2.158-.98 3.428l12.517 22.611c-24.08 12.53-40.346 36.341-40.378 63.669H206.87c-.033-27.328-16.298-51.14-40.378-63.669l12.516-22.61c.704-1.27.287-2.727-.98-3.429a2.467 2.467 0 0 0-1.197-.326c-.9-.01-1.751.44-2.231 1.306l-12.68 22.855c-10.372-4.6-22.006-7.183-34.283-7.183-12.277 0-23.91 2.582-34.283 7.183L80.674 8.423a2.486 2.486 0 0 0-2.23-1.306zm-30.038 96.292v114.85a19.113 19.113 0 0 0 19.155 19.154h120.155a19.113 19.113 0 0 0 19.155-19.155V103.41H48.405z" fill="#A4C639"/>
<path d="M91.068 54.923c-3.617 0-6.612 2.994-6.612 6.611 0 3.618 2.995 6.612 6.612 6.612 3.618 0 6.612-2.994 6.612-6.612 0-3.617-2.994-6.611-6.612-6.611zm73.138 0c-3.618 0-6.612 2.994-6.612 6.611 0 3.618 2.994 6.612 6.612 6.612 3.617 0 6.612-2.994 6.612-6.612 0-3.617-2.995-6.611-6.612-6.611z" fill="#FFF"/>
</svg>
This diff is collapsed.
<svg width="256" height="346" viewBox="0 0 256 346" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<path d="M82.554 267.473s-13.198 7.675 9.393 10.272c27.369 3.122 41.356 2.675 71.517-3.034 0 0 7.93 4.972 19.003 9.279-67.611 28.977-153.019-1.679-99.913-16.517M74.292 229.659s-14.803 10.958 7.805 13.296c29.236 3.016 52.324 3.263 92.276-4.43 0 0 5.526 5.602 14.215 8.666-81.747 23.904-172.798 1.885-114.296-17.532" fill="#5382A1"/>
<path d="M143.942 165.515c16.66 19.18-4.377 36.44-4.377 36.44s42.301-21.837 22.874-49.183c-18.144-25.5-32.059-38.172 43.268-81.858 0 0-118.238 29.53-61.765 94.6" fill="#E76F00"/>
<path d="M233.364 295.442s9.767 8.047-10.757 14.273c-39.026 11.823-162.432 15.393-196.714.471-12.323-5.36 10.787-12.8 18.056-14.362 7.581-1.644 11.914-1.337 11.914-1.337-13.705-9.655-88.583 18.957-38.034 27.15 137.853 22.356 251.292-10.066 215.535-26.195M88.9 190.48s-62.771 14.91-22.228 20.323c17.118 2.292 51.243 1.774 83.03-.89 25.978-2.19 52.063-6.85 52.063-6.85s-9.16 3.923-15.787 8.448c-63.744 16.765-186.886 8.966-151.435-8.183 29.981-14.492 54.358-12.848 54.358-12.848M201.506 253.422c64.8-33.672 34.839-66.03 13.927-61.67-5.126 1.066-7.411 1.99-7.411 1.99s1.903-2.98 5.537-4.27c41.37-14.545 73.187 42.897-13.355 65.647 0 .001 1.003-.895 1.302-1.697" fill="#5382A1"/>
<path d="M162.439.371s35.887 35.9-34.037 91.101c-56.071 44.282-12.786 69.53-.023 98.377-32.73-29.53-56.75-55.526-40.635-79.72C111.395 74.612 176.918 57.393 162.439.37" fill="#E76F00"/>
<path d="M95.268 344.665c62.199 3.982 157.712-2.209 159.974-31.64 0 0-4.348 11.158-51.404 20.018-53.088 9.99-118.564 8.824-157.399 2.421.001 0 7.95 6.58 48.83 9.201" fill="#5382A1"/>
</svg>
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<path d="M0 0h256v256H0V0z" fill="#F7DF1E"/>
<path d="M67.312 213.932l19.59-11.856c3.78 6.701 7.218 12.371 15.465 12.371 7.905 0 12.89-3.092 12.89-15.12v-81.798h24.057v82.138c0 24.917-14.606 36.259-35.916 36.259-19.245 0-30.416-9.967-36.087-21.996M152.381 211.354l19.588-11.341c5.157 8.421 11.859 14.607 23.715 14.607 9.969 0 16.325-4.984 16.325-11.858 0-8.248-6.53-11.17-17.528-15.98l-6.013-2.58c-17.357-7.387-28.87-16.667-28.87-36.257 0-18.044 13.747-31.792 35.228-31.792 15.294 0 26.292 5.328 34.196 19.247L210.29 147.43c-4.125-7.389-8.591-10.31-15.465-10.31-7.046 0-11.514 4.468-11.514 10.31 0 7.217 4.468 10.14 14.778 14.608l6.014 2.577c20.45 8.765 31.963 17.7 31.963 37.804 0 21.654-17.012 33.51-39.867 33.51-22.339 0-36.774-10.654-43.819-24.574"/>
</svg>
<svg width="256" height="289" viewBox="0 0 256 289" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<path d="M128 288.464c-3.975 0-7.685-1.06-11.13-2.915l-35.247-20.936c-5.3-2.915-2.65-3.975-1.06-4.505 7.155-2.385 8.48-2.915 15.9-7.156.796-.53 1.856-.265 2.65.265l27.032 16.166c1.06.53 2.385.53 3.18 0l105.74-61.217c1.06-.53 1.59-1.59 1.59-2.915V83.08c0-1.325-.53-2.385-1.59-2.915l-105.74-60.953c-1.06-.53-2.385-.53-3.18 0L20.405 80.166c-1.06.53-1.59 1.855-1.59 2.915v122.17c0 1.06.53 2.385 1.59 2.915l28.887 16.695c15.636 7.95 25.44-1.325 25.44-10.6V93.68c0-1.59 1.326-3.18 3.181-3.18h13.516c1.59 0 3.18 1.325 3.18 3.18v120.58c0 20.936-11.396 33.126-31.272 33.126-6.095 0-10.865 0-24.38-6.625l-27.827-15.9C4.24 220.885 0 213.465 0 205.515V83.346C0 75.396 4.24 67.976 11.13 64L116.87 2.783c6.625-3.71 15.635-3.71 22.26 0L244.87 64C251.76 67.975 256 75.395 256 83.346v122.17c0 7.95-4.24 15.37-11.13 19.345L139.13 286.08c-3.445 1.59-7.42 2.385-11.13 2.385zm32.596-84.009c-46.377 0-55.917-21.2-55.917-39.221 0-1.59 1.325-3.18 3.18-3.18h13.78c1.59 0 2.916 1.06 2.916 2.65 2.12 14.045 8.215 20.936 36.306 20.936 22.261 0 31.802-5.035 31.802-16.96 0-6.891-2.65-11.926-37.367-15.372-28.886-2.915-46.907-9.275-46.907-32.33 0-21.467 18.02-34.187 48.232-34.187 33.921 0 50.617 11.66 52.737 37.101 0 .795-.265 1.59-.795 2.385-.53.53-1.325 1.06-2.12 1.06h-13.78c-1.326 0-2.65-1.06-2.916-2.385-3.18-14.575-11.395-19.345-33.126-19.345-24.38 0-27.296 8.48-27.296 14.84 0 7.686 3.445 10.07 36.306 14.31 32.597 4.24 47.967 10.336 47.967 33.127-.265 23.321-19.345 36.571-53.002 36.571z" fill="#539E43"/>
</svg>
<svg width="256" height="135" viewBox="0 0 256 135" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<defs>
<radialGradient id="a" cx=".837" cy="-125.811" r="363.057" gradientTransform="matrix(.463 0 0 .463 76.464 81.918)" gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#fff"/>
<stop offset=".5" stop-color="#4c6b97"/>
<stop offset="1" stop-color="#231f20"/>
</radialGradient>
</defs>
<ellipse fill="url(#a)" cx="128" cy="67.3" rx="128" ry="67.3"/>
<ellipse fill="#6181B6" cx="128" cy="67.3" rx="123" ry="62.3"/>
<g fill="#FFF">
<path d="M152.9 87.5l6.1-31.4c1.4-7.1.2-12.4-3.4-15.7-3.5-3.2-9.5-4.8-18.3-4.8h-10.6l3-15.6c.1-.6 0-1.2-.4-1.7s-.9-.7-1.5-.7h-14.6c-1 0-1.8.7-2 1.6l-6.5 33.3c-.6-3.8-2-7-4.4-9.6-4.3-4.9-11-7.4-20.1-7.4H52.1c-1 0-1.8.7-2 1.6L37 104.7c-.1.6 0 1.2.4 1.7s.9.7 1.5.7h14.7c1 0 1.8-.7 2-1.6l3.2-16.3h10.9c5.7 0 10.6-.6 14.3-1.8 3.9-1.3 7.4-3.4 10.5-6.3 2.5-2.3 4.6-4.9 6.2-7.7l-2.6 13.5c-.1.6 0 1.2.4 1.7s.9.7 1.5.7h14.6c1 0 1.8-.7 2-1.6l7.2-37h10c4.3 0 5.5.8 5.9 1.2.3.3.9 1.5.2 5.2L134.1 87c-.1.6 0 1.2.4 1.7s.9.7 1.5.7h15c.9-.3 1.7-1 1.9-1.9zm-67.6-26c-.9 4.7-2.6 8.1-5.1 10-2.5 1.9-6.6 2.9-12 2.9h-6.5l4.7-24.2h8.4c6.2 0 8.7 1.3 9.7 2.4 1.3 1.6 1.6 4.7.8 8.9zM215.3 42.9c-4.3-4.9-11-7.4-20.1-7.4h-28.3c-1 0-1.8.7-2 1.6l-13.1 67.5c-.1.6 0 1.2.4 1.7s.9.7 1.5.7h14.7c1 0 1.8-.7 2-1.6l3.2-16.3h10.9c5.7 0 10.6-.6 14.3-1.8 3.9-1.3 7.4-3.4 10.5-6.3 2.6-2.4 4.8-5.1 6.4-8 1.6-2.9 2.8-6.1 3.5-9.6 1.7-8.7.4-15.5-3.9-20.5zM200 61.5c-.9 4.7-2.6 8.1-5.1 10-2.5 1.9-6.6 2.9-12 2.9h-6.5l4.7-24.2h8.4c6.2 0 8.7 1.3 9.7 2.4 1.4 1.6 1.7 4.7.8 8.9z"/>
</g>
<g fill="#000004">
<path d="M74.8 48.2c5.6 0 9.3 1 11.2 3.1 1.9 2.1 2.3 5.6 1.3 10.6-1 5.2-3 9-5.9 11.2-2.9 2.2-7.3 3.3-13.2 3.3h-8.9l5.5-28.2h10zM39 105h14.7l3.5-17.9h12.6c5.6 0 10.1-.6 13.7-1.8 3.6-1.2 6.8-3.1 9.8-5.9 2.5-2.3 4.5-4.8 6-7.5s2.6-5.7 3.2-9c1.6-8 .4-14.2-3.5-18.7s-10.1-6.7-18.6-6.7H52.1L39 105zM113.3 19.6h14.6l-3.5 17.9h13c8.2 0 13.8 1.4 16.9 4.3 3.1 2.9 4 7.5 2.8 13.9L151 87.1h-14.8l5.8-29.9c.7-3.4.4-5.7-.7-6.9-1.1-1.2-3.6-1.9-7.3-1.9h-11.7l-7.5 38.7h-14.6l13.1-67.5zM189.5 48.2c5.6 0 9.3 1 11.2 3.1 1.9 2.1 2.3 5.6 1.3 10.6-1 5.2-3 9-5.9 11.2-2.9 2.2-7.3 3.3-13.2 3.3H174l5.5-28.2h10zM153.7 105h14.7l3.5-17.9h12.6c5.6 0 10.1-.6 13.7-1.8 3.6-1.2 6.8-3.1 9.8-5.9 2.5-2.3 4.5-4.8 6-7.5s2.6-5.7 3.2-9c1.6-8 .4-14.2-3.5-18.7s-10.1-6.7-18.6-6.7h-28.3L153.7 105z"/>
</g>
</svg>
<svg width="256" height="255" viewBox="0 0 256 255" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<defs>
<linearGradient x1="12.959%" y1="12.039%" x2="79.639%" y2="78.201%" id="a">
<stop stop-color="#387EB8" offset="0%"/>
<stop stop-color="#366994" offset="100%"/>
</linearGradient>
<linearGradient x1="19.128%" y1="20.579%" x2="90.742%" y2="88.429%" id="b">
<stop stop-color="#FFE052" offset="0%"/>
<stop stop-color="#FFC331" offset="100%"/>
</linearGradient>
</defs>
<path d="M126.916.072c-64.832 0-60.784 28.115-60.784 28.115l.072 29.128h61.868v8.745H41.631S.145 61.355.145 126.77c0 65.417 36.21 63.097 36.21 63.097h21.61v-30.356s-1.165-36.21 35.632-36.21h61.362s34.475.557 34.475-33.319V33.97S194.67.072 126.916.072zM92.802 19.66a11.12 11.12 0 0 1 11.13 11.13 11.12 11.12 0 0 1-11.13 11.13 11.12 11.12 0 0 1-11.13-11.13 11.12 11.12 0 0 1 11.13-11.13z" fill="url(#a)"/>
<path d="M128.757 254.126c64.832 0 60.784-28.115 60.784-28.115l-.072-29.127H127.6v-8.745h86.441s41.486 4.705 41.486-60.712c0-65.416-36.21-63.096-36.21-63.096h-21.61v30.355s1.165 36.21-35.632 36.21h-61.362s-34.475-.557-34.475 33.32v56.013s-5.235 33.897 62.518 33.897zm34.114-19.586a11.12 11.12 0 0 1-11.13-11.13 11.12 11.12 0 0 1 11.13-11.131 11.12 11.12 0 0 1 11.13 11.13 11.12 11.12 0 0 1-11.13 11.13z" fill="url(#b)"/>
</svg>
<svg width="256" height="255" viewBox="0 0 256 255" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<defs>
<linearGradient x1="84.75%" y1="111.399%" x2="58.254%" y2="64.584%" id="a">
<stop stop-color="#FB7655" offset="0%"/>
<stop stop-color="#FB7655" offset="0%"/>
<stop stop-color="#E42B1E" offset="41%"/>
<stop stop-color="#900" offset="99%"/>
<stop stop-color="#900" offset="100%"/>
</linearGradient>
<linearGradient x1="116.651%" y1="60.89%" x2="1.746%" y2="19.288%" id="b">
<stop stop-color="#871101" offset="0%"/>
<stop stop-color="#871101" offset="0%"/>
<stop stop-color="#911209" offset="99%"/>
<stop stop-color="#911209" offset="100%"/>
</linearGradient>
<linearGradient x1="75.774%" y1="219.327%" x2="38.978%" y2="7.829%" id="c">
<stop stop-color="#871101" offset="0%"/>
<stop stop-color="#871101" offset="0%"/>
<stop stop-color="#911209" offset="99%"/>
<stop stop-color="#911209" offset="100%"/>
</linearGradient>
<linearGradient x1="50.012%" y1="7.234%" x2="66.483%" y2="79.135%" id="d">
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#E57252" offset="23%"/>
<stop stop-color="#DE3B20" offset="46%"/>
<stop stop-color="#A60003" offset="99%"/>
<stop stop-color="#A60003" offset="100%"/>
</linearGradient>
<linearGradient x1="46.174%" y1="16.348%" x2="49.932%" y2="83.047%" id="e">
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#E4714E" offset="23%"/>
<stop stop-color="#BE1A0D" offset="56%"/>
<stop stop-color="#A80D00" offset="99%"/>
<stop stop-color="#A80D00" offset="100%"/>
</linearGradient>
<linearGradient x1="36.965%" y1="15.594%" x2="49.528%" y2="92.478%" id="f">
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#E46342" offset="18%"/>
<stop stop-color="#C82410" offset="40%"/>
<stop stop-color="#A80D00" offset="99%"/>
<stop stop-color="#A80D00" offset="100%"/>
</linearGradient>
<linearGradient x1="13.609%" y1="58.346%" x2="85.764%" y2="-46.717%" id="g">
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#C81F11" offset="54%"/>
<stop stop-color="#BF0905" offset="99%"/>
<stop stop-color="#BF0905" offset="100%"/>
</linearGradient>
<linearGradient x1="27.624%" y1="21.135%" x2="50.745%" y2="79.056%" id="h">
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#FFF" offset="0%"/>
<stop stop-color="#DE4024" offset="31%"/>
<stop stop-color="#BF190B" offset="99%"/>
<stop stop-color="#BF190B" offset="100%"/>
</linearGradient>
<linearGradient x1="-20.667%" y1="122.282%" x2="104.242%" y2="-6.342%" id="i">
<stop stop-color="#BD0012" offset="0%"/>
<stop stop-color="#BD0012" offset="0%"/>
<stop stop-color="#FFF" offset="7%"/>
<stop stop-color="#FFF" offset="17%"/>
<stop stop-color="#C82F1C" offset="27%"/>
<stop stop-color="#820C01" offset="33%"/>
<stop stop-color="#A31601" offset="46%"/>
<stop stop-color="#B31301" offset="72%"/>
<stop stop-color="#E82609" offset="99%"/>
<stop stop-color="#E82609" offset="100%"/>
</linearGradient>
<linearGradient x1="58.792%" y1="65.205%" x2="11.964%" y2="50.128%" id="j">
<stop stop-color="#8C0C01" offset="0%"/>
<stop stop-color="#8C0C01" offset="0%"/>
<stop stop-color="#990C00" offset="54%"/>
<stop stop-color="#A80D0E" offset="99%"/>
<stop stop-color="#A80D0E" offset="100%"/>
</linearGradient>
<linearGradient x1="79.319%" y1="62.754%" x2="23.088%" y2="17.888%" id="k">
<stop stop-color="#7E110B" offset="0%"/>
<stop stop-color="#7E110B" offset="0%"/>
<stop stop-color="#9E0C00" offset="99%"/>
<stop stop-color="#9E0C00" offset="100%"/>
</linearGradient>
<linearGradient x1="92.88%" y1="74.122%" x2="59.841%" y2="39.704%" id="l">
<stop stop-color="#79130D" offset="0%"/>
<stop stop-color="#79130D" offset="0%"/>
<stop stop-color="#9E120B" offset="99%"/>
<stop stop-color="#9E120B" offset="100%"/>
</linearGradient>
<radialGradient cx="32.001%" cy="40.21%" fx="32.001%" fy="40.21%" r="69.573%" id="m">
<stop stop-color="#A80D00" offset="0%"/>
<stop stop-color="#A80D00" offset="0%"/>
<stop stop-color="#7E0E08" offset="99%"/>
<stop stop-color="#7E0E08" offset="100%"/>
</radialGradient>
<radialGradient cx="13.549%" cy="40.86%" fx="13.549%" fy="40.86%" r="88.386%" id="n">
<stop stop-color="#A30C00" offset="0%"/>
<stop stop-color="#A30C00" offset="0%"/>
<stop stop-color="#800E08" offset="99%"/>
<stop stop-color="#800E08" offset="100%"/>
</radialGradient>
<linearGradient x1="56.57%" y1="101.717%" x2="3.105%" y2="11.993%" id="o">
<stop stop-color="#8B2114" offset="0%"/>
<stop stop-color="#8B2114" offset="0%"/>
<stop stop-color="#9E100A" offset="43%"/>
<stop stop-color="#B3100C" offset="99%"/>
<stop stop-color="#B3100C" offset="100%"/>
</linearGradient>
<linearGradient x1="30.87%" y1="35.599%" x2="92.471%" y2="100.694%" id="p">
<stop stop-color="#B31000" offset="0%"/>
<stop stop-color="#B31000" offset="0%"/>
<stop stop-color="#910F08" offset="44%"/>
<stop stop-color="#791C12" offset="99%"/>
<stop stop-color="#791C12" offset="100%"/>
</linearGradient>
</defs>
<path d="M197.467 167.764l-145.52 86.41 188.422-12.787L254.88 51.393l-57.414 116.37z" fill="url(#a)"/>
<path d="M240.677 241.257L224.482 129.48l-44.113 58.25 60.308 53.528z" fill="url(#b)"/>
<path d="M240.896 241.257l-118.646-9.313-69.674 21.986 188.32-12.673z" fill="url(#c)"/>
<path d="M52.744 253.955l29.64-97.1L17.16 170.8l35.583 83.154z" fill="url(#d)"/>
<path d="M180.358 188.05L153.085 81.226l-78.047 73.16 105.32 33.666z" fill="url(#e)"/>
<path d="M248.693 82.73l-73.777-60.256-20.544 66.418 94.321-6.162z" fill="url(#f)"/>
<path d="M214.191.99L170.8 24.97 143.424.669l70.767.322z" fill="url(#g)"/>
<path d="M0 203.372l18.177-33.151-14.704-39.494L0 203.372z" fill="url(#h)"/>
<path d="M2.496 129.48l14.794 41.963 64.283-14.422 73.39-68.207 20.712-65.787L143.063 0 87.618 20.75c-17.469 16.248-51.366 48.396-52.588 49-1.21.618-22.384 40.639-32.534 59.73z" fill="#FFF"/>
<path d="M54.442 54.094c37.86-37.538 86.667-59.716 105.397-40.818 18.72 18.898-1.132 64.823-38.992 102.349-37.86 37.525-86.062 60.925-104.78 42.027-18.73-18.885.515-66.032 38.375-103.558z" fill="url(#i)"/>
<path d="M52.744 253.916l29.408-97.409 97.665 31.376c-35.312 33.113-74.587 61.106-127.073 66.033z" fill="url(#j)"/>
<path d="M155.092 88.622l25.073 99.313c29.498-31.016 55.972-64.36 68.938-105.603l-94.01 6.29z" fill="url(#k)"/>
<path d="M248.847 82.833c10.035-30.282 12.35-73.725-34.966-81.791l-38.825 21.445 73.791 60.346z" fill="url(#l)"/>
<path d="M0 202.935c1.39 49.979 37.448 50.724 52.808 51.162l-35.48-82.86L0 202.935z" fill="#9E1209"/>
<path d="M155.232 88.777c22.667 13.932 68.35 41.912 69.276 42.426 1.44.81 19.695-30.784 23.838-48.64l-93.114 6.214z" fill="url(#m)"/>
<path d="M82.113 156.507l39.313 75.848c23.246-12.607 41.45-27.967 58.121-44.42l-97.434-31.428z" fill="url(#n)"/>
<path d="M17.174 171.34l-5.57 66.328c10.51 14.357 24.97 15.605 40.136 14.486-10.973-27.311-32.894-81.92-34.566-80.814z" fill="url(#o)"/>
<path d="M174.826 22.654l78.1 10.96c-4.169-17.662-16.969-29.06-38.787-32.623l-39.313 21.663z" fill="url(#p)"/>
</svg>
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
<linearGradient id="a" gradientUnits="userSpaceOnUse" x1="-1845.501" y1="1255.639" x2="-1797.134" y2="981.338" gradientTransform="rotate(180 -846.605 623.252)">
<stop offset="0" stop-color="#faae42"/>
<stop offset="1" stop-color="#ef3e31"/>
</linearGradient>
<path fill="url(#a)" d="M56.9 0h141.8c6.9 0 13.6 1.1 20.1 3.4 9.4 3.4 17.9 9.4 24.3 17.2 6.5 7.8 10.8 17.4 12.3 27.4.6 3.7.7 7.4.7 11.1V197.4c0 4.4-.2 8.9-1.1 13.2-2 9.9-6.7 19.2-13.5 26.7-6.7 7.5-15.5 13.1-25 16.1-5.8 1.8-11.8 2.6-17.9 2.6-2.7 0-142.1 0-144.2-.1-10.2-.5-20.3-3.8-28.8-9.5-8.3-5.6-15.1-13.4-19.5-22.4-3.8-7.7-5.7-16.3-5.7-24.9V56.9C.2 48.4 2 40 5.7 32.4c4.3-9 11-16.9 19.3-22.5C33.5 4.1 43.5.7 53.7.2c1-.2 2.1-.2 3.2-.2z"/>
<linearGradient id="b" gradientUnits="userSpaceOnUse" x1="130.612" y1="4.136" x2="95.213" y2="204.893">
<stop offset="0" stop-color="#e39f3a"/>
<stop offset="1" stop-color="#d33929"/>
</linearGradient>
<path fill="url(#b)" d="M216 209.4c-.9-1.4-1.9-2.8-3-4.1-2.5-3-5.4-5.6-8.6-7.8-4-2.7-8.7-4.4-13.5-4.6-3.4-.2-6.8.4-10 1.6-3.2 1.1-6.3 2.7-9.3 4.3-3.5 1.8-7 3.6-10.7 5.1-4.4 1.8-9 3.2-13.7 4.2-5.9 1.1-11.9 1.5-17.8 1.4-10.7-.2-21.4-1.8-31.6-4.8-9-2.7-17.6-6.4-25.7-11.1-7.1-4.1-13.7-8.8-19.9-14.1-5.1-4.4-9.8-9.1-14.2-14.1-3-3.5-5.9-7.2-8.6-11-1.1-1.5-2.1-3.1-3-4.7L0 121.2V56.7C0 25.4 25.3 0 56.6 0h50.5l37.4 38c84.4 57.4 57.1 120.7 57.1 120.7s24 27 14.4 50.7z"/>
<path fill="#FFF" d="M144.7 38c84.4 57.4 57.1 120.7 57.1 120.7s24 27.1 14.3 50.8c0 0-9.9-16.6-26.5-16.6-16 0-25.4 16.6-57.6 16.6-71.7 0-105.6-59.9-105.6-59.9C91 192.1 135.1 162 135.1 162c-29.1-16.9-91-97.7-91-97.7 53.9 45.9 77.2 58 77.2 58-13.9-11.5-52.9-67.7-52.9-67.7 31.2 31.6 93.2 75.7 93.2 75.7C179.2 81.5 144.7 38 144.7 38z"/>
</svg>
This diff is collapsed.
<svg width="171" height="43" viewBox="0 0 171 43" xmlns="http://www.w3.org/2000/svg">
<title>
the-example-app-logo-csharp
</title>
<g fill="none" fill-rule="evenodd">
<g transform="translate(0 2)" fill-rule="nonzero">
<path fill="#D9453F" d="M3.75 37v-7.1H28V37z"/>
<circle fill="#F5A623" cx="9.3" cy="14.3" r="9.3"/>
<path fill="#3C80CF" d="M35 27.6L19 .4h16z"/>
</g>
<path d="M53.14 31.88c0-.52-.163-.967-.49-1.34-.327-.373-.817-.56-1.47-.56-.32 0-.613.05-.88.15-.267.1-.5.237-.7.41-.2.173-.36.377-.48.61-.12.233-.187.477-.2.73h4.22zm3.02 1.26v.4c0 .133-.007.26-.02.38h-7.22c.027.28.11.533.25.76.14.227.32.423.54.59.22.167.467.297.74.39.273.093.557.14.85.14.52 0 .96-.097 1.32-.29.36-.193.653-.443.88-.75l2.28 1.44a4.613 4.613 0 0 1-1.85 1.57c-.767.367-1.657.55-2.67.55a6.371 6.371 0 0 1-2.12-.35 5.148 5.148 0 0 1-1.75-1.02 4.723 4.723 0 0 1-1.18-1.65c-.287-.653-.43-1.4-.43-2.24 0-.813.14-1.55.42-2.21.28-.66.66-1.22 1.14-1.68a5.082 5.082 0 0 1 1.7-1.07 5.814 5.814 0 0 1 2.12-.38c.733 0 1.407.123 2.02.37s1.14.603 1.58 1.07c.44.467.783 1.033 1.03 1.7s.37 1.427.37 2.28zm8.4 4.86l-2.14-3.06-2.2 3.06h-3.8l3.98-5.22-3.6-4.74h3.82l1.86 2.66 1.88-2.66h3.7l-3.58 4.74L68.42 38h-3.86zm10.96-4.34h-.42c-.36 0-.723.017-1.09.05a4.483 4.483 0 0 0-.98.19c-.287.093-.523.23-.71.41-.187.18-.28.417-.28.71 0 .187.043.347.13.48.087.133.197.24.33.32a1.4 1.4 0 0 0 .46.17c.173.033.34.05.5.05.667 0 1.177-.183 1.53-.55.353-.367.53-.863.53-1.49v-.34zM69.5 29.4c.587-.56 1.27-.98 2.05-1.26.78-.28 1.577-.42 2.39-.42.84 0 1.55.103 2.13.31.58.207 1.05.527 1.41.96.36.433.623.98.79 1.64.167.66.25 1.443.25 2.35V38h-3v-1.06h-.06c-.253.413-.637.733-1.15.96-.513.227-1.07.34-1.67.34-.4 0-.813-.053-1.24-.16a3.632 3.632 0 0 1-1.17-.52 2.816 2.816 0 0 1-.87-.96c-.227-.4-.34-.893-.34-1.48 0-.72.197-1.3.59-1.74.393-.44.9-.78 1.52-1.02s1.31-.4 2.07-.48c.76-.08 1.5-.12 2.22-.12v-.16c0-.493-.173-.857-.52-1.09-.347-.233-.773-.35-1.28-.35-.467 0-.917.1-1.35.3-.433.2-.803.44-1.11.72L69.5 29.4zM93.68 38v-5.44c0-.28-.02-.55-.06-.81a1.934 1.934 0 0 0-.22-.67 1.265 1.265 0 0 0-.43-.45c-.18-.113-.417-.17-.71-.17-.28 0-.523.06-.73.18-.207.12-.38.28-.52.48a2.1 2.1 0 0 0-.31.69c-.067.26-.1.53-.1.81V38h-3.28v-5.68c0-.573-.113-1.027-.34-1.36-.227-.333-.587-.5-1.08-.5-.52 0-.923.207-1.21.62-.287.413-.43.92-.43 1.52V38h-3.3v-9.96h3.18v1.38h.04c.12-.213.27-.42.45-.62s.397-.377.65-.53a3.596 3.596 0 0 1 1.88-.51c.72 0 1.33.147 1.83.44.5.293.89.713 1.17 1.26.333-.52.76-.933 1.28-1.24.52-.307 1.153-.46 1.9-.46.68 0 1.25.123 1.71.37.46.247.83.567 1.11.96.28.393.483.833.61 1.32.127.487.19.963.19 1.43V38h-3.28zm16.84-5.02c0 .693-.107 1.36-.32 2a5.142 5.142 0 0 1-.93 1.69 4.594 4.594 0 0 1-1.49 1.17c-.587.293-1.253.44-2 .44a4.182 4.182 0 0 1-1.74-.37c-.547-.247-.973-.583-1.28-1.01h-.04v5.9h-3.28V28.04h3.12v1.22h.06c.307-.4.73-.75 1.27-1.05.54-.3 1.177-.45 1.91-.45.72 0 1.373.14 1.96.42.587.28 1.083.66 1.49 1.14.407.48.72 1.037.94 1.67.22.633.33 1.297.33 1.99zm-3.18 0c0-.32-.05-.633-.15-.94a2.442 2.442 0 0 0-.44-.81 2.225 2.225 0 0 0-.73-.57 2.252 2.252 0 0 0-1.02-.22c-.373 0-.707.073-1 .22-.293.147-.543.34-.75.58-.207.24-.367.513-.48.82a2.692 2.692 0 0 0 0 1.88c.113.307.273.58.48.82.207.24.457.433.75.58.293.147.627.22 1 .22.387 0 .727-.073 1.02-.22.293-.147.537-.34.73-.58.193-.24.34-.517.44-.83.1-.313.15-.63.15-.95zm5.28 5.02V22.88h3.32V38h-3.32zm12.78-6.12c0-.52-.163-.967-.49-1.34-.327-.373-.817-.56-1.47-.56-.32 0-.613.05-.88.15-.267.1-.5.237-.7.41-.2.173-.36.377-.48.61-.12.233-.187.477-.2.73h4.22zm3.02 1.26v.4c0 .133-.007.26-.02.38h-7.22c.027.28.11.533.25.76.14.227.32.423.54.59.22.167.467.297.74.39.273.093.557.14.85.14.52 0 .96-.097 1.32-.29.36-.193.653-.443.88-.75l2.28 1.44a4.613 4.613 0 0 1-1.85 1.57c-.767.367-1.657.55-2.67.55a6.371 6.371 0 0 1-2.12-.35 5.148 5.148 0 0 1-1.75-1.02 4.723 4.723 0 0 1-1.18-1.65c-.287-.653-.43-1.4-.43-2.24 0-.813.14-1.55.42-2.21.28-.66.66-1.22 1.14-1.68a5.082 5.082 0 0 1 1.7-1.07 5.814 5.814 0 0 1 2.12-.38c.733 0 1.407.123 2.02.37s1.14.603 1.58 1.07c.44.467.783 1.033 1.03 1.7s.37 1.427.37 2.28zm12.92.52h-.42c-.36 0-.723.017-1.09.05a4.483 4.483 0 0 0-.98.19c-.287.093-.523.23-.71.41-.187.18-.28.417-.28.71 0 .187.043.347.13.48.087.133.197.24.33.32a1.4 1.4 0 0 0 .46.17c.173.033.34.05.5.05.667 0 1.177-.183 1.53-.55.353-.367.53-.863.53-1.49v-.34zm-6.02-4.26c.587-.56 1.27-.98 2.05-1.26.78-.28 1.577-.42 2.39-.42.84 0 1.55.103 2.13.31.58.207 1.05.527 1.41.96.36.433.623.98.79 1.64.167.66.25 1.443.25 2.35V38h-3v-1.06h-.06c-.253.413-.637.733-1.15.96-.513.227-1.07.34-1.67.34-.4 0-.813-.053-1.24-.16a3.632 3.632 0 0 1-1.17-.52 2.816 2.816 0 0 1-.87-.96c-.227-.4-.34-.893-.34-1.48 0-.72.197-1.3.59-1.74.393-.44.9-.78 1.52-1.02s1.31-.4 2.07-.48c.76-.08 1.5-.12 2.22-.12v-.16c0-.493-.173-.857-.52-1.09-.347-.233-.773-.35-1.28-.35-.467 0-.917.1-1.35.3-.433.2-.803.44-1.11.72l-1.66-1.78zm22.54 3.58c0 .693-.107 1.36-.32 2a5.142 5.142 0 0 1-.93 1.69 4.594 4.594 0 0 1-1.49 1.17c-.587.293-1.253.44-2 .44a4.182 4.182 0 0 1-1.74-.37c-.547-.247-.973-.583-1.28-1.01h-.04v5.9h-3.28V28.04h3.12v1.22h.06c.307-.4.73-.75 1.27-1.05.54-.3 1.177-.45 1.91-.45.72 0 1.373.14 1.96.42.587.28 1.083.66 1.49 1.14.407.48.72 1.037.94 1.67.22.633.33 1.297.33 1.99zm-3.18 0c0-.32-.05-.633-.15-.94a2.442 2.442 0 0 0-.44-.81 2.225 2.225 0 0 0-.73-.57 2.252 2.252 0 0 0-1.02-.22c-.373 0-.707.073-1 .22-.293.147-.543.34-.75.58-.207.24-.367.513-.48.82a2.692 2.692 0 0 0 0 1.88c.113.307.273.58.48.82.207.24.457.433.75.58.293.147.627.22 1 .22.387 0 .727-.073 1.02-.22.293-.147.537-.34.73-.58.193-.24.34-.517.44-.83.1-.313.15-.63.15-.95zm16.32 0c0 .693-.107 1.36-.32 2a5.142 5.142 0 0 1-.93 1.69 4.594 4.594 0 0 1-1.49 1.17c-.587.293-1.253.44-2 .44a4.182 4.182 0 0 1-1.74-.37c-.547-.247-.973-.583-1.28-1.01h-.04v5.9h-3.28V28.04h3.12v1.22h.06c.307-.4.73-.75 1.27-1.05.54-.3 1.177-.45 1.91-.45.72 0 1.373.14 1.96.42.587.28 1.083.66 1.49 1.14.407.48.72 1.037.94 1.67.22.633.33 1.297.33 1.99zm-3.18 0c0-.32-.05-.633-.15-.94a2.442 2.442 0 0 0-.44-.81 2.225 2.225 0 0 0-.73-.57 2.252 2.252 0 0 0-1.02-.22c-.373 0-.707.073-1 .22-.293.147-.543.34-.75.58-.207.24-.367.513-.48.82a2.692 2.692 0 0 0 0 1.88c.113.307.273.58.48.82.207.24.457.433.75.58.293.147.627.22 1 .22.387 0 .727-.073 1.02-.22.293-.147.537-.34.73-.58.193-.24.34-.517.44-.83.1-.313.15-.63.15-.95z" fill="#000"/>
<path d="M53.02 16H47.4l.1-.58c.32-.12.637-.217.95-.29.313-.073.657-.137 1.03-.19L52 3.04h-3.06L47.28 5.9h-.52c.013-.267.043-.57.09-.91.047-.34.097-.687.15-1.04.053-.353.11-.7.17-1.04.06-.34.123-.643.19-.91h11.6c-.053.267-.12.567-.2.9a46.818 46.818 0 0 1-.55 2.07c-.1.34-.197.65-.29.93h-.52l-.42-2.86h-3.06l-2.52 11.9c.32.067.623.133.91.2.287.067.557.16.81.28l-.1.58zm6.24-13.82v-.46c.2-.08.423-.163.67-.25.247-.087.503-.167.77-.24.267-.073.527-.133.78-.18.253-.047.493-.077.72-.09l.42.18-1.8 8.4h.1c.253-.533.54-1.03.86-1.49.32-.46.657-.86 1.01-1.2.353-.34.723-.607 1.11-.8.387-.193.78-.29 1.18-.29.613 0 1.057.177 1.33.53.273.353.41.79.41 1.31 0 .36-.033.747-.1 1.16-.067.413-.147.813-.24 1.2l-1.14 4.92h1.48v.52a3.298 3.298 0 0 1-.96.52c-.373.133-.747.2-1.12.2-.387 0-.653-.08-.8-.24a.757.757 0 0 1-.22-.52c0-.24.033-.51.1-.81.067-.3.133-.603.2-.91l.72-3.34c.093-.413.177-.8.25-1.16.073-.36.11-.707.11-1.04 0-.307-.067-.567-.2-.78-.133-.213-.313-.32-.54-.32-.413 0-.813.147-1.2.44a6.226 6.226 0 0 0-1.08 1.06 9.592 9.592 0 0 0-.88 1.29c-.253.447-.447.817-.58 1.11L59.56 16h-1.68l2.74-13.64-1.36-.18zM75.9 14.22c-.12.253-.293.5-.52.74a4.1 4.1 0 0 1-1.84 1.11c-.387.113-.8.17-1.24.17-1.107 0-1.957-.317-2.55-.95-.593-.633-.89-1.51-.89-2.63 0-.947.133-1.84.4-2.68.267-.84.64-1.573 1.12-2.2a5.258 5.258 0 0 1 1.75-1.48c.687-.36 1.45-.54 2.29-.54.64 0 1.19.163 1.65.49.46.327.69.81.69 1.45 0 .947-.47 1.693-1.41 2.24-.94.547-2.457.9-4.55 1.06-.053.227-.09.447-.11.66-.02.213-.03.42-.03.62 0 .8.203 1.453.61 1.96.407.507 1.003.76 1.79.76.253 0 .503-.04.75-.12.247-.08.48-.18.7-.3.22-.12.42-.247.6-.38s.337-.253.47-.36l.32.38zm-2.1-7.6c-.573.013-1.107.283-1.6.81-.493.527-.913 1.43-1.26 2.71 1.293-.04 2.3-.263 3.02-.67.72-.407 1.08-.97 1.08-1.69 0-.28-.083-.54-.25-.78-.167-.24-.497-.367-.99-.38zm19.34 5.74h.5c0 .373-.037.843-.11 1.41a12.045 12.045 0 0 1-.33 1.65 5.594 5.594 0 0 1-.86.38 7.242 7.242 0 0 1-.96.26c-.333.067-.66.113-.98.14-.32.027-.62.04-.9.04-1.133 0-2.093-.16-2.88-.48-.787-.32-1.423-.74-1.91-1.26-.487-.52-.84-1.1-1.06-1.74-.22-.64-.33-1.28-.33-1.92 0-1.36.193-2.597.58-3.71.387-1.113.937-2.067 1.65-2.86a7.375 7.375 0 0 1 2.6-1.85c1.02-.44 2.17-.66 3.45-.66.88 0 1.627.06 2.24.18.613.12 1.127.287 1.54.5-.04.213-.097.463-.17.75-.073.287-.16.59-.26.91-.1.32-.203.633-.31.94-.107.307-.207.587-.3.84h-.52L93.4 3c-.173-.067-.437-.117-.79-.15a10.97 10.97 0 0 0-1.03-.05c-.88 0-1.693.17-2.44.51a5.363 5.363 0 0 0-1.94 1.51c-.547.667-.973 1.5-1.28 2.5-.307 1-.46 2.16-.46 3.48 0 .827.143 1.523.43 2.09.287.567.647 1.023 1.08 1.37.433.347.903.593 1.41.74.507.147.987.213 1.44.2.453.013.837-.02 1.15-.1.313-.08.543-.167.69-.26l1.48-2.48zm7.9-1.02h-2.86l-1.56 3.5h-1.06l1.58-3.5h-1.62l.5-.92h1.52l1.36-3h-1.54l.48-.92h1.46l1.52-3.34h1.04l-1.5 3.34h2.84l1.52-3.34h1.04l-1.52 3.34h1.6l-.54.92h-1.46l-1.36 3h1.54l-.56.92h-1.38l-1.58 3.5h-1.04l1.58-3.5zm-2.44-.92h2.84l1.36-3h-2.84l-1.36 3z" fill="#8091A5"/>
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="168" height="43" viewBox="0 0 168 43">
<g fill="none" fill-rule="evenodd">
<g transform="translate(0 2)">
<path fill="#D9453F" d="M3.75 37v-7.1H28V37z"/>
<circle cx="9.3" cy="14.3" r="9.3" fill="#F5A623"/>
<path fill="#3C80CF" d="M35 27.6L19 .4h16z"/>
</g>
<g font-size="20">
<text fill="#2A3039" font-family="AvenirNext-Bold, Avenir Next" font-weight="bold" letter-spacing="-.3" transform="translate(45 18)">
<tspan x="0" y="20">example app</tspan>
</text>
<text fill="#8091A5" font-family="PTSerif-Italic, PT Serif" font-style="italic" transform="translate(45 -27)">
<tspan x="0" y="43">The .NET</tspan>
</text>
</g>
</g>
</svg>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<svg width="171" height="43" viewBox="0 0 171 43" xmlns="http://www.w3.org/2000/svg">
<title>
the-example-app-logo-php
</title>
<g fill="none" fill-rule="evenodd">
<g transform="translate(0 2)" fill-rule="nonzero">
<path fill="#D9453F" d="M3.75 37v-7.1H28V37z"/>
<circle fill="#F5A623" cx="9.3" cy="14.3" r="9.3"/>
<path fill="#3C80CF" d="M35 27.6L19 .4h16z"/>
</g>
<path d="M53.14 31.88c0-.52-.163-.967-.49-1.34-.327-.373-.817-.56-1.47-.56-.32 0-.613.05-.88.15-.267.1-.5.237-.7.41-.2.173-.36.377-.48.61-.12.233-.187.477-.2.73h4.22zm3.02 1.26v.4c0 .133-.007.26-.02.38h-7.22c.027.28.11.533.25.76.14.227.32.423.54.59.22.167.467.297.74.39.273.093.557.14.85.14.52 0 .96-.097 1.32-.29.36-.193.653-.443.88-.75l2.28 1.44a4.613 4.613 0 0 1-1.85 1.57c-.767.367-1.657.55-2.67.55a6.371 6.371 0 0 1-2.12-.35 5.148 5.148 0 0 1-1.75-1.02 4.723 4.723 0 0 1-1.18-1.65c-.287-.653-.43-1.4-.43-2.24 0-.813.14-1.55.42-2.21.28-.66.66-1.22 1.14-1.68a5.082 5.082 0 0 1 1.7-1.07 5.814 5.814 0 0 1 2.12-.38c.733 0 1.407.123 2.02.37s1.14.603 1.58 1.07c.44.467.783 1.033 1.03 1.7s.37 1.427.37 2.28zm8.4 4.86l-2.14-3.06-2.2 3.06h-3.8l3.98-5.22-3.6-4.74h3.82l1.86 2.66 1.88-2.66h3.7l-3.58 4.74L68.42 38h-3.86zm10.96-4.34h-.42c-.36 0-.723.017-1.09.05a4.483 4.483 0 0 0-.98.19c-.287.093-.523.23-.71.41-.187.18-.28.417-.28.71 0 .187.043.347.13.48.087.133.197.24.33.32a1.4 1.4 0 0 0 .46.17c.173.033.34.05.5.05.667 0 1.177-.183 1.53-.55.353-.367.53-.863.53-1.49v-.34zM69.5 29.4c.587-.56 1.27-.98 2.05-1.26.78-.28 1.577-.42 2.39-.42.84 0 1.55.103 2.13.31.58.207 1.05.527 1.41.96.36.433.623.98.79 1.64.167.66.25 1.443.25 2.35V38h-3v-1.06h-.06c-.253.413-.637.733-1.15.96-.513.227-1.07.34-1.67.34-.4 0-.813-.053-1.24-.16a3.632 3.632 0 0 1-1.17-.52 2.816 2.816 0 0 1-.87-.96c-.227-.4-.34-.893-.34-1.48 0-.72.197-1.3.59-1.74.393-.44.9-.78 1.52-1.02s1.31-.4 2.07-.48c.76-.08 1.5-.12 2.22-.12v-.16c0-.493-.173-.857-.52-1.09-.347-.233-.773-.35-1.28-.35-.467 0-.917.1-1.35.3-.433.2-.803.44-1.11.72L69.5 29.4zM93.68 38v-5.44c0-.28-.02-.55-.06-.81a1.934 1.934 0 0 0-.22-.67 1.265 1.265 0 0 0-.43-.45c-.18-.113-.417-.17-.71-.17-.28 0-.523.06-.73.18-.207.12-.38.28-.52.48a2.1 2.1 0 0 0-.31.69c-.067.26-.1.53-.1.81V38h-3.28v-5.68c0-.573-.113-1.027-.34-1.36-.227-.333-.587-.5-1.08-.5-.52 0-.923.207-1.21.62-.287.413-.43.92-.43 1.52V38h-3.3v-9.96h3.18v1.38h.04c.12-.213.27-.42.45-.62s.397-.377.65-.53a3.596 3.596 0 0 1 1.88-.51c.72 0 1.33.147 1.83.44.5.293.89.713 1.17 1.26.333-.52.76-.933 1.28-1.24.52-.307 1.153-.46 1.9-.46.68 0 1.25.123 1.71.37.46.247.83.567 1.11.96.28.393.483.833.61 1.32.127.487.19.963.19 1.43V38h-3.28zm16.84-5.02c0 .693-.107 1.36-.32 2a5.142 5.142 0 0 1-.93 1.69 4.594 4.594 0 0 1-1.49 1.17c-.587.293-1.253.44-2 .44a4.182 4.182 0 0 1-1.74-.37c-.547-.247-.973-.583-1.28-1.01h-.04v5.9h-3.28V28.04h3.12v1.22h.06c.307-.4.73-.75 1.27-1.05.54-.3 1.177-.45 1.91-.45.72 0 1.373.14 1.96.42.587.28 1.083.66 1.49 1.14.407.48.72 1.037.94 1.67.22.633.33 1.297.33 1.99zm-3.18 0c0-.32-.05-.633-.15-.94a2.442 2.442 0 0 0-.44-.81 2.225 2.225 0 0 0-.73-.57 2.252 2.252 0 0 0-1.02-.22c-.373 0-.707.073-1 .22-.293.147-.543.34-.75.58-.207.24-.367.513-.48.82a2.692 2.692 0 0 0 0 1.88c.113.307.273.58.48.82.207.24.457.433.75.58.293.147.627.22 1 .22.387 0 .727-.073 1.02-.22.293-.147.537-.34.73-.58.193-.24.34-.517.44-.83.1-.313.15-.63.15-.95zm5.28 5.02V22.88h3.32V38h-3.32zm12.78-6.12c0-.52-.163-.967-.49-1.34-.327-.373-.817-.56-1.47-.56-.32 0-.613.05-.88.15-.267.1-.5.237-.7.41-.2.173-.36.377-.48.61-.12.233-.187.477-.2.73h4.22zm3.02 1.26v.4c0 .133-.007.26-.02.38h-7.22c.027.28.11.533.25.76.14.227.32.423.54.59.22.167.467.297.74.39.273.093.557.14.85.14.52 0 .96-.097 1.32-.29.36-.193.653-.443.88-.75l2.28 1.44a4.613 4.613 0 0 1-1.85 1.57c-.767.367-1.657.55-2.67.55a6.371 6.371 0 0 1-2.12-.35 5.148 5.148 0 0 1-1.75-1.02 4.723 4.723 0 0 1-1.18-1.65c-.287-.653-.43-1.4-.43-2.24 0-.813.14-1.55.42-2.21.28-.66.66-1.22 1.14-1.68a5.082 5.082 0 0 1 1.7-1.07 5.814 5.814 0 0 1 2.12-.38c.733 0 1.407.123 2.02.37s1.14.603 1.58 1.07c.44.467.783 1.033 1.03 1.7s.37 1.427.37 2.28zm12.92.52h-.42c-.36 0-.723.017-1.09.05a4.483 4.483 0 0 0-.98.19c-.287.093-.523.23-.71.41-.187.18-.28.417-.28.71 0 .187.043.347.13.48.087.133.197.24.33.32a1.4 1.4 0 0 0 .46.17c.173.033.34.05.5.05.667 0 1.177-.183 1.53-.55.353-.367.53-.863.53-1.49v-.34zm-6.02-4.26c.587-.56 1.27-.98 2.05-1.26.78-.28 1.577-.42 2.39-.42.84 0 1.55.103 2.13.31.58.207 1.05.527 1.41.96.36.433.623.98.79 1.64.167.66.25 1.443.25 2.35V38h-3v-1.06h-.06c-.253.413-.637.733-1.15.96-.513.227-1.07.34-1.67.34-.4 0-.813-.053-1.24-.16a3.632 3.632 0 0 1-1.17-.52 2.816 2.816 0 0 1-.87-.96c-.227-.4-.34-.893-.34-1.48 0-.72.197-1.3.59-1.74.393-.44.9-.78 1.52-1.02s1.31-.4 2.07-.48c.76-.08 1.5-.12 2.22-.12v-.16c0-.493-.173-.857-.52-1.09-.347-.233-.773-.35-1.28-.35-.467 0-.917.1-1.35.3-.433.2-.803.44-1.11.72l-1.66-1.78zm22.54 3.58c0 .693-.107 1.36-.32 2a5.142 5.142 0 0 1-.93 1.69 4.594 4.594 0 0 1-1.49 1.17c-.587.293-1.253.44-2 .44a4.182 4.182 0 0 1-1.74-.37c-.547-.247-.973-.583-1.28-1.01h-.04v5.9h-3.28V28.04h3.12v1.22h.06c.307-.4.73-.75 1.27-1.05.54-.3 1.177-.45 1.91-.45.72 0 1.373.14 1.96.42.587.28 1.083.66 1.49 1.14.407.48.72 1.037.94 1.67.22.633.33 1.297.33 1.99zm-3.18 0c0-.32-.05-.633-.15-.94a2.442 2.442 0 0 0-.44-.81 2.225 2.225 0 0 0-.73-.57 2.252 2.252 0 0 0-1.02-.22c-.373 0-.707.073-1 .22-.293.147-.543.34-.75.58-.207.24-.367.513-.48.82a2.692 2.692 0 0 0 0 1.88c.113.307.273.58.48.82.207.24.457.433.75.58.293.147.627.22 1 .22.387 0 .727-.073 1.02-.22.293-.147.537-.34.73-.58.193-.24.34-.517.44-.83.1-.313.15-.63.15-.95zm16.32 0c0 .693-.107 1.36-.32 2a5.142 5.142 0 0 1-.93 1.69 4.594 4.594 0 0 1-1.49 1.17c-.587.293-1.253.44-2 .44a4.182 4.182 0 0 1-1.74-.37c-.547-.247-.973-.583-1.28-1.01h-.04v5.9h-3.28V28.04h3.12v1.22h.06c.307-.4.73-.75 1.27-1.05.54-.3 1.177-.45 1.91-.45.72 0 1.373.14 1.96.42.587.28 1.083.66 1.49 1.14.407.48.72 1.037.94 1.67.22.633.33 1.297.33 1.99zm-3.18 0c0-.32-.05-.633-.15-.94a2.442 2.442 0 0 0-.44-.81 2.225 2.225 0 0 0-.73-.57 2.252 2.252 0 0 0-1.02-.22c-.373 0-.707.073-1 .22-.293.147-.543.34-.75.58-.207.24-.367.513-.48.82a2.692 2.692 0 0 0 0 1.88c.113.307.273.58.48.82.207.24.457.433.75.58.293.147.627.22 1 .22.387 0 .727-.073 1.02-.22.293-.147.537-.34.73-.58.193-.24.34-.517.44-.83.1-.313.15-.63.15-.95z" fill="#000"/>
<path d="M53.02 16H47.4l.1-.58c.32-.12.637-.217.95-.29.313-.073.657-.137 1.03-.19L52 3.04h-3.06L47.28 5.9h-.52c.013-.267.043-.57.09-.91.047-.34.097-.687.15-1.04.053-.353.11-.7.17-1.04.06-.34.123-.643.19-.91h11.6c-.053.267-.12.567-.2.9a46.818 46.818 0 0 1-.55 2.07c-.1.34-.197.65-.29.93h-.52l-.42-2.86h-3.06l-2.52 11.9c.32.067.623.133.91.2.287.067.557.16.81.28l-.1.58zm6.24-13.82v-.46c.2-.08.423-.163.67-.25.247-.087.503-.167.77-.24.267-.073.527-.133.78-.18.253-.047.493-.077.72-.09l.42.18-1.8 8.4h.1c.253-.533.54-1.03.86-1.49.32-.46.657-.86 1.01-1.2.353-.34.723-.607 1.11-.8.387-.193.78-.29 1.18-.29.613 0 1.057.177 1.33.53.273.353.41.79.41 1.31 0 .36-.033.747-.1 1.16-.067.413-.147.813-.24 1.2l-1.14 4.92h1.48v.52a3.298 3.298 0 0 1-.96.52c-.373.133-.747.2-1.12.2-.387 0-.653-.08-.8-.24a.757.757 0 0 1-.22-.52c0-.24.033-.51.1-.81.067-.3.133-.603.2-.91l.72-3.34c.093-.413.177-.8.25-1.16.073-.36.11-.707.11-1.04 0-.307-.067-.567-.2-.78-.133-.213-.313-.32-.54-.32-.413 0-.813.147-1.2.44a6.226 6.226 0 0 0-1.08 1.06 9.592 9.592 0 0 0-.88 1.29c-.253.447-.447.817-.58 1.11L59.56 16h-1.68l2.74-13.64-1.36-.18zM75.9 14.22c-.12.253-.293.5-.52.74a4.1 4.1 0 0 1-1.84 1.11c-.387.113-.8.17-1.24.17-1.107 0-1.957-.317-2.55-.95-.593-.633-.89-1.51-.89-2.63 0-.947.133-1.84.4-2.68.267-.84.64-1.573 1.12-2.2a5.258 5.258 0 0 1 1.75-1.48c.687-.36 1.45-.54 2.29-.54.64 0 1.19.163 1.65.49.46.327.69.81.69 1.45 0 .947-.47 1.693-1.41 2.24-.94.547-2.457.9-4.55 1.06-.053.227-.09.447-.11.66-.02.213-.03.42-.03.62 0 .8.203 1.453.61 1.96.407.507 1.003.76 1.79.76.253 0 .503-.04.75-.12.247-.08.48-.18.7-.3.22-.12.42-.247.6-.38s.337-.253.47-.36l.32.38zm-2.1-7.6c-.573.013-1.107.283-1.6.81-.493.527-.913 1.43-1.26 2.71 1.293-.04 2.3-.263 3.02-.67.72-.407 1.08-.97 1.08-1.69 0-.28-.083-.54-.25-.78-.167-.24-.497-.367-.99-.38zm11.58 8.32c.347.04.67.103.97.19.3.087.577.183.83.29l-.1.58H81.6l.1-.58c.307-.133.603-.233.89-.3.287-.067.577-.127.87-.18l2.5-11.86a5.661 5.661 0 0 1-.82-.21 7.047 7.047 0 0 1-.72-.29l.14-.58h1.8c.533 0 1.117-.027 1.75-.08.633-.053 1.237-.08 1.81-.08.64 0 1.23.063 1.77.19s1 .327 1.38.6c.38.273.677.613.89 1.02.213.407.32.89.32 1.45a5.46 5.46 0 0 1-.43 2.18 5.016 5.016 0 0 1-1.24 1.74c-.54.493-1.19.88-1.95 1.16-.76.28-1.607.42-2.54.42h-.33a13.574 13.574 0 0 1-1.04-.04c-.18-.013-.33-.027-.45-.04l-.92 4.42zM89.4 2.88a10.642 10.642 0 0 0-1.5.1l-1.36 6.48c.067.013.163.027.29.04.127.013.26.023.4.03l.4.02c.127.007.23.01.31.01 1.387 0 2.433-.397 3.14-1.19.707-.793 1.06-1.877 1.06-3.25a3.51 3.51 0 0 0-.11-.91 1.52 1.52 0 0 0-.42-.71c-.207-.2-.487-.353-.84-.46-.353-.107-.81-.16-1.37-.16zM98.36 16H93.3l.1-.58c.293-.133.587-.237.88-.31.293-.073.587-.13.88-.17l2.5-11.86c-.6-.12-1.113-.287-1.54-.5l.14-.58h5.06l-.12.58c-.227.12-.487.22-.78.3-.293.08-.573.147-.84.2l-1.1 5.22h6.28l1.12-5.22a13.115 13.115 0 0 1-.75-.21 3.509 3.509 0 0 1-.67-.29l.14-.58h5.06l-.12.58c-.267.133-.55.237-.85.31-.3.073-.597.137-.89.19l-2.52 11.86c.6.107 1.107.267 1.52.48l-.1.58h-5.06l.1-.58c.253-.107.517-.2.79-.28.273-.08.55-.147.83-.2l1.18-5.6h-6.3l-1.16 5.6c.24.053.48.12.72.2.24.08.46.173.66.28l-.1.58zm13.1-1.06c.347.04.67.103.97.19.3.087.577.183.83.29l-.1.58h-5.48l.1-.58c.307-.133.603-.233.89-.3.287-.067.577-.127.87-.18l2.5-11.86a5.661 5.661 0 0 1-.82-.21 7.047 7.047 0 0 1-.72-.29l.14-.58h1.8c.533 0 1.117-.027 1.75-.08.633-.053 1.237-.08 1.81-.08.64 0 1.23.063 1.77.19s1 .327 1.38.6c.38.273.677.613.89 1.02.213.407.32.89.32 1.45a5.46 5.46 0 0 1-.43 2.18 5.016 5.016 0 0 1-1.24 1.74c-.54.493-1.19.88-1.95 1.16-.76.28-1.607.42-2.54.42h-.33a13.574 13.574 0 0 1-1.04-.04c-.18-.013-.33-.027-.45-.04l-.92 4.42zm4.02-12.06a10.642 10.642 0 0 0-1.5.1l-1.36 6.48c.067.013.163.027.29.04.127.013.26.023.4.03l.4.02c.127.007.23.01.31.01 1.387 0 2.433-.397 3.14-1.19.707-.793 1.06-1.877 1.06-3.25a3.51 3.51 0 0 0-.11-.91 1.52 1.52 0 0 0-.42-.71c-.207-.2-.487-.353-.84-.46-.353-.107-.81-.16-1.37-.16z" fill="#8091A5"/>
</g>
</svg>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
{
"name": "The Example App",
"icons": [
{
"src": "/android-chrome-96x96.png",
"sizes": "96x96",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}
\ No newline at end of file
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="128.000000pt" height="128.000000pt" viewBox="0 0 128.000000 128.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,128.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M700 1275 c0 -2 26 -49 58 -102 32 -54 154 -261 271 -460 117 -200
216 -363 220 -363 3 0 6 209 6 465 l0 465 -277 0 c-153 0 -278 -2 -278 -5z"/>
<path d="M282 1114 c-28 -7 -73 -27 -100 -44 -138 -84 -190 -267 -119 -412
119 -243 466 -243 584 0 23 49 27 69 27 142 0 73 -4 93 -27 137 -32 62 -95
126 -152 153 -60 29 -153 39 -213 24z"/>
<path d="M162 125 l0 -125 424 0 424 0 0 125 0 125 -424 0 -425 0 1 -125z"/>
</g>
</svg>
This diff is collapsed.
This diff is collapsed.
/*
* The purpose of this module is to render the category page when the route is requested
*/
const {
getCourses,
getCourse,
getCategories,
getCoursesByCategory
} = require('./../services/contentful')
const attachEntryState = require('../lib/entry-state')
const enhanceBreadcrumb = require('../lib/enhance-breadcrumb')
const shouldAttachEntryState = require('../lib/should-attach-entry-state')
const { updateCookie } = require('../lib/cookies')
const { translate } = require('../i18n/i18n')
/**
* Renders courses list when `/courses` route is requested
*
* @param request - Object - Express request object
* @param response - Object - Express response object
* @param next - Function - express callback
*
* @returns {undefined}
*/
module.exports.getCourses = async (request, response, next) => {
// Get all the entries of content type course
let courses = []
let categories = []
courses = await getCourses(response.locals.currentLocale.code, response.locals.currentApi.id)
// Attach entry state flags when using preview API
if (shouldAttachEntryState(response)) {
courses = await attachEntryStateToCourses(courses)
}
categories = await getCategories(response.locals.currentLocale.code, response.locals.currentApi.id)
response.render('courses', {
title: `${translate('allCoursesLabel', response.locals.currentLocale.code)} (${courses.length})`,
categories,
courses
})
}
/**
* Renders a course when `/courses/:slug` route is requested
*
* @param request - Object - Express request object
* @param response - Object - Express response object
* @param next - Function - express callback
*
* @returns {undefined}
*/
module.exports.getCourse = async (request, response, next) => {
let course
try {
course = await getCourse(request.params.slug, response.locals.currentLocale.code, response.locals.currentApi.id)
} catch (err) {
if (err.status === 404) {
err.message = translate('errorMessage404Course', response.currentLocale)
}
throw err
}
// Get lessons
if (!course.fields.lessons) {
course.fields.lessons = []
}
const lessons = course.fields.lessons
let {lesson, lessonIndex} = getNextLesson(lessons, request.params.lslug)
// Manage state of viewed lessons
const cookie = request.cookies.visitedLessons
let visitedLessons = cookie || []
visitedLessons.push(course.sys.id)
visitedLessons = [...new Set(visitedLessons)]
updateCookie(response, 'visitedLessons', visitedLessons)
// Attach entry state flags when using preview API
if (shouldAttachEntryState(response)) {
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})
}
/**
* Renders a courses list by a category when `/courses/category/:category` route is requested
*
* @param request - Object - Express request object
* @param response - Object - Express response object
* @param next - Function - Express callback
*
* @returns {undefined}
*/
module.exports.getCoursesByCategory = async (request, response, next) => {
const categories = await getCategories(response.locals.currentLocale.code, response.locals.currentApi.id)
const activeCategory = categories.find((category) => category.fields.slug === request.params.category)
if (!activeCategory) {
const error = new Error(translate('errorMessage404Category', response.currentLocale))
error.status = 404
throw error
}
// We get all the entries with the content type `course` filtered by a category
let courses = await getCoursesByCategory(activeCategory.sys.id, response.locals.currentLocale.code, response.locals.currentApi.id)
// Enhance the breadcrumbs with the active category
enhanceBreadcrumb(request, activeCategory)
// Attach entry state flags when using preview API
if (shouldAttachEntryState(response)) {
courses = await attachEntryStateToCourses(courses)
}
response.render('courses', { title: `${activeCategory.fields.title} (${courses.length})`, categories, courses })
}
/**
* Renders a lesson details when `/courses/:courseSlug/lessons/:lessonSlug` route is requested
*
* @param request - Object - Express request object
* @param response - Object - Express response object
* @param next - Function - express callback
*
* @returns {undefined}
*/
module.exports.getLesson = async (request, response, next) => {
let course = await getCourse(request.params.cslug, response.locals.currentLocale.code, response.locals.currentApi.id)
const lessons = course.fields.lessons
let {lesson, nextLesson} = getNextLesson(lessons, request.params.lslug)
if (!lesson) {
const error = new Error(translate('errorMessage404Lesson', response.currentLocale))
error.status = 404
throw error
}
// Save visited lessons
const cookie = request.cookies.visitedLessons
let visitedLessons = cookie || []
visitedLessons.push(lesson.sys.id)
visitedLessons = [...new Set(visitedLessons)]
updateCookie(response, 'visitedLessons', visitedLessons)
// Attach entry state flags when using preview API
if (shouldAttachEntryState(response)) {
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,
lesson,
lessons,
nextLesson,
visitedLessons
})
}
function attachEntryStateToCourses (courses) {
return Promise.all(
courses
.map((course) => {
// Do not include lessons in entry state detection
const cleanCourse = Object.assign({}, course)
delete cleanCourse.fields.lessons
return cleanCourse
})
.map(attachEntryState)
)
}
function getNextLesson (lessons, lslug) {
const lessonIndex = lessons.findIndex((lesson) => lesson.fields.slug === lslug)
let lesson = lessons[lessonIndex]
const nextLesson = lessons[lessonIndex + 1] || null
return {
lessonIndex,
lesson,
nextLesson
}
}
const { translate } = require('../i18n/i18n')
/**
* Renders imprint page when `/imprint` route is requested
* @param request - Object - Express request
* @param response - Object - Express response
* @param next - Function - Express callback
* @returns {undefined}
*/
module.exports.getImprint = (request, response, next) => {
response.render('imprint', {
title: translate('imprintLabel', response.locals.currentLocale.code)
})
}
/**
* This module connects rendering modules to routes
*/
const express = require('express')
const router = express.Router()
const { catchErrors } = require('../handlers/errorHandlers')
const { getCourses, getCourse, getLesson, getCoursesByCategory } = require('./courses')
const { getSettings, postSettings } = require('./settings')
const { getLandingPage } = require('./landingPage')
const { getImprint } = require('./imprint')
// Display settings in case of invalid credentials
router.all('*', async (request, response, next) => {
if (response.locals.forceSettingsRoute) {
await getSettings(request, response, next)
return
}
next()
})
// GET the home landing page
router.get('/', catchErrors(getLandingPage))
// Courses routes
router.get('/courses', catchErrors(getCourses))
router.get('/courses/categories', catchErrors(getCourses))
router.get('/courses/categories/:category', catchErrors(getCoursesByCategory))
router.get('/courses/:slug', catchErrors(getCourse))
router.get('/courses/:slug/lessons', catchErrors(getCourse))
router.get('/courses/:cslug/lessons/:lslug', catchErrors(getLesson))
// Settings routes
router.get('/settings', catchErrors(getSettings))
router.post('/settings', catchErrors(postSettings))
// Imprint route
router.get('/imprint', catchErrors(getImprint))
module.exports = router
/**
* This module renders a layout when its route is requested
* It is used for pages like home page
*/
const url = require('url')
const { getLandingPage } = require('../services/contentful')
const attachEntryState = require('../lib/entry-state')
const shouldAttachEntryState = require('../lib/should-attach-entry-state')
/**
* Renders a landing page when `/` route is requested
* based on the pathname an entry is queried from contentful
* and a view is rendered from the pulled data
*
* @param request - Object - Express request
* @param response - Object - Express response
* @param next - Function - Express callback
* @returns {undefined}
*/
module.exports.getLandingPage = async (request, response, next) => {
let pathname = url.parse(request.url).pathname.split('/').filter(Boolean)[0]
pathname = pathname || 'home'
let landingPage = await getLandingPage(
pathname,
response.locals.currentLocale.code,
response.locals.currentApi.id
)
// Attach entry state flags when using preview API
if (shouldAttachEntryState(response)) {
landingPage = await attachEntryState(landingPage)
}
response.render('landingPage', { title: pathname, landingPage })
}
/**
* This module renders the settings page when `settings` route is requested
* it also saves the settings to a cookie
*/
const { uniqWith, isEqual } = require('lodash')
const { createClient } = require('contentful')
const { isCustomCredentials, updateSettingsQuery } = require('../helpers')
const { updateCookie } = require('../lib/cookies')
const { translate } = require('../i18n/i18n')
const { initClients, getSpace } = require('../services/contentful')
const SETTINGS_NAME = 'theExampleAppSettings'
async function renderSettings (response, opts) {
// Get connected space to display the space name on top of the settings
let space = false
try {
space = await getSpace()
} catch (error) {
// We handle errors within the settings page.
// No need to throw here.
}
response.render('settings', {
title: translate('settingsLabel', response.locals.currentLocale.code),
errors: {},
hasErrors: false,
success: false,
space,
...opts
})
}
/**
* Renders the settings page when `/settings` route is requested
*
* @param request - Object - Express request
* @param response - Object - Express response
* @param next - Function - Express callback
*
* @returns {undefined}
*/
module.exports.getSettings = async (request, response, next) => {
const currentLocale = response.locals.currentLocale
const { settings } = response.locals
const errorList = await generateErrorList(settings, currentLocale)
// If no errors detected, update app to use new settings
if (!errorList.length) {
applyUpdatedSettings(request, response, settings)
}
const errors = generateErrorDictionary(errorList)
await renderSettings(response, {
settings,
errors,
hasErrors: errorList.length > 0,
success: isCustomCredentials(settings) && errorList.length === 0
})
}
/**
* Save settings when POST request is triggered to the `/settings` route
* and render the settings page
*
* @param request - Object - Express request
* @param response - Object - Express response
* @param next - Function - Express callback
*
* @returns {undefined}
*/
module.exports.postSettings = async (request, response, next) => {
const currentLocale = response.locals.currentLocale
let { spaceId, deliveryToken, previewToken, editorialFeatures } = request.body
if (request.query.reset) {
spaceId = process.env.CONTENTFUL_SPACE_ID
deliveryToken = process.env.CONTENTFUL_DELIVERY_TOKEN
previewToken = process.env.CONTENTFUL_PREVIEW_TOKEN
}
const settings = {
spaceId,
deliveryToken,
previewToken,
editorialFeatures: !!editorialFeatures
}
const errorList = await generateErrorList(settings, currentLocale)
// If no errors detected, update app to use new settings
if (!errorList.length) {
applyUpdatedSettings(request, response, settings)
}
const errors = generateErrorDictionary(errorList)
await renderSettings(response, {
settings,
errors,
hasErrors: errorList.length > 0,
success: errorList.length === 0
})
}
async function generateErrorList (settings, currentLocale) {
const { spaceId, deliveryToken, previewToken } = settings
let errorList = []
// Validate required fields.
if (!spaceId) {
errorList.push({
field: 'spaceId',
message: translate('fieldIsRequiredLabel', currentLocale.code)
})
}
if (!deliveryToken) {
errorList.push({
field: 'deliveryToken',
message: translate('fieldIsRequiredLabel', currentLocale.code)
})
}
if (!previewToken) {
errorList.push({
field: 'previewToken',
message: translate('fieldIsRequiredLabel', currentLocale.code)
})
}
// Validate space and delivery access token.
if (spaceId && deliveryToken) {
try {
await createClient({
space: spaceId,
accessToken: deliveryToken,
// Environment variable is used here to enable testing this app internally at Contentful.
// You can just omit the host since it defaults to 'cdn.contentful.com'
host: process.env.CONTENTFUL_DELIVERY_API_HOST
}).getSpace()
} catch (err) {
if (err.response.status === 401) {
errorList.push({
field: 'deliveryToken',
message: translate('deliveryKeyInvalidLabel', currentLocale.code)
})
} else if (err.response.status === 404) {
errorList.push({
field: 'spaceId',
message: translate('spaceOrTokenInvalid', currentLocale.code)
})
} else {
errorList.push({
field: 'deliveryToken',
message: `${translate('somethingWentWrongLabel', currentLocale.code)}: ${err.response.data.message}`
})
}
}
}
// Validate space and CPA access token.
if (spaceId && previewToken) {
try {
await createClient({
space: spaceId,
accessToken: previewToken,
// Environment variable is used here to enable testing this app internally at Contentful.
// You should use 'preview.contentful.com' as host to use the preview api
host: process.env.CONTENTFUL_PREVIEW_API_HOST
}).getSpace()
} catch (err) {
if (err.response.status === 401) {
errorList.push({
field: 'previewToken',
message: translate('previewKeyInvalidLabel', currentLocale.code)
})
} else if (err.response.status === 404) {
errorList.push({
field: 'spaceId',
message: translate('spaceOrTokenInvalid', currentLocale.code)
})
} else {
errorList.push({
field: 'previewToken',
message: `${translate('somethingWentWrongLabel', currentLocale.code)}: ${err.response.data.message}`
})
}
}
}
errorList = uniqWith(errorList, isEqual)
return errorList
}
// Generate error dictionary
// Format: { FIELD_NAME: [array, of, error, messages] }
function generateErrorDictionary (errorList) {
return errorList.reduce((errors, error) => {
return {
...errors,
[error.field]: [
...(errors[error.field] || []),
error.message
]
}
}, {})
}
function applyUpdatedSettings (request, response, settings) {
// Store new settings
updateCookie(response, SETTINGS_NAME, settings)
response.locals.settings = settings
// Update query settings string
updateSettingsQuery(request, response, settings)
// Reinit clients
initClients(settings)
}
/**
* The purpose of this module is to get data from contentful
*/
const { createClient } = require('contentful')
let deliveryClient = null
let previewClient = null
/**
* Initialize the contentful Client
* @param options {spaceId: string, deliveryToken: string, previewToken: string}
*
* @returns {undefined}
*/
module.exports.initClients = (options) => {
// Getting the app version
const { version } = require('../package.json')
const applicationName = `the-example-app.nodejs/${version}`
const config = options || {
spaceId: process.env.CONTENTFUL_SPACE_ID,
deliveryToken: process.env.CONTENTFUL_DELIVERY_TOKEN,
previewToken: process.env.CONTENTFUL_PREVIEW_TOKEN
}
deliveryClient = createClient({
application: applicationName,
space: config.spaceId,
accessToken: config.deliveryToken,
// Environment variable is used here to enable testing this app internally at Contentful.
// You can just omit the host since it defaults to 'cdn.contentful.com'
host: process.env.CONTENTFUL_DELIVERY_API_HOST,
removeUnresolved: true
})
previewClient = createClient({
application: applicationName,
space: config.spaceId,
accessToken: config.previewToken,
// Environment variable is used here to enable testing this app internally at Contentful.
// You should use 'preview.contentful.com' as host to use the preview api
host: process.env.CONTENTFUL_PREVIEW_API_HOST,
removeUnresolved: true
})
}
/**
* Get the Space the app is connected to. Used for the settings form and to get all available locales
* @param api - string - the api to use, cda or cap. Default: 'cda'
* @returns {undefined}
*/
module.exports.getSpace = throwOnEmptyResult('Space', (api = 'cda') => {
return getClient(api).getSpace()
})
/**
* Get the environment locales
* @param api - string - the api to use, cda or cap. Default: 'cda'
* @returns {undefined}
*/
module.exports.getLocales = throwOnEmptyResult('Environment', (api = 'cda') => {
return getClient(api).getLocales()
.then((response) => response.items)
})
/**
* Gets an entry. Used to detect the `Draft` or `Pending Changes` state
* @param entryId - string - the entry id
* @param api - string - the api to use fetching the entry
*
* @returns {Object}
*/
module.exports.getEntry = throwOnEmptyResult('Entry', (entryId, contentType, api = 'cda') => {
return getClient(api).getEntries({content_type: contentType, 'sys.id': entryId})
.then((response) => response.items[0])
})
/**
* Get all entries with content_type `course`
* @param locale - string - the locale of the entry [default: 'en-US']
* @param api - string the api enpoint to use when fetching the data
* @returns {Array<Object>}
*/
module.exports.getCourses = throwOnEmptyResult('Course', (locale = 'en-US', api = 'cda') => {
return getClient(api).getEntries({
content_type: 'course',
locale,
order: '-sys.createdAt', // Ordering the entries by creation date
include: 1 // We use include param to increase the link level, the include value goes from 1 to 6
})
.then((response) => response.items)
})
/**
* Get entries of content_type `layout` e.g. Landing page
* @param slug - string - the slug of the entry to use in the query
* @param locale - string - locale of the entry to request [default: 'en-US']
* @param api - string - the api enpoint to use when fetching the data
* @returns {Object}
*/
module.exports.getLandingPage = (slug, locale = 'en-US', api = 'cda') => {
// Even though we need a single entry, we request it using the collection endpoint
// To get all the linked refs in one go, the SDK will use the data and resolve the links automatically
return getClient(api).getEntries({
content_type: 'layout',
locale,
'fields.slug': slug,
include: 3
})
.then((response) => response.items[0])
}
/**
* Get an entry with content_type `course`
* @param slug - string - the slug of the entry to use in the query
* @param locale - string - locale of the entry to request [default: 'en-US']
* @param api - string - the api enpoint to use when fetching the data
* @returns {Object}
*/
module.exports.getCourse = throwOnEmptyResult('Course', (slug, locale = 'en-US', api = 'cda') => {
// Even though we need a single entry, we request it using the collection endpoint
// To get all the linked refs in one go, the SDK will use the data and resolve the links automatically
return getClient(api).getEntries({
content_type: 'course',
'fields.slug': slug,
locale,
include: 2
})
.then((response) => response.items[0])
})
module.exports.getCategories = throwOnEmptyResult('Course', (locale = 'en-US', api = 'cda') => {
return getClient(api).getEntries({content_type: 'category', locale})
.then((response) => response.items)
})
/**
* Get Courses by Categories
* To get a course by category, simply query all entries
* with a query params `fields.categories.sys.id` equal to the desired category id
* Note that you need to send the `content_type` param to be able to query the entry
* @param category - string - the id of the category
* @param locale - string - locale of the entry to request [default: 'en-US']
* @param api - string - the api enpoint to use when fetching the data
* @returns {Object}
*/
module.exports.getCoursesByCategory = throwOnEmptyResult('Category', (category, locale = 'en-US', api = 'cda') => {
return getClient(api).getEntries({
content_type: 'course',
'fields.categories.sys.id': category,
locale,
order: '-sys.createdAt',
include: 1
})
.then((response) => response.items)
})
// Utility function
function getClient (api = 'cda') {
return api === 'cda' ? deliveryClient : previewClient
}
/**
* Utility function for wrapping regular API calls.
* This is done for easily catching 404 errors.
* @param {string} context The type of result the function is looking for
* @param {Function} fn The function to wrap
* @return {Object} The result of `fn`, if not empty
* @throws {Error} When `fn` returns an empty result
*/
function throwOnEmptyResult (context, fn) {
return function (...params) {
return fn(...params)
.then((data) => {
if (!data) {
var err = new Error(`${context} Not Found`)
err.status = 404
throw err
}
return data
})
}
}
sonar.projectKey=nodeProject
sonar.projectName=nodeProject
sonar.projectVersion=1.0
sonar.language=js
sonar.sourceEncoding=UTF-8
sonar.test.inclusions=test/**/*.js
sonar.javascript.lcov.reportPaths=coverage/lcov.info
module.exports = {
'env': {
'node': true,
'jest': true
}
}
NODE_ENV=development
CONTENTFUL_SPACE_ID=qz0n5cdakyl9
CONTENTFUL_DELIVERY_TOKEN=580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8
CONTENTFUL_PREVIEW_TOKEN=e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b
CONTENTFUL_DELIVERY_API_HOST=cdn.contentful.com
CONTENTFUL_PREVIEW_API_HOST=preview.contentful.com
CONTENTFUL_QA_SPACE_ID=jnzexv31feqf
CONTENTFUL_QA_DELIVERY_TOKEN=7c1c321a528a25c351c1ac5f53e6ddc6bcce0712ecebec60817f53b35dd3c42b
CONTENTFUL_QA_PREVIEW_TOKEN=4310226db935f0e9b6b34fb9ce6611e2061abe1aab5297fa25bd52af5caa531a
CONTENTFUL_QA_EMPTY_STATES_SPACE_ID=85si70kq8sjj
CONTENTFUL_QA_EMPTY_STATES_DELIVERY_TOKEN=eecf609f1411e10439f95c7d89a86c2661476bc3f92f8f0ead0e303e8e4ce9f9
CONTENTFUL_QA_EMPTY_STATES_PREVIEW_TOKEN=f9d927308234f773f90be351e72010fa6ca8f0e36b94cb3cc0f84a40c43f2e4d
PORT=3007
const request = require('supertest')
const app = require('../../app')
describe('courses', () => {
test('it should render a list of courses', () => {
return request(app).get('/courses')
.expect(200)
.then((response) => {
expect(response.text.match(/<h1>All courses /)).toBeTruthy()
})
})
test('it should render a course', () => {
return request(app).get('/courses/hello-contentful')
.expect(200)
.then((response) => {
expect(response.text.match(/class="course__title"/)).toBeTruthy()
})
})
test('it should return 404 when a course does not exist', () => {
return request(app).get('/courses/dont-exist').expect(404)
})
test('it should render a lesson', () => {
return request(app).get('/courses/hello-contentful/lessons/content-management')
.expect(200)
.then((response) => {
expect(response.text.match(/class="lesson__title"/)).toBeTruthy()
})
})
})
const request = require('supertest')
const app = require('../../app')
describe('Home page', () => {
test('it should render the landing page', () => {
return request(app).get('/').expect(200)
})
})
const cheerio = require('cheerio')
const cookie = require('cookie')
const cookieParser = require('cookie-parser')
const request = require('supertest')
const app = require('../../app')
function getSettingsCookie (response) {
try {
const cookies = response.headers['set-cookie']
const settingsCookie = cookies.find((cookie) => cookie.startsWith('theExampleAppSettings='))
const parsedCookie = cookie.parse(settingsCookie)
return cookieParser.JSONCookie(parsedCookie.theExampleAppSettings)
} catch (err) {
throw new Error(`Settings cookie was not set or could not be parsed. ${err.message}`)
}
}
describe('settings', () => {
test('should render', () => {
return request(app).get('/settings')
.expect(200)
.then((response) => {
const $ = cheerio.load(response.text)
const title = $('main h1')
expect(title.text()).toBe('Settings')
const status = $('main .status-block.status-block--info')
expect(status.text()).toMatch(/Connected space:/)
const inputSpaceId = $('#input-space-id')
expect(inputSpaceId.val()).toBe(process.env.CONTENTFUL_SPACE_ID)
const inputCda = $('#input-delivery-token')
expect(inputCda.val()).toBe(process.env.CONTENTFUL_DELIVERY_TOKEN)
const inputCpa = $('#input-preview-token')
expect(inputCpa.val()).toBe(process.env.CONTENTFUL_PREVIEW_TOKEN)
const inputEditorialFeatures = $('#input-editorial-features')
expect(inputEditorialFeatures.prop('checked')).toBeFalsy()
})
})
test('should have the editorial features enabled when query parameter is set and set cookie for it', () => {
return request(app).get('/settings?editorial_features=enabled')
.expect(200)
.expect((response) => {
const cookie = getSettingsCookie(response)
if (cookie.editorialFeatures === false) {
throw new Error('Editorial features value in cookie should not be false')
}
if (cookie.spaceId !== process.env.CONTENTFUL_SPACE_ID) {
throw new Error('Did not set correct cookie value for SpaceID')
}
if (cookie.deliveryToken !== process.env.CONTENTFUL_DELIVERY_TOKEN) {
throw new Error('Did not set correct cookie value for CDA access token')
}
if (cookie.previewToken !== process.env.CONTENTFUL_PREVIEW_TOKEN) {
throw new Error('Did not set correct cookie value for CPA access token')
}
})
.then((response) => {
const $ = cheerio.load(response.text)
const inputEditorialFeatures = $('#input-editorial-features')
expect(inputEditorialFeatures.prop('checked')).toBeTruthy()
})
})
test('should have the editorial features disabled when query parameter is set and set cookie for it', () => {
return request(app).get('/settings?editorial_features=disabled')
.expect(200)
.expect((response) => {
const cookie = getSettingsCookie(response)
if (cookie.editorialFeatures === true) {
throw new Error('Editorial features value in cookie should not be true')
}
})
.then((response) => {
const $ = cheerio.load(response.text)
const inputEditorialFeatures = $('#input-editorial-features')
expect(inputEditorialFeatures.prop('checked')).toBeFalsy()
})
})
})
const http = require('http')
const { resolve } = require('path')
const execa = require('execa')
const argv = require('yargs').argv
require('dotenv').config({ path: resolve(__dirname, 'e2e-variables.env') })
const app = require('../app')
const {
CONTENTFUL_SPACE_ID, CONTENTFUL_DELIVERY_TOKEN, CONTENTFUL_PREVIEW_TOKEN,
CONTENTFUL_QA_SPACE_ID, CONTENTFUL_QA_DELIVERY_TOKEN, CONTENTFUL_QA_PREVIEW_TOKEN,
CONTENTFUL_QA_EMPTY_STATES_SPACE_ID, CONTENTFUL_QA_EMPTY_STATES_DELIVERY_TOKEN, CONTENTFUL_QA_EMPTY_STATES_PREVIEW_TOKEN,
PORT
} = process.env
const TEST_PORT = parseInt(PORT)
app.set('port', TEST_PORT)
const server = http.createServer(app)
server.on('error', console.error)
server.listen(TEST_PORT, function () {
console.log(`Testserver listening at port ${TEST_PORT}`)
const cypressBin = resolve(__dirname, 'e2e', 'node_modules', '.bin', 'cypress')
const env = [
`LANGUAGE=nodejs`,
`CONTENTFUL_SPACE_ID=${CONTENTFUL_SPACE_ID}`,
`CONTENTFUL_DELIVERY_TOKEN=${CONTENTFUL_DELIVERY_TOKEN}`,
`CONTENTFUL_PREVIEW_TOKEN=${CONTENTFUL_PREVIEW_TOKEN}`,
`CONTENTFUL_QA_SPACE_ID=${CONTENTFUL_QA_SPACE_ID}`,
`CONTENTFUL_QA_DELIVERY_TOKEN=${CONTENTFUL_QA_DELIVERY_TOKEN}`,
`CONTENTFUL_QA_PREVIEW_TOKEN=${CONTENTFUL_QA_PREVIEW_TOKEN}`,
`CONTENTFUL_QA_EMPTY_STATES_SPACE_ID=${CONTENTFUL_QA_EMPTY_STATES_SPACE_ID}`,
`CONTENTFUL_QA_EMPTY_STATES_DELIVERY_TOKEN=${CONTENTFUL_QA_EMPTY_STATES_DELIVERY_TOKEN}`,
`CONTENTFUL_QA_EMPTY_STATES_PREVIEW_TOKEN=${CONTENTFUL_QA_EMPTY_STATES_PREVIEW_TOKEN}`
]
let command = [
'run',
!process.env.CI ? '--headed' : null
]
if (argv.dev) {
command = [
'open'
]
}
execa(cypressBin, [
...command,
'--env',
env.join(',')
].filter(Boolean))
.then((result) => {
console.log('✔ e2e test succeeded:')
console.log(result.stdout)
server.close()
process.exit(0)
})
.catch((error) => {
console.log(`✖ e2e test failed with status code ${error.code}:`)
console.error(error.stdout)
console.error(error.stderr)
server.close()
process.exit(1)
})
})
/* global describe, test, beforeAll, afterEach, jest, expect */
const { getCourses, getCourse, getCoursesByCategory, getLesson } = require('../../routes/courses')
const { getSettings } = require('../../routes/settings')
const { mockCourse, mockCategory } = require('./mocks/index')
const { translate, translationAvaliable, initializeTranslations } = require('../../i18n/i18n')
jest.mock('../../services/contentful')
const contentful = require('../../services/contentful')
const request = {
app: {
locals: {
breadcrumb: []
}
}
}
const response = {
locals: {
settings: {
space: 'spaceId',
cda: 'cda',
cpa: 'cpa',
editorialFeatures: false
},
currentLocale: {
code: 'en-US'
},
currentApi: {
id: 'cda'
}
}
}
beforeAll(() => {
initializeTranslations()
contentful.getCourses.mockImplementation(() => [mockCourse])
contentful.getCourse.mockImplementation(() => mockCourse)
contentful.getCategories.mockImplementation(() => [mockCategory])
contentful.getCoursesByCategory.mockImplementation(() => [])
response.render = jest.fn()
response.cookie = jest.fn()
request.cookies = { visitedLessons: [] }
})
afterEach(() => {
response.render.mockClear()
response.render.mockReset()
})
describe('Courses', () => {
test('it should courses list once', async () => {
await getCourses(request, response)
expect(response.render.mock.calls[0][0]).toBe('courses')
expect(response.render.mock.calls[0][1].title).toBe('All courses (1)')
expect(response.render.mock.calls[0][1].courses.length).toBe(1)
expect(response.render.mock.calls.length).toBe(1)
})
test('it should render single course once', async () => {
request.params = {slug: 'slug', lslug: 'lessonSlug'}
await getCourse(request, response)
expect(response.render.mock.calls[0][0]).toBe('course')
expect(response.render.mock.calls[0][1].title).toBe(mockCourse.fields.title)
expect(response.render.mock.calls[0][1].course.sys.id).toBe(mockCourse.sys.id)
expect(response.render.mock.calls[0][1].lesson.sys.id).toBe(mockCourse.fields.lessons[0].sys.id)
expect(response.render.mock.calls.length).toBe(1)
})
test('it should render list of courses by categories', async () => {
request.params = {slug: 'slug', lslug: 'lslug', category: 'categorySlug'}
await getCoursesByCategory(request, response)
expect(response.render.mock.calls[0][0]).toBe('courses')
expect(response.render.mock.calls[0][1].title).toBe(`${mockCategory.fields.title} (0)`)
expect(response.render.mock.calls.length).toBe(1)
})
})
describe('Lessons', () => {
test('it should render a lesson', async () => {
request.params = { cslug: 'courseSlug', lslug: 'lessonSlug' }
await getLesson(request, response)
expect(response.render.mock.calls[0][0]).toBe('course')
expect(response.render.mock.calls[0][1].title).toBe('Course title | Lesson title')
expect(response.render.mock.calls[0][1].course.sys.id).toBe(mockCourse.sys.id)
expect(response.render.mock.calls[0][1].lesson.sys.id).toBe(mockCourse.fields.lessons[0].sys.id)
expect(response.render.mock.calls.length).toBe(1)
})
})
describe('Settings', () => {
test('It should render settings', async () => {
await getSettings(request, response)
expect(response.render.mock.calls[0][0]).toBe('settings')
expect(response.render.mock.calls[0][1].title).toBe('Settings')
expect(response.render.mock.calls[0][1].settings).toBe(response.locals.settings)
})
})
describe('i18n', () => {
test('It returns the fallback value when locale file is not found', () => {
expect(translate('defaultTitle', 'not-existing-locale-file')).toBe('The Example App')
})
test('It returns an error when symbol is not found on locale file', () => {
expect(translate('foo', 'en-US')).toBe('Translation not found for foo')
})
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 locale is not found', () => {
expect(translationAvaliable('foo-symbol', 'en-US')).toBe(false)
expect(translationAvaliable('foo-symbol', 'not-existing-locale')).toBe(false)
})
})
const germanLocales = require('../../i18n/locales/de-DE.json')
const englishLocales = require('../../i18n/locales/en-US.json')
describe('locales', () => {
test('all labels coexist in all locale files', async () => {
const germanKeys = Object.keys(germanLocales)
const englishKeys = Object.keys(englishLocales)
expect(germanKeys).toEqual(englishKeys)
})
})
const mockCourse = {
sys: { id: 'courseId' },
fields: {
slug: 'courseSlug',
title: 'Course title',
lessons: [
{ sys: {id: 'lessonId'}, fields: { slug: 'lessonSlug', title: 'Lesson title' } }
]
}
}
const mockCategory = {
sys: {
id: 'categoryId'
},
fields: {
slug: 'categorySlug',
title: 'categoryTitle'
}
}
module.exports = {
mockCourse,
mockCategory
}
NODE_ENV=development
CONTENTFUL_SPACE_ID=qz0n5cdakyl9
CONTENTFUL_DELIVERY_TOKEN=580d5944194846b690dd89b630a1cb98a0eef6a19b860ef71efc37ee8076ddb8
CONTENTFUL_PREVIEW_TOKEN=e8fc39d9661c7468d0285a7ff949f7a23539dd2e686fcb7bd84dc01b392d698b
CONTENTFUL_DELIVERY_API_HOST=cdn.contentful.com
CONTENTFUL_PREVIEW_API_HOST=preview.contentful.com
PORT=3000
extends layout
include mixins/_breadcrumb
include mixins/_editorialFeatures
include mixins/_lesson
block content
.layout-no-sidebar
+breadcrumb
.layout-sidebar
section.layout-sidebar__sidebar
.layout-sidebar__sidebar-header
h2.layout-sidebar__sidebar-title #{translate('tableOfContentsLabel', currentLocale.code)}
.layout-sidebar__sidebar-content
.table-of-contents
.table-of-contents__list
.table-of-contents__item
a.table-of-contents__link(href=`/courses/${course.fields.slug}${queryString}` class=(currentPath.endsWith(course.fields.slug) ? 'active' : '') class=(visitedLessons.includes(course.sys.id) ? 'visited' : '')) #{translate('courseOverviewLabel', currentLocale.code)}
each l in course.fields.lessons
if l.fields
.table-of-contents__item
a.table-of-contents__link(href=`/courses/${course.fields.slug}/lessons/${l.fields.slug}${queryString}` class=(currentPath.endsWith(l.fields.slug) ? 'active' : '') class=(visitedLessons.includes(l.sys.id) ? 'visited' : '')) #{l.fields.title}
section.layout-sidebar__content
if lesson
+lesson(lesson, course, nextLesson)
else
.course
h1.course__title= course.fields.title
+editorialFeatures(course)
.course__overview
h3.course__overview-title #{translate('overviewLabel', currentLocale.code)}
if course.fields.duration
.course__overview-item
svg.course__overview-icon
use(xlink:href='/icons/icons.svg#duration')
.course__overview-value #{translate('durationLabel', currentLocale.code)}: #{course.fields.duration} #{translate('minutesLabel', currentLocale.code)}
if course.fields.skillLevel
.course__overview-item
svg.course__overview-icon
use(xlink:href='/icons/icons.svg#skill-level')
.course__overview-value #{translate('skillLevelLabel', currentLocale.code)}: #{translate(`${course.fields.skillLevel}Label`, currentLocale.code)}
if course.fields.lessons && course.fields.lessons.length
.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)}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment