Latest news + banner, cypress tests, open source copy, general style tweaks (#13)

* Adding latest news and banners

* Adding first draft of cypress specs and github actions workflow

* Adding sticky position for top banner

* Tweak styles for new latest news section

* Tweak styles for text__layout innerHTML

* Fix Cypress homepage test spec

* Fix mobile navi z-index with sticky top banner

* Fix sticky banner z-index bug with mobile navi

* Refactor markdown tools to support pages beyond developer docs

* Adjust TADHACK text max-widths for small mobile

* initial changes for open source copy

* more copy

* more copy

* updated open source structure

* minor

* typo

* more copy

* Adjust styles for Open Source markdown small text

* Update readme and remove floats from docs webhooks markdown

* Add readme notes on Cypress and flesh out navi spec tests

* Fix main navi highlight when on sub-sections of markdown pages

Co-authored-by: Dave Horton <daveh@beachdognet.com>
This commit is contained in:
Brandon Lee Kitajchuk
2021-07-22 09:34:01 -07:00
committed by GitHub
parent ac33cdaac1
commit db94b17829
79 changed files with 1737 additions and 217 deletions

View File

@@ -1,4 +1,5 @@
**/node_modules/*
**/out/*
**/.next/*
next.config.js
next.config.js
**/cypress/*

25
.github/workflows/main.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: github
on:
push:
branches:
- 'main'
pull_request:
branches:
- '**'
workflow_dispatch:
jobs:
test:
environment: Production
# Available tools on this machine:
# https://github.com/actions/virtual-environments/blob/main/images/linux/Ubuntu2004-README.md
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup and Install
run: yarn install
- name: Build and Start Next.js
run: yarn build && (yarn start&) > /dev/null
- name: Run Tests
run: yarn test

9
.gitignore vendored
View File

@@ -35,4 +35,11 @@ examples
packages
# jambonz...
/.notes
/.notes
# cypress
/cypress/*
!/cypress/integration
!/cypress/plugins
!/cypress/support
!/cypress/scripts

View File

@@ -1,2 +0,0 @@
.next
node_modules

View File

@@ -3,6 +3,8 @@ jambonz
> The "bring your own everything" CPaaS
[![CI](https://github.com/jambonz/next-static-site/actions/workflows/main.yml/badge.svg)](https://github.com/jambonz/next-static-site/actions/workflows/main.yml)
![](/public/jambonz.png)
## Stack
@@ -36,9 +38,26 @@ Clone this repository and install [yarn](https://yarnpkg.com/getting-started/ins
- `yarn build && yarn start`
- Create an optimized Next.js production build and serve it locally
- `yarn build && yarn export`
- Create a static production build for any static deploy target
- Create a static production export for any static deploy target
Other packages being used prominently in this apps source code are [classnames](https://www.npmjs.com/package/classnames) and [nanoid](https://www.npmjs.com/package/nanoid#react).
### Testing
*Note cypress test suite is still a work in progress*
You can run e2e tests for the site using [Cypress](https://docs.cypress.io). Cypress specs rely on running the Next.js site on port `3000` as the baseUrl so the best way to test locally is to `yarn dev` in one shell and then `yarn test` in another shell. Optionally, you can `yarn build && yarn start` to create an optimized production server locally and in another shell run `yarn test`. The GitHub workflow for this repository runs the Cypress tests by building and then starting Next.js in the background like `yarn build && (yarn start&) > /dev/null` and then `yarn test`.
Cypress specs are located at `cypress/integration/...`. The source of truth static YAML data should always be used when authoring Cypress tests so we've implemented a script that generates `JSON` data fixtures for Cypress from the YAML data before tests are run. When running `yarn test` what happens is:
* A `pretest` script runs and generates the JSON fixtures for Cypress
* The Cypress tests are run in headless mode
* A `posttest` script runs and performs cleanup on the Cypress fixtures
### Packages
Packages being used prominently in this apps source code are:
* [classnames](https://www.npmjs.com/package/classnames)
* [nanoid](https://www.npmjs.com/package/nanoid#react)
## Jambonz UI library
@@ -54,8 +73,8 @@ You should always use the reusable components from the `jambonz-ui` component li
We are using static data with [yamljs](https://www.npmjs.com/package/yamljs) and [Next.js static props](https://nextjs.org/docs/basic-features/data-fetching#getstaticprops-static-generation). Data files are located in the `data` directory. There's a JS data utility in `lib/data.js` that provides a method for "fetching" the static data for use with a Next.js pages async `getStaticProps` SSR method.
## Jambonz developer docs
## Jambonz markdown data
The project is generating developer docs from markdown files using static file JS utilities alongside Next.js static paths/props system. We are leveraging their [catch-all](https://nextjs.org/docs/routing/dynamic-routes#optional-catch-all-routes) dynamic routes logic located at `pages/docs/[[...slug]].js`. The markdown files are in the `docs` directory. The docs structure is controlled in the docs page YAML data located in `data/docs.yaml`. You can create docs markdown files at will in the `docs` directory but they will not render in the sidebar nav until they are also added to the nav structure in this file.
The project is generating some dynamic layouts with markdown files using static file JS utilities alongside Next.js static paths/props system. We are leveraging their [catch-all](https://nextjs.org/docs/routing/dynamic-routes#optional-catch-all-routes) dynamic routes logic. Example located at `pages/docs/[[...slug]].js`. The markdown files are in the `markdown` directory organized by subfolders. The markdown navigation structure is controlled in the relevant page YAML data located in the `data` directory for each `markdown` subfolder. You can create markdown files at will but they will not render in the sidebar nav until they are also added to the nav structure in the relevant `data` file. For example, the markdown files for the developer docs are located at `markdown/docs/...` and the YAML data for this layout is located at `data/docs.yml`.
We are using [remark](https://github.com/remarkjs/remark), [remark-html](https://github.com/remarkjs/remark-html) and [remark-gfm](https://github.com/remarkjs/remark-gfm) as well as [gray-matter](https://github.com/jonschlinkert/gray-matter) for parsing the docs markdown files. Code syntax highlighting is done with [prismjs](https://prismjs.com) and the associative babel config is in the `.babelrc` file. It's important to leave the preset in this file that merges our config with `next/babel` so Next.js works properly.

View File

@@ -49,7 +49,7 @@ export function useMatchMedia(mediaQuery = null) {
// Normalize for our mobile media query
export function useMobileMedia() {
return useMatchMedia('(max-width: 768px)');
return useMatchMedia('(max-width: 896px)');
}
export function H1({ children }) {
@@ -92,6 +92,33 @@ export function MXS({ children }) {
return <div className="mxs">{children}</div>;
}
export function Latest({ data }) {
const classes = {
'latest': true,
[`latest--${data.label}`]: true,
'pad': true,
'bg-pink': true,
};
return (
<section className={classNames(classes)}>
<div className="wrap latest__wrap">
<div className="latest__headline">
<H2>{data.headline}</H2>
</div>
<div className="latest__subtext">
<H5>
{/* Use dangerouslySetInnerHTML to render inline links from YAML data */}
{normalizeSubtext(data.subtext).map((subtext) => {
return <div key={nanoid()} dangerouslySetInnerHTML={{ __html: subtext }} />;
})}
</H5>
</div>
</div>
</section>
);
}
export function Hero({ data, subStyle }) {
const classes = {
'hero': true,
@@ -177,7 +204,7 @@ export function Icon({ name, mainStyle = 'inline', subStyle = null, ...props })
export function TextLayout({ data }) {
return (
<div className="text__layout">
<div dangerouslySetInnerHTML={{ __html: data.contentHtml }} />
<div className="text__layout__wrap" dangerouslySetInnerHTML={{ __html: data.contentHtml }} />
</div>
);
}

View File

@@ -1,8 +1,17 @@
import Head from 'next/head';
import Link from 'next/link';
import Navi from './navi';
import Footer from './footer';
function Banner({ data }) {
return (
<Link href={data.link}>
<a target="_blank" className="banner">{data.text}</a>
</Link>
);
}
export default function Layout({ children, siteData, title = 'jambonz' }) {
return (
<>
@@ -23,6 +32,7 @@ export default function Layout({ children, siteData, title = 'jambonz' }) {
*/}
<link rel="manifest" href="/manifest.json" />
</Head>
{siteData.banner && siteData.banner.active && <Banner data={siteData.banner} />}
<Navi siteData={siteData} />
<main className="main">
{children}

91
components/markdown.js Normal file
View File

@@ -0,0 +1,91 @@
import { useState } from 'react';
import { nanoid } from 'nanoid';
import classNames from 'classnames';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { Icon, TextLayout } from './jambonz-ui';
function MarkdownSidebar({scope, data}) {
const router = useRouter();
const regex = new RegExp(`^/${scope}/|^/+|/+$`, 'g');
const parsedTab = router.asPath.replace(regex, '').split('/').shift();
const parsedPath = router.asPath.replace(/^\/+|\/+$/g, '').split('/').pop();
const [active, setActive] = useState({
[parsedTab]: true,
});
const handleToggle = (slug) => {
setActive((oldActive) => {
const newActive = {};
for (let i in oldActive) {
newActive[i] = oldActive[i];
}
newActive[slug] = newActive[slug] ? false : true;
return newActive;
});
};
return (
<nav className="markdown__navi">
<div className="markdown__link">
<Link href={data.root.link}>
<a className="m">
<strong>{data.root.label}</strong>
</a>
</Link>
</div>
<ul className="markdown__list">
{data.navi.map((item) => {
const isActiveToggle = (active[item.path] ? true : false);
const subClasses = {
'markdown__sublist': true,
'active': isActiveToggle,
};
return (
<li key={nanoid()} className="markdown__item">
<div className="m markdown__label" onClick={() => handleToggle(item.path)}>
{isActiveToggle ? <Icon name="ChevronDown" /> : <Icon name="ChevronRight" />}
<strong>{item.title}</strong>
</div>
<ul className={classNames(subClasses)}>
{item.pages.map((page) => {
const isActiveItem = (parsedPath === page.path && parsedTab === item.path) && isActiveToggle;
const itemClasses = {
'ms': true,
'active': isActiveItem,
};
return (
<li key={nanoid()} className="markdown__subitem">
<Link href={`/${scope}/${item.path}/${page.path}`}>
<a className={classNames(itemClasses)}>{page.title}</a>
</Link>
</li>
);
})}
</ul>
</li>
);
})}
</ul>
</nav>
);
}
export default function Markdown({scope, data, docs}) {
return (
<div className="markdown">
<div className="wrap markdown__wrap">
<MarkdownSidebar scope={scope} data={data} />
<TextLayout data={docs} />
</div>
</div>
);
}

View File

@@ -11,10 +11,10 @@ function NaviItem({obj}) {
const router = useRouter();
const rSlash = /^\/|\/$/g;
const cleanLink = obj.link.replace(rSlash, '');
const cleanPath = router.asPath.replace(rSlash, '');
const cleanPath = router.asPath.replace(rSlash, '').split('/')[0];
const classes = {
navi__link: true,
active: cleanLink === cleanPath,
active: cleanLink && cleanLink === cleanPath,
};
return (
@@ -71,6 +71,11 @@ function NaviMobile({ active, handler, siteData }) {
export default function Navi({ siteData }) {
const [active, setActive] = useState(false);
const mobile = useMobileMedia();
const classes = {
navi: true,
mobile,
active,
};
const handleNavi = () => {
setActive(!active);
@@ -82,7 +87,7 @@ export default function Navi({ siteData }) {
}
return (
<nav className="navi">
<nav className={classNames(classes)}>
<div className="wrap navi__wrap">
<Link href="/">
<a className="navi__logo">

4
cypress.json Normal file
View File

@@ -0,0 +1,4 @@
{
"video": false,
"baseUrl": "http://localhost:3000"
}

View File

@@ -0,0 +1,37 @@
describe('Footer', () => {
beforeEach(() => {
cy.fixture('site.json').as('site');
});
it('Has support email', () => {
cy.get('@site').then((site) => {
cy.visit('/');
cy.get('.foot__support .btn')
.contains(site.footer.email);
});
});
it('Has page links', () => {
cy.get('@site').then((site) => {
cy.visit('/');
site.navi.links.forEach((item, i) => {
cy.get(`.foot__links:last-child li:nth-child(${i + 2}) .foot__link`)
.contains(item.label)
.should('have.attr', 'href', item.link);
});
});
});
it('Has resource links', () => {
cy.get('@site').then((site) => {
cy.visit('/');
site.footer.links.forEach((item, i) => {
cy.get(`.foot__links:first-child li:nth-child(${i + 1}) .foot__link`)
.contains(item.label)
.should('have.attr', 'href', item.link);
});
});
});
});

View File

@@ -0,0 +1,29 @@
describe('Home page', () => {
beforeEach(() => {
cy.fixture('home.json').as('home');
cy.fixture('site.json').as('site');
});
it('Has latest', () => {
cy.get('@site').then((site) => {
const latest = site.latest.find((item) => item.active);
if (latest) {
cy.visit('/');
cy.get('.latest__headline h2')
.contains(latest.headline);
}
});
});
it('Has banner', () => {
cy.get('@site').then((site) => {
if (site.banner && site.banner.active) {
cy.visit('/');
cy.get('.banner')
.contains(site.banner.text)
.should('have.attr', 'href', site.banner.link);
}
});
});
});

View File

@@ -0,0 +1,87 @@
describe('Navigation', () => {
beforeEach(() => {
cy.fixture('site.json').as('site');
});
it('Has jambonz logo', () => {
cy.get('@site').then((site) => {
cy.visit('/');
cy.get('.navi__logo')
.should('have.attr', 'href', site.navi.home.link);
});
});
it('Has page links', () => {
cy.get('@site').then((site) => {
cy.visit('/');
site.navi.links.forEach((item, i) => {
cy.get(`.navi__links li:nth-child(${i + 1}) .navi__link`)
.contains(item.label)
.should('have.attr', 'href', item.link);
});
});
});
it('Has login button', () => {
cy.get('@site').then((site) => {
cy.visit('/');
cy.get('.navi__login .btn')
.contains(site.navi.login.label)
.should('have.attr', 'href', site.navi.login.link)
.should('have.attr', 'target', '_blank');
});
});
it('Has no mobile navi above max-width', () => {
cy.visit('/');
cy.viewport('macbook-15');
cy.get('.navi__mobile')
.should('have.length', 0);
cy.get('.navi__links')
.should('be.visible');
cy.get('.navi__logo')
.should('be.visible');
cy.get('.navi__icon')
.should('not.be.visible');
});
it('Has mobile navi below max-width', () => {
cy.visit('/');
cy.viewport('ipad-2');
cy.get('.navi')
.should('have.class', 'mobile');
cy.get('.navi__links')
.should('not.be.visible');
cy.get('.navi__mobile')
.should('have.length', 1);
cy.get('.navi__icon')
.should('be.visible')
.click();
cy.get('.navi')
.should('have.class', 'active');
cy.get('.navi__mobile')
.should('have.class', 'active');
cy.get('.navi__mobile__icon')
.click();
cy.get('.navi')
.should('not.have.class', 'active');
cy.get('.navi__mobile')
.should('not.have.class', 'active');
});
});

22
cypress/plugins/index.js Normal file
View File

@@ -0,0 +1,22 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@@ -0,0 +1,22 @@
// Generates fixtures JSON files from jambonz YAML data for Cypress
// Uses `yarn pretest` to run this script
// Uses `yarn posttest` to run fixtures cleanup (rm -rf)
const fs = require('fs');
const path = require('path');
const yamljs = require('yamljs');
const dataDir = path.join(process.cwd(), 'data');
const fixDir = path.join(process.cwd(), 'cypress/fixtures');
if (!fs.existsSync(fixDir)) {
fs.mkdirSync(fixDir);
}
fs.readdirSync(dataDir).forEach((file) => {
const filePath = `${dataDir}/${file}`;
const fileContents = fs.readFileSync(filePath, 'utf8');
const fileJSON = yamljs.parse(fileContents);
const fileOut = path.join(fixDir, file.replace('.yml', '.json'));
fs.writeFileSync(fileOut, JSON.stringify(fileJSON, null, 2));
});

View File

@@ -0,0 +1,25 @@
// ***********************************************
// This example commands.js shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

20
cypress/support/index.js Normal file
View File

@@ -0,0 +1,20 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')

27
data/open-source.yml Normal file
View File

@@ -0,0 +1,27 @@
root:
link: /open-source/
label: Open Source
navi:
-
path: commitment
title: Our commitment
pages:
-
path: oss-we-make
title: Open source we make
-
path: oss-we-use
title: Open source we use
-
path: oss-contributions
title: How we contribute
-
path: your-contributions
title: How you contribute
-
path: install
title: How to install
pages:
-
path: overview
title: Overview

View File

@@ -1,3 +1,25 @@
banner:
active: true
text: jambonz now available on AWS Marketplace
link: https://aws.amazon.com/marketplace/pp/prodview-7lmody7uv2sye
latest:
-
active: true
label: tadhack
headline: jambonz is a proud sponsor of TADHACK 2021 in Orlando!
subtext:
- Sign up <a href="https://jambonz.us/register" target="_blank">here</a> for your free development account,
- and <a href="https://www.telecomsxchange.com/jambonz" target="_blank">visit our partner TelecomXChange</a> for $20 free credit!
-
active: false
label: econnect
headline: jambonz will be at Enterprise Connect 2021
subtext: Ping us at <a href="mailto:sales@jambonz.org" target="_blank">sales@jambonz.org</a> to set up a meeting!
-
active: false
label: aws
headline: jambonz now available on AWS Marketplace
subtext: <a href="https://aws.amazon.com/marketplace/pp/prodview-7lmody7uv2sye" target="_blank">Deploy jambonz</a> with AWS CloudFormation today!
navi:
home:
link: /
@@ -15,6 +37,9 @@ navi:
-
label: Pricing
link: /pricing/
-
label: Open Source
link: /open-source/
footer:
email: support@jambonz.org
links:
@@ -31,4 +56,4 @@ footer:
link: /privacy/
-
label: Terms of Service
link: /terms/
link: /terms/

View File

@@ -6,18 +6,24 @@ const remark = require('remark');
const remarkHtml = require('remark-html');
const remarkGfm = require('remark-gfm');
const dataDir = path.join(process.cwd(), 'data');
const docsDir = path.join(process.cwd(), 'docs');
const markdownDir = path.join(process.cwd(), 'markdown');
/******************************************************************************
* Static page data
*******************************************************************************/
/**
* Parse YAML data to generate the static props for Next.js page components
* @param {string} key The fileName without extension to load from ./data
* @returns {Object}
*/
function _getData(key) {
const fullPath = path.join(dataDir, `${key}.yml`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
return yamljs.parse(fileContents);
}
// Load YAML data to generate the static props for Next.js page components
/**
* Load YAML data to generate the static props for Next.js page components
* @param {string} key The fileName without extension to load from ./data
* @returns {Object}
*/
export function getData(key) {
return {
[key]: _getData(key),
@@ -25,17 +31,24 @@ export function getData(key) {
};
}
/******************************************************************************
* Static developer docs data
*******************************************************************************/
function _getCleanSlug(slug) {
/**
* Get a cleaned slug for Next.js static paths pattern
* @param {string} scope The subdirectory of ./markdown
* @param {string} slug The fileName of a found markdown file
* @returns {Array}
*/
function _getCleanSlug(scope, slug) {
return slug
.replace(docsDir, '')
.replace(path.join(markdownDir, scope), '')
.replace(/^\/+|\/+$/, '')
.split('/');
}
// Load Markdown and YAML front-matter to generate the static props for Next.js docs component
/**
* Load Markdown and YAML front-matter to generate the static props for Next.js docs component
* @param {string} filePath The full path to a markdown file
* @returns {Object}
*/
export async function getParsedMarkdown(filePath) {
const fileContents = fs.readFileSync(filePath, 'utf8');
const fileMatter = matter(fileContents);
@@ -55,8 +68,14 @@ export async function getParsedMarkdown(filePath) {
};
}
// Walk the pages/docs file tree to generate the static paths for Next.js docs pages
function _getDocsPaths(dirPath, arrayOfFiles = [{params:{slug:[]}}]) {
/**
* Walk the markdown file tree to generate the static paths for Next.js components
* @param {string} dirPath The full path to markdown subdirectory
* @param {string} scope The subdirectory of ./markdown
* @param {Array} arrayOfFiles The static paths array formatted for Next.js
* @returns {Array}
*/
function _getMarkdownPaths(dirPath, scope, arrayOfFiles = [{params:{slug:[]}}]) {
const files = fs.readdirSync(dirPath);
files.forEach((file) => {
@@ -70,7 +89,7 @@ function _getDocsPaths(dirPath, arrayOfFiles = [{params:{slug:[]}}]) {
const isIndexFile = fs.existsSync(indexFile);
if (isIndexFile) {
slug = _getCleanSlug(path.join(
slug = _getCleanSlug(scope, path.join(
__dirname,
dirPath,
'/',
@@ -84,10 +103,10 @@ function _getDocsPaths(dirPath, arrayOfFiles = [{params:{slug:[]}}]) {
});
}
arrayOfFiles = _getDocsPaths(filePath, arrayOfFiles);
arrayOfFiles = _getMarkdownPaths(filePath, scope, arrayOfFiles);
} else if (isMarkdown) {
slug = _getCleanSlug(path.join(
slug = _getCleanSlug(scope, path.join(
__dirname,
dirPath,
'/',
@@ -105,13 +124,20 @@ function _getDocsPaths(dirPath, arrayOfFiles = [{params:{slug:[]}}]) {
return arrayOfFiles;
}
// Public method to return static props for Next.js docs component
export async function getDocs(slug) {
/**
* Public method to return static props for Next.js components
* @param {string} scope The subdirectory of ./markdown
* @param {string} slug The markdown fileName
* @returns {Object|null}
*/
export async function getMarkdown(scope, slug) {
let filePath = slug ? path.join(
docsDir,
markdownDir,
scope,
slug.join('/')
) : path.join(
docsDir,
markdownDir,
scope,
'index.md'
);
const isDirectory = (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory());
@@ -119,7 +145,8 @@ export async function getDocs(slug) {
if (isDirectory) {
filePath = path.join(
docsDir,
markdownDir,
scope,
slug.join('/'),
'index.md'
);
@@ -127,7 +154,8 @@ export async function getDocs(slug) {
} else if (!isMarkdown) {
filePath = path.join(
docsDir,
markdownDir,
scope,
`${slug.join('/')}.md`
);
isMarkdown = true;
@@ -140,9 +168,13 @@ export async function getDocs(slug) {
return null;
}
// Public proxy for _getDocsPaths() to return static paths for Next.js docs pages
export function getDocsPaths() {
const paths = _getDocsPaths(docsDir);
/**
* Public proxy for _getMarkdownPaths() to return static paths for Next.js components
* @param {string} scope The subdirectory of ./markdown
* @returns {Array}
*/
export function getMarkdownPaths(scope) {
const paths = _getMarkdownPaths(path.join(markdownDir, scope), scope);
return paths;
}

View File

@@ -44,7 +44,7 @@ Conference status webhooks will contain the following additional parameters:
- members: the current number of members in the conference
- duration: the current length of the conference in seconds
<p>
<a href="/docs/webhooks/overview" style="float: left;">Prev: Overview</a>
<a href="/docs/webhooks/dequeue" style="float: right;">Next: dequeue</a>
<p class="flex">
<a href="/docs/webhooks/overview">Prev: Overview</a>
<a href="/docs/webhooks/dequeue">Next: dequeue</a>
</p>

View File

@@ -27,7 +27,7 @@ The *actionHook* webhook will contain a `dequeueResult` property indicating the
- 'timeout' - no call appeared in the named queue during the timeout interval
- 'error' - a system error of some kind occurred
<p>
<a href="/docs/webhooks/conference" style="float: left;">Prev: conference</a>
<a href="/docs/webhooks/dial" style="float: right;">Next: dial</a>
<p class="flex">
<a href="/docs/webhooks/conference">Prev: conference</a>
<a href="/docs/webhooks/dial">Next: dial</a>
</p>

View File

@@ -119,7 +119,7 @@ The actionHook that is invoked when the dial command ends will include the follo
| dial_sip_status | the sip status of the final response to the INVITE that was sent|
<p>
<a href="/docs/webhooks/dequeue" style="float: left;">Prev: dequeue</a>
<a href="/docs/webhooks/dialogflow" style="float: right;">Next: dialogflow</a>
<p class="flex">
<a href="/docs/webhooks/dequeue">Prev: dequeue</a>
<a href="/docs/webhooks/dialogflow">Next: dialogflow</a>
</p>

View File

@@ -82,7 +82,7 @@ if (evt.event === 'intent') {
Please refer to [this tutorial](/tutorials/#dialogflow-part-2-adding-call-transfer-functionality) for a detailed example.
<p>
<a href="/docs/webhooks/dial" style="float: left;">Prev: dial</a>
<a href="/docs/webhooks/enqueue" style="float: right;">Next: enqueue</a>
<p class="flex">
<a href="/docs/webhooks/dial">Prev: dial</a>
<a href="/docs/webhooks/enqueue">Next: enqueue</a>
</p>

View File

@@ -35,7 +35,7 @@ The *waitHook* webhook will contain the following additional parameters:
- `queueTime`: the current number of seconds the call has spent in queue
- `queueSize`: the current number of calls in the queue
<p>
<a href="/docs/webhooks/dialogflow" style="float: left;">Prev: dialogflow</a>
<a href="/docs/webhooks/gather" style="float: right;">Next: gather</a>
<p class="flex">
<a href="/docs/webhooks/dialogflow">Prev: dialogflow</a>
<a href="/docs/webhooks/gather">Next: gather</a>
</p>

View File

@@ -62,7 +62,7 @@ In the case of digits input, the payload will simple include a `digits` property
Note: the `partialResultCallback` web callback should not return content; any returned content will be discarded.
<p>
<a href="/docs/webhooks/enqueue" style="float: left;">Prev: enqueue</a>
<a href="/docs/webhooks/hangup" style="float: right;">Next: hangup</a>
<p class="flex">
<a href="/docs/webhooks/enqueue">Prev: enqueue</a>
<a href="/docs/webhooks/hangup">Next: hangup</a>
</p>

View File

@@ -16,7 +16,7 @@ You can use the following options in the `hangup` action:
| ------------- |-------------| -----|
| headers | an object containing SIP headers to include in the BYE request | no |
<p>
<a href="/docs/webhooks/gather" style="float: left;">Prev: gather</a>
<a href="/docs/webhooks/leave" style="float: right;">Next: leave</a>
<p class="flex">
<a href="/docs/webhooks/gather">Prev: gather</a>
<a href="/docs/webhooks/leave">Next: leave</a>
</p>

View File

@@ -10,7 +10,7 @@ The `leave` verb transfers a call out of a queue. The call then returns to the
There are no options for the `leave` verb.
<p>
<a href="/docs/webhooks/hangup" style="float: left;">Prev: hangup</a>
<a href="/docs/webhooks/lex" style="float: right;">Next: lex</a>
<p class="flex">
<a href="/docs/webhooks/hangup">Prev: hangup</a>
<a href="/docs/webhooks/lex">Next: lex</a>
</p>

View File

@@ -75,7 +75,7 @@ The *eventHook* webhook will contain two parameters: `event` and `data`. The `e
- `stop-play`: an audio segment returned from Lex or TTS completing playing
- `play-interrupted`: an audio segment was interrupted
<p>
<a href="/docs/webhooks/leave" style="float: left;">Prev: leave</a>
<a href="/docs/webhooks/listen" style="float: right;">Next: listen</a>
<p class="flex">
<a href="/docs/webhooks/leave">Prev: leave</a>
<a href="/docs/webhooks/listen">Next: listen</a>
</p>

View File

@@ -41,7 +41,7 @@ You can use the following options in the `listen` action:
| wsAuth.username | HTTP basic auth username to use on websocket connection | no |
| wsAuth.password | HTTP basic auth password to use on websocket connection | no |
<p>
<a href="/docs/webhooks/lex" style="float: left;">Prev: lex</a>
<a href="/docs/webhooks/pause" style="float: right;">Next: pause</a>
<p class="flex">
<a href="/docs/webhooks/lex">Prev: lex</a>
<a href="/docs/webhooks/pause">Next: pause</a>
</p>

View File

@@ -264,6 +264,7 @@ Alternatively, you can provide an object containing a url and optional method an
```
In the sections that follow, we will describe each of the verbs in detail.
<p>
<a href="/docs/webhooks/conference" style="float: right;">Next: conference</a>
<p class="flex">
<span>&nbsp;</span>
<a href="/docs/webhooks/conference">Next: conference</a>
</p>

View File

@@ -14,7 +14,7 @@ You can use the following options in the `pause` action:
| ------------- |-------------| -----|
| length | number of seconds to wait before continuing the app | yes |
<p>
<a href="/docs/webhooks/listen" style="float: left;">Prev: listen</a>
<a href="/docs/webhooks/play" style="float: right;">Next: play</a>
<p class="flex">
<a href="/docs/webhooks/listen">Prev: listen</a>
<a href="/docs/webhooks/play">Next: play</a>
</p>

View File

@@ -16,7 +16,7 @@ You can use the following options in the `play` action:
| loop | number of times to play the url(s) | no (default: 1) |
| earlyMedia | if true and the call has not yet been answered, play the audio without answering call. Defaults to false | no |
<p>
<a href="/docs/webhooks/pause" style="float: left;">Prev: pause</a>
<a href="/docs/webhooks/redirect" style="float: right;">Next: redirect</a>
<p class="flex">
<a href="/docs/webhooks/pause">Prev: pause</a>
<a href="/docs/webhooks/redirect">Next: redirect</a>
</p>

View File

@@ -15,7 +15,7 @@ You can use the following options in the `redirect` action:
| ------------- |-------------| -----|
| actionHook | URL of webhook to retrieve new application from. | yes |
<p>
<a href="/docs/webhooks/play" style="float: left;">Prev: play</a>
<a href="/docs/webhooks/say" style="float: right;">Next: say</a>
<p class="flex">
<a href="/docs/webhooks/play">Prev: play</a>
<a href="/docs/webhooks/say">Next: say</a>
</p>

View File

@@ -25,7 +25,7 @@ You can use the following options in the `say` action:
| loop | the number of times a text is to be repeated; 0 means repeat forever. Defaults to 1. | no |
| earlyMedia | if true and the call has not yet been answered, play the audio without answering call. Defaults to false | no |
<p>
<a href="/docs/webhooks/redirect" style="float: left;">Prev: redirect</a>
<a href="/docs/webhooks/sip-decline" style="float: right;">Next: sip:decline</a>
<p class="flex">
<a href="/docs/webhooks/redirect">Prev: redirect</a>
<a href="/docs/webhooks/sip-decline">Next: sip:decline</a>
</p>

View File

@@ -25,7 +25,7 @@ You can use the following options in the `sip:decline` action:
| reason | a brief description | no (default: the well-known SIP reasons associated with the specified status code |
| headers | SIP headers to include in the response | no
<p>
<a href="/docs/webhooks/say" style="float: left;">Prev: say</a>
<a href="/docs/webhooks/tag" style="float: right;">Next: tag</a>
<p class="flex">
<a href="/docs/webhooks/say">Prev: say</a>
<a href="/docs/webhooks/tag">Next: tag</a>
</p>

View File

@@ -50,7 +50,7 @@ You can use the following options in the `tag` command:
| ------------- |-------------| -----|
| data | a JSON object containing values to be saved and included in future action or call status notifications (HTTP POST only) for this call | yes |
<p>
<a href="/docs/webhooks/sip-decline" style="float: left;">Prev: sip:decline</a>
<a href="/docs/webhooks/transcribe" style="float: right;">Next: transcribe</a>
<p class="flex">
<a href="/docs/webhooks/sip-decline">Prev: sip:decline</a>
<a href="/docs/webhooks/transcribe">Next: transcribe</a>
</p>

View File

@@ -29,6 +29,7 @@ You can use the following options in the `transcribe` command:
> **Note**: the `dualChannel` property is not currently implemented.
<p>
<a href="/docs/webhooks/tag" style="float: left;">Prev: tag</a>
<p class="flex">
<a href="/docs/webhooks/tag">Prev: tag</a>
<span>&nbsp;</span>
</p>

View File

@@ -0,0 +1,13 @@
# How we contribute
Of course, our main contribution to the open-source ecosystem has been to create the [jambonz](https://jambonz.org) and [drachtio](https://drachtio.org) projects, and to make them freely available under the MIT License. As part of this, we provide an extensive amount of unpaid support by responding to issues on github, email or [our slack channel](https://joinslack.jambonz.org).
However, we are also a [consumer of open-source products](/open-source/commitment/oss-we-use/), and we believe that we have a responsibility to try to find additional ways to support the RTC open-source ecosystem where possible.
To that end, this year we are sponsoring [TADHack Global 2021](https://tadhack.com/2021/), providing both funds for prize money as well as free services for participants. Depending on the covid situation, we hope to travel to Orlando to attend in person.
We have also sponsored other conferences in the past, including the wonderful [CommCon](https://2019.commcon.xyz/) conference in the UK (run by the awesome Dan Jenkins of [Nimble Ape](https://nimblea.pe/)), and have attended and spoken at other open-source conferences, including [SimCon](https://blog.simwood.com/2020/06/simcon4-and-something-new/).
We provided a small amount of funding this year to support the openSIPS security audit via their [gofundme](https://www.gofundme.com/f/opensips-security-audit-penetration-tests) initiative.
And, finally, last but not least, we tried to purchase enough yummy baked goods last year to help save the awesome [Bearkery Bakery](https://bearbakeshop.com/), which was run by one of the best people in the RTC community, [Fred Posner](https://qxork.com/). Ultimately, we were defeated by the pandemic, but we don't regret a moment of the effort (gurgle, burp).

View File

@@ -0,0 +1,26 @@
# Open source that we make
We<sup>*</sup> make [jambonz](https://jambonz.org), a communication platform-as-a-service (CPaaS) designed for use by service providers.
We also make <a href="https://drachtio.org" target="_blank">drachtio</a>, a programmable [SIP]() server. drachtio is one of the main open source components that is used in jambonz.
We release both jambonz and drachtio under the <a href="https://github.com/jambonz/jambonz-feature-server/blob/main/LICENSE" target="_blank">MIT License</a>.
<span class="mxs"><sup>*</sup>We = Drachtio Communications Services, LLC.</span>
<div id="why-mit"></div>
#### Why we chose the MIT License
A few words might be in order on why we chose the MIT License, because we notice that quite often purveyors of "free as in beer" FOSS seem to be pictured by their user base as an austere sect of itinerant, karma-seeking monks who have taken a vow of poverty and wander the open source-scape doing saintly good deeds here and there - like providing free support, or adding any old feature that anyone thinks up, at any time, at no charge.
And sometimes, the fact that software is provided at no cost seems to result in a sense on the part of the consumer (not all mind you, but some) that it must accordingly have little or no value.
First of all, as big consumers of beer, we resent the implication that free beer has no value. That's just wrong.
More seriously, companies choose different open source licenses because they have different business models. We chose the MIT license - arguably the most permissive open-source license - because we are focused on encouraging trials, usage and adoption, and we wanted to remove as many barriers as possible that could stand in the way of that goal.
We believe that jambonz fills a void in the market by delivering a true service provider-focused CPaaS. There is a need for it. But we acknowledge that we are competing in an arena with well-heeled commercial vendors. In this environment, delivering our solution in open-source format - as unrestricted as we could make it - is a purely tactical business decision. We are after a faster pace of play - an increased tempo of create / find / fix / create / repeat. We are playing catch-up (as every startup has to, at the outset) but we are looking to establish a rate of product improvement that closed-source commercial vendors can not match.
The fuel that drives that product improvement tempo is contributions from the user base - unleashed of any licensing concerns or restrictions and with their imaginations fired by their ability to innovate freely on our platform. The MIT license is our way of enabling you to help us. Together, we can win.

View File

@@ -0,0 +1,31 @@
# Open source that we use
We are proud to have built jambonz by standing on the shoulders of some awesome open-source products.
Building an application like jambonz requires curating a selection of the best open source products, and without exception these products have been a delight to work with and are well-supported by talented teams of developers. Please consider supporting them!
| product | used for | license |
| ------------- |-------------| -----|
| <a href="https://drachtio.org" target="_blank">drachtio</a> | application logic and call control | <a href="https://github.com/drachtio/drachtio-srf/blob/master/LICENSE" target="_blank">MIT</a> |
| <a href="https://github.com/sipwise/rtpengine" target="_blank">rtpengine</a> | media proxy and transcoding | <a href="https://www.gnu.org/licenses/quick-guide-gplv3.html" target="_blank">GPL v3.0</a> |
| <a href="https://github.com/signalwire/freeswitch" target="_blank">freeswitch</a> | media server | <a href="https://github.com/signalwire/freeswitch/blob/master/LICENSE" target="_blank">MPL v1.1</a> |
| <a href ="https://github.com/drachtio/drachtio-freeswitch-modules" target="_blank">freeswitch plugins</a> | audio integrations w/ google, AWS, others| <a href="https://github.com/drachtio/drachtio-freeswitch-modules/blob/master/LICENSE" target="_blank">MIT</a> |
| <a href="https://www.apiban.org/" target="_blank">apiban</a> | SBC protection from bad actors |<a href="https://www.gnu.org/licenses/old-licenses/gpl-2.0.html" target="_blank">GPL v2.0</a> |
| <a href="https://expressjs.com/" target="_blank">express</a> | HTTP middleware and web framework |<a href="https://github.com/expressjs/expressjs.com/blob/gh-pages/LICENSE.md" target="_blank">Creative Commons v3.0</a> |
| <a href="https://nodejs.org/" target="_blank">Node.js</a> | Javascript runtime |<a href="https://github.com/nodejs/node/blob/master/LICENSE" target="_blank">MIT</a> |
| <a href="https://libwebsockets.org/" target="_blank">libwebsockets</a> | websockets library |<a href="https://github.com/warmcat/libwebsockets/blob/main/LICENSE" target="_blank">MIT</a> |
| <a href="https://www.mysql.com/" target="_blank">mysql</a> | susbcriber database |<a href=" http://oss.oracle.com/licenses/universal-foss-exception" target="_blank">GPL v2.0 with FOSS Exception</a> |
| <a href="https://github.com/influxdata/telegraf" target="_blank">Telegraf</a> | metrics agent | <a href="https://github.com/influxdata/telegraf/blob/master/LICENSE" target="_blank">MIT</a> |
| <a href="https://github.com/influxdata/influxdb" target="_blank">Influxdb</a> | time series database | <a href="https://github.com/influxdata/influxdb/blob/master/LICENSE" target="_blank">MIT</a> |
| <a href="https://redis.io/" target="_blank">Redis</a> | key-value store | <a href="https://redis.io/topics/license" target="_blank">3-clause BSD</a> |
| <a href="https://github.com/sipcapture" target="_blank">Homer (optional)</a> | SIP capture | <a href="https://github.com/sipcapture/homer/blob/homer7/LICENSE" target="_blank">AGPL v3.0</a><span style="vertical-align: super; color: #da1c5c; font-size: 80%">*</span> |
| <a href="https://www.postgresql.org" target="_blank">Postgresl (optional)</a> | homer database | <a href="https://www.postgresql.org/about/licence/" target="_blank">PostgreSQL License</a> |
| <a href="https://grafana.com/" target="_blank">Grafana (optional)</a> | Monitoring dashboard | <a href="https://github.com/grafana/grafana/blob/main/LICENSE" target="_blank">AGPL v3.0</a> |
<span class="mxs"><sup>*</sup> Note that:</span>
<ol class="mxs">
<li>When using Homer (or Grafana) with the AGPL v3 license, any changes that you make to the jambonz source code <strong>are not</strong> considered a "covered work" by that license, as the two programs are not linked.<br/>TLDR: any changes you make to jambonz source code remain under the more permissive MIT license</li>
<li><a href="https://qxip.net/" target="_blank">QXIP</a>, the creator of Homer and the <a href="https://github.com/sipcapture/HEP" target="_blank">HEP protocol</a>, also offer a non-GPL option (<a href="https://hepic.tel" target="_blank">HEPIC</a>) that is specifically designed for the needs of large-scale telcos and Communications Service Providers. We highly recommend it to those who need a carrier-class monitoring and SIP capture solution.</li>
<li>If, after reading the above, you (or the company you work for) are still scared off by the AGPL v3 license, and are not interested in <a href="https://hepic.tel" target="_blank">HEPIC</a> (have you checked it out?), then know that neither Homer nor Grafana are required components of jambonz: simply don't install them, or remove if already installed.</li>
</ol>

View File

@@ -0,0 +1,27 @@
# How you contribute
The MIT open source license spells out your legal requirements when using the jambonz software, and these, by design, are minimal.
However, since you are using the software we would expect that you have a vested interest in seeing the project grow more healthy, and the capabilities and reliability of the software improve. To that end, please consider ways that you can help make this happen.
Here are some easy (i.e. no cost) ways you can contribute:
- Report bugs (open issues on github, or raise them on our [slack channel](https://joinslack.jambonz.org)), providing as much detail in possible and offering to help recreate and to test fixes.
- Offer to help test new or experimental features
- Have coding skills (Node.js, React, Voip/SIP, or C++)? We'd love your help as a code contributor.
- Evangelize. Talk the project up to your friends and business contacts. Allow us to use your success stories in our marketing materials.
All of these can be really helpful in moving a project forward.
There are also several easy ways you can provide direct financial support to the project:
- <a href="mailto:support@jambonz.org?subject=Hey,%20I'd%20like%20to%20discuss%20a%20project%20with%20you!">Hire us for services</a>: our business model is providing services around jambonz and drachtio, and we are available to assist customers with jambonz deployments as well as custom development of VoIP applications.
- Sign up for a monthly subscription on [jambonz.us](https://jambonz.us) - you get a hosted jambonz account while supporting the project.
- Use our [AWS Marketplace offering](https://aws.amazon.com/marketplace/pp/prodview-7lmody7uv2sye) to build your own jambonz system on AWS.
- [Sponsor us on Github](https://github.com/sponsors/drachtio/).
Finally, we encourage you to think about supporting open-source RTC projects more generally.
In fact, we don't think you should think of supporting open-source in a purely symmetrical fashion - e.g., "I use project A, so I should figure out how to send money to project A".
We'd encourage you to think in a more asymmetric fashion. Supporting project A is great, but if you can't manage that, perhaps you can send some of your staff to an open-source conference this year, or maybe even provide sponsorship for one. Or maybe you can allow project A to use your experience publicly as a success story on their web site. The point is, there are many ways to support the overall ecosystem that you are now part of. All you need to do is care.

View File

@@ -0,0 +1,18 @@
# Our philosophy
We're proud to offer jambonz in open-source format, available at no cost to our users and with very few restrictions. Open source software has driven many of the innovations in the real-time communications (RTC) industry, and we take pride in continuing that effort.
We believe that the use and delivery of open source in RTC should be treated as an ecosystem; that is:
- It is an environment where everyone that participates has responsibilities.
- It is an environment that is impacted -- either positively or negatively -- by everyone that shares it and by every action taken within it.
The health of the RTC open source ecosystem, like any ecosystem, is inherently fragile and should not be taken for granted.
To that end, we believe that all companies in the RTC space that are engaged with open source -- both consumers and makers -- should provide an annual report summarizing their commitment to the open source ecosystem.
As a point of comparison, many companies feel that they have a social mission responsibility and will report on the actions they've taken to fulfill that responsibility apart and aside from their financial results. Our shared responsibility to the stewardship of the open source RTC ecosystem should be treated in the same manner.
On these pages you will find our own statement of commitment as well as instructions on how to install and use the open source.

View File

@@ -0,0 +1 @@
# How to install from open source

View File

@@ -10,6 +10,9 @@
"prebuild": "yarn lint",
"build": "next build",
"export": "next export",
"pretest": "node cypress/scripts/fixtures.js",
"test": "cypress run --headless --browser chrome",
"posttest": "rm -rf cypress/fixtures",
"lint": "eslint components pages lib --ext js"
},
"author": "Jambonz Developers",
@@ -27,6 +30,7 @@
"devDependencies": {
"babel-eslint": "^10.1.0",
"babel-plugin-prismjs": "^2.0.1",
"cypress": "^7.7.0",
"env-cmd": "^10.1.0",
"eslint": "^7.26.0",
"eslint-config-react-app": "^6.0.0",

View File

@@ -1,84 +1,10 @@
import { useEffect, useState } from 'react';
import { nanoid } from 'nanoid';
import classNames from 'classnames';
import { useEffect } from 'react';
import Prism from 'prismjs';
import Link from 'next/link';
import { useRouter } from 'next/router';
import Layout from '../../components/layout';
import { Icon, TextLayout } from '../../components/jambonz-ui';
import { getData, getDocs, getDocsPaths } from '../../lib/data';
function Sidebar({data}) {
const router = useRouter();
const parsedTab = router.asPath.replace(/^\/docs\/|^\/+|\/+$/g, '').split('/').shift();
const parsedPath = router.asPath.replace(/^\/+|\/+$/g, '').split('/').pop();
const [active, setActive] = useState({
[parsedTab]: true,
});
const handleToggle = (slug) => {
setActive((oldActive) => {
const newActive = {};
for (let i in oldActive) {
newActive[i] = oldActive[i];
}
newActive[slug] = newActive[slug] ? false : true;
return newActive;
});
};
return (
<nav className="docs__navi">
<div className="docs__link">
<Link href={data.root.link}>
<a className="m">
<strong>{data.root.label}</strong>
</a>
</Link>
</div>
<ul className="docs__list">
{data.navi.map((item) => {
const isActiveToggle = (active[item.path] ? true : false);
const subClasses = {
'docs__sublist': true,
'active': isActiveToggle,
};
return (
<li key={nanoid()} className="docs__item">
<div className="m docs__label" onClick={() => handleToggle(item.path)}>
{isActiveToggle ? <Icon name="ChevronDown" /> : <Icon name="ChevronRight" />}
<strong>{item.title}</strong>
</div>
<ul className={classNames(subClasses)}>
{item.pages.map((page) => {
const isActiveItem = (parsedPath === page.path && parsedTab === item.path) && isActiveToggle;
const itemClasses = {
'ms': true,
'active': isActiveItem,
};
return (
<li key={nanoid()} className="docs__subitem">
<Link href={`/docs/${item.path}/${page.path}`}>
<a className={classNames(itemClasses)}>{page.title}</a>
</Link>
</li>
);
})}
</ul>
</li>
);
})}
</ul>
</nav>
);
}
import Markdown from '../../components/markdown';
import { getData, getMarkdown, getMarkdownPaths } from '../../lib/data';
export default function Docs({ data, docs }) {
useEffect(() => {
@@ -87,18 +13,13 @@ export default function Docs({ data, docs }) {
return (
<Layout siteData={data.site}>
<div className="docs">
<div className="wrap docs__wrap">
<Sidebar data={data.docs} />
<TextLayout data={docs} />
</div>
</div>
<Markdown scope="docs" data={data.docs} docs={docs} />
</Layout>
);
}
export async function getStaticPaths() {
const paths = getDocsPaths();
const paths = getMarkdownPaths('docs');
return {
paths,
@@ -108,7 +29,7 @@ export async function getStaticPaths() {
export async function getStaticProps({ params }) {
const data = getData('docs');
const docs = await getDocs(params.slug);
const docs = await getMarkdown('docs', params.slug);
return {
props: {

View File

@@ -3,7 +3,7 @@ import classNames from 'classnames';
import { useState, useEffect, useRef } from 'react';
import Layout from '../components/layout';
import { Hero, Icon, Button, H6, H5, H2, P, MS, normalizeSubtext, normalizeSlug, useMobileMedia } from '../components/jambonz-ui';
import { Latest, Hero, Icon, Button, H6, H5, H2, P, MS, normalizeSubtext, normalizeSlug, useMobileMedia } from '../components/jambonz-ui';
import { getData } from '../lib/data';
function Tech({data}) {
@@ -146,8 +146,11 @@ function BYO({data}) {
}
export default function Home({ data }) {
const latest = data.site.latest.find((item) => item.active);
return (
<Layout siteData={data.site}>
{latest && <Latest data={latest} />}
<Hero data={data.home.hero} subStyle="home" />
<Tech data={data.home.tech} />
<Dilemma data={data.home.dilemma} />

View File

@@ -0,0 +1,40 @@
import { useEffect } from 'react';
import Prism from 'prismjs';
import Layout from '../../components/layout';
import Markdown from '../../components/markdown';
import { getData, getMarkdown, getMarkdownPaths } from '../../lib/data';
export default function OpenSource({ data, docs }) {
useEffect(() => {
setTimeout(() => Prism.highlightAll(), 0);
});
return (
<Layout siteData={data.site}>
<Markdown scope="open-source" data={data['open-source']} docs={docs} />
</Layout>
);
}
export async function getStaticPaths() {
const paths = getMarkdownPaths('open-source');
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }) {
const data = getData('open-source');
const docs = await getMarkdown('open-source', params.slug);
return {
props: {
data,
docs,
},
};
}

View File

@@ -34,9 +34,85 @@
/******************************************************************************
* Above the fold
*******************************************************************************/
.banner {
@include flex-cols();
@include ms();
justify-content: center;
width: 100%;
height: 32px;
color: $white;
background: $jambonz;
font-family: $font-medium;
position: sticky;
top: 0;
z-index: $navi-index + 1;
@media (max-width: $width-mobile) {
@include mxs();
}
}
.latest {
text-align: center;
&--tadhack {
.latest__headline {
max-width: 840px;
@media (max-width: $width-tablet-1) {
max-width: 760px;
}
@media (max-width: 400px) {
max-width: 330px;
}
}
.latest__subtext {
max-width: 740px;
@media (max-width: 400px) {
max-width: 300px;
}
div {
@media (max-width: $width-small) {
display: inline;
}
}
div:last-child {
@media (max-width: $width-small) {
padding-left: 6px;
}
}
}
}
&__wrap {
@include flex-cols();
}
&__headline {
color: $jambonz;
}
&__subtext {
margin-top: 16px;
a {
text-decoration: underline;
}
}
}
.hero {
text-align: center;
&--home {
padding-bottom: 64px;
}
&--why {
.hero__subtext {
max-width: 870px;
@@ -80,7 +156,7 @@
}
/******************************************************************************
* Fluid video embeds
* Classes for extra markdown styling
*******************************************************************************/
.video-wrap {
position: relative;
@@ -93,4 +169,9 @@
left: 0;
top: 0;
}
}
.flex {
display: flex;
justify-content: space-between;
}

View File

@@ -1,6 +1,4 @@
$font-mono: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
.docs {
.markdown {
padding-top: 64px;
padding-bottom: 96px;

View File

@@ -3,6 +3,10 @@
position: relative;
z-index: $navi-index;
&.mobile.active {
z-index: $navi-index + 2;
}
// Menu / X icons for mobile nav interactions
.icon {
width: $icon-size-3;
@@ -43,7 +47,7 @@
&__links,
&__login {
@media (max-width: $width-tablet-2) {
.mobile & {
display: none;
}
}
@@ -57,7 +61,7 @@
transform: translateY(-5px);
}
@media (max-width: $width-tablet-2) {
.mobile & {
display: block;
}
}
@@ -134,6 +138,11 @@
&__support {
text-align: center;
padding: 32px 0;
// Force mobile padding size on button
.btn {
padding: 18px 46px;
}
}
}
}

View File

@@ -16,7 +16,11 @@
* v2.0 | 20110126
* License: none (public domain)
*******************************************************************************/
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
/**
* Intentionally removed elements
* sup
*/
html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;

View File

@@ -1,5 +1,3 @@
$font-mono: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
.text__layout {
max-width: $width-tablet-2;
width: 100%;
@@ -9,7 +7,11 @@ $font-mono: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
max-width: 100%;
}
> div {
&__wrap {
> :first-child {
margin-top: 0;
}
h1 {
@include h2();
}
@@ -18,14 +20,22 @@ $font-mono: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
@include h3();
}
sup {
color: $jambonz;
}
> p, > blockquote > p {
@include ms();
}
> blockquote > p {
font-family: $font-regular-italic;
border-left: inset;
padding-left: 10px;
> blockquote {
padding-left: 16px;
> p {
font-family: $font-regular-italic;
border-left: 3px solid $jambonz;
padding-left: 10px;
}
}
> h1, > h2, > h3, > h4, > h5, > h6, > p, > div, > ul, > ol, > table, > blockquote {
@@ -47,11 +57,7 @@ $font-mono: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
}
> ul {
padding-left: 48px;
@media (max-width: $width-tablet-1) {
padding-left: 32px;
}
padding-left: 32px;
li {
list-style-type: disc;
@@ -93,8 +99,4 @@ $font-mono: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
font-size: 13px;
}
}
> div > :first-child {
margin-top: 0;
}
}

View File

@@ -23,6 +23,7 @@ $font-medium: 'objectivitymedium';
$font-medium-italic: 'objectivitymedium_slanted';
$font-bold: 'objectivitybold';
$font-bold-italic: 'objectivitybold_slanted';
$font-mono: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
/******************************************************************************
* Font sizes

View File

@@ -39,6 +39,7 @@
*******************************************************************************/
@import 'layout.scss';
@import 'text-layout.scss';
@import 'markdown.scss';
/******************************************************************************
* Navi
@@ -56,5 +57,4 @@
@import 'pages/home.scss';
@import 'pages/why.scss';
@import 'pages/pricing.scss';
@import 'pages/docs.scss';
@import 'pages/jambonz-ui.scss';

View File

@@ -98,6 +98,7 @@
@media (max-width: $width-tablet-2) {
flex-wrap: wrap;
width: 100%;
}
}

855
yarn.lock

File diff suppressed because it is too large Load Diff