Make the listing site work on Netlify (#460)

This commit is contained in:
James Nylen 2018-08-11 20:41:11 -05:00 committed by GitHub
parent a65dfb4464
commit 4ee26500e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 1034 additions and 175 deletions

View file

@ -2,4 +2,4 @@ language: node_js
before_script: npm install
script: node bin/validate.js && node node_modules/.bin/mocha
script: npm run validate && npm test

View file

@ -1,12 +1,13 @@
# Remote-friendly companies
A list of semi to fully remote-friendly companies in or around tech. This is the list. There is also a [blog / information website](https://remoteintech.company/).
A list of semi to fully remote-friendly companies in or around tech.
Contributions are very welcome! Please read the [contribution guidelines](/CONTRIBUTING.md) and complete a [Pull Request template](/PULL_REQUEST_TEMPLATE.MD/) for all new submissions.
Contributions are very welcome! Please
[submit a pull request on GitHub](https://github.com/remoteintech/remote-jobs/blob/master/CONTRIBUTING.md).
You can see the format needed in the [example.md](/company-profiles/example.md) file.
_Some company names have a ⚠️️️ icon next to them. This icon means we don't have much information about this company yet and we would love a contribution! See each individual company profile for details._
_Some company names have a ⚠️️️ icon next to them. This icon means we don't have
much information about this company yet and we would love a contribution! See
each individual company profile for details._
## Companies

View file

@ -1,3 +1,6 @@
---
layout: none
---
<!DOCTYPE html>
<html>
<head>
@ -28,10 +31,10 @@ p {
</head>
<body>
<p>
This site (the Netlify site for
<a href="https://github.com/remoteintech/remote-jobs">remoteintech/remote-jobs</a>)
This site is <strong>deprecated</strong>.
<br />
is still under construction. Check back later!
Go here instead:
<a href="https://remoteintech.company/">remoteintech.company</a>
</p>
</body>
</html>

View file

@ -1,5 +0,0 @@
---
layout: default
---
{{ content }}
<script type="text/javascript" src="/remote-jobs/site/assets/companies-table.js"></script>

View file

@ -2,18 +2,236 @@
const fs = require( 'fs' );
const path = require( 'path' );
const util = require( 'util' );
const siteDir = path.join( __dirname, '..', 'site' );
const siteBuildDir = path.join( siteDir, 'build' );
const cheerio = require( 'cheerio' );
const phin = require( 'phin' );
const rimraf = require( 'rimraf' );
const swig = require( 'swig-templates' );
if ( ! fs.existsSync( siteBuildDir ) ) {
fs.mkdirSync( siteBuildDir );
const { parseFromDirectory, headingPropertyNames } = require( '../lib' );
const contentPath = path.join( __dirname, '..' );
const sitePath = path.join( __dirname, '..', 'site' );
const siteBuildPath = path.join( sitePath, 'build' );
// If we are inside the site build path, this is going to cause problems since
// we blow away this directory before regenerating the site
// Error message (in node core): path.js:1086 cwd = process.cwd();
// Error: ENOENT: no such file or directory, uv_cwd
function checkPath( wd ) {
const checkWorkingPath = path.resolve( wd ) + path.sep;
const checkBuildPath = siteBuildPath + path.sep;
if ( checkWorkingPath.substring( 0, checkBuildPath.length ) === checkBuildPath ) {
throw new Error(
"Please change out of the 'site/build' directory before running this script"
);
}
}
checkPath( process.cwd() );
if ( process.env.INIT_CWD ) {
// This script was run via npm; check the original working directory
// because npm barfs in this situation too
checkPath( process.env.INIT_CWD );
}
fs.writeFileSync(
path.join( siteBuildDir, 'index.html' ),
fs.readFileSync(
path.join( siteDir, 'coming-soon.html' ),
'utf8'
)
);
// Parse the content from the Markdown files
console.log( 'Parsing content' );
const data = parseFromDirectory( contentPath );
// Stop if there were any errors
if ( data.errors && data.errors.length > 0 ) {
data.errors.forEach( err => {
err.message.split( '\n' ).forEach( line => {
console.log( '%s: %s', err.filename, line );
} );
} );
process.exit( 1 );
}
// Otherwise, OK to continue building the static site
const assetCacheBuster = Date.now();
// https://github.com/nodejs/node/issues/17871 :(
process.on( 'unhandledRejection', err => {
console.error( 'Unhandled promise rejection:', err );
process.exit( 1 );
} );
/**
* Perform an HTTP request to a URL and return the request body.
*/
async function request( url ) {
console.log(
'Requesting URL "%s"',
url.length > 70
? url.substring( 0, 67 ) + '...'
: url
);
const res = await phin.promisified( url );
if ( res.statusCode !== 200 ) {
throw new Error(
'HTTP response code ' + res.statusCode
+ ' for URL: ' + url
);
}
return res.body.toString();
}
/**
* Write a file to site/build/assets/ (from memory or from an existing file in
* site/assets/) and include a cache buster in the new name. Return the URL to
* the asset file.
*/
function copyAssetToBuild( filename, content = null ) {
const destFilename = filename
.replace( /(\.[^.]+)$/, '-' + assetCacheBuster + '$1' );
const destPath = path.join( siteBuildPath, 'assets', destFilename );
if ( ! content ) {
const srcPath = path.join( sitePath, 'assets', filename );
content = fs.readFileSync( srcPath, 'utf8' );
}
fs.writeFileSync( destPath, content );
return '/assets/' + destFilename;
}
/**
* Write a page's contents to an HTML file.
*/
function writePage( filename, pageContent ) {
console.log( 'Writing page "%s"', filename );
filename = path.join( siteBuildPath, filename );
if ( ! fs.existsSync( path.dirname( filename ) ) ) {
fs.mkdirSync( path.dirname( filename ) );
}
fs.writeFileSync( filename, pageContent );
}
/**
* The main function that prepares the static site.
*/
async function buildSite() {
// Load the HTML from the WP.com blog site
const $ = cheerio.load( await request( 'https://blog.remoteintech.company/' ) );
// Load stylesheets from the WP.com blog site
const wpcomStylesheets = $( 'style, link[rel=stylesheet]' ).map( ( i, el ) => {
const $el = $( el );
const stylesheet = {
id: $el.attr( 'id' ) || null,
media: $el.attr( 'media' ) || null,
};
if ( $el.is( 'style' ) ) {
stylesheet.content = $el.html();
} else {
stylesheet.url = $el.attr( 'href' );
}
return stylesheet;
} ).toArray();
// Fetch the contents of stylesheets included via <link> tags
await Promise.all(
wpcomStylesheets.filter( s => !! s.url ).map( stylesheet => {
return request( stylesheet.url ).then( content => {
stylesheet.content = content;
} );
} )
);
// TODO: Most URLs that appear inside these CSS files are broken because
// they refer to relative URLs against s[012].wp.com
const wpcomStylesheetContent = wpcomStylesheets
.filter( stylesheet => !! stylesheet.content.trim() )
.map( stylesheet => {
const lines = [ '/**' ];
const idString = (
stylesheet.id ? ' (id="' + stylesheet.id + '")' : ''
);
if ( stylesheet.url ) {
lines.push( ' * WP.com external style' + idString );
lines.push( ' * ' + stylesheet.url );
} else {
lines.push( ' * WP.com inline style' + idString );
}
lines.push( ' */' );
if ( stylesheet.media && stylesheet.media !== 'all' ) {
lines.push( '@media ' + stylesheet.media + ' {' );
}
lines.push( stylesheet.content.trim() );
if ( stylesheet.media && stylesheet.media !== 'all' ) {
lines.push( '} /* @media ' + stylesheet.media + ' */' );
}
return lines.join( '\n' );
} ).join( '\n\n' ) + '\n';
// Use the emoji code from WP.com
// Most platforms will display emoji natively, but e.g. Linux does not
let wpcomEmojiScript = null;
$( 'script' ).each( ( i, el ) => {
const scriptContents = $( el ).html();
if ( /\bwindow\._wpemojiSettings\s*=\s*{/.test( scriptContents ) ) {
wpcomEmojiScript = scriptContents;
}
} );
// Set up the site build directory (start fresh each time)
rimraf.sync( siteBuildPath );
fs.mkdirSync( siteBuildPath );
fs.mkdirSync( path.join( siteBuildPath, 'assets' ) );
// Set up styles/scripts to be included on all pages
const stylesheets = [ {
url: copyAssetToBuild( 'wpcom-blog-styles.css', wpcomStylesheetContent ),
}, {
url: '//fonts.googleapis.com/css?family=Source+Sans+Pro:r%7CSource+Sans+Pro:r,i,b,bi&amp;subset=latin,latin-ext,latin,latin-ext',
} ];
const scripts = [];
if ( wpcomEmojiScript ) {
scripts.push( {
url: copyAssetToBuild( 'wpcom-emoji.js', wpcomEmojiScript ),
} );
}
// Set up styles/scripts for specific pages
const indexStylesheets = [ {
url: copyAssetToBuild( 'companies-table.css' ),
} ];
const indexScripts = [ {
url: '//cdnjs.cloudflare.com/ajax/libs/list.js/1.5.0/list.min.js',
}, {
url: copyAssetToBuild( 'companies-table.js' ),
} ];
const profileStylesheets = [ {
url: copyAssetToBuild( 'company-profile.css' ),
} ];
// Generate the index.html file from the main README
// TODO: Build this page and its table dynamically; more filters
const readmeTemplate = swig.compileFile(
path.join( sitePath, 'templates', 'index.html' )
);
writePage( 'index.html', readmeTemplate( {
stylesheets: stylesheets.concat( indexStylesheets ),
scripts: scripts.concat( indexScripts ),
pageContent: data.readmeContent,
} ) );
// Generate the page for each company
const companyTemplate = swig.compileFile(
path.join( sitePath, 'templates', 'company.html' )
);
data.companies.forEach( company => {
const dirname = company.linkedFilename.replace( /\.md$/, '' );
const missingHeadings = Object.keys( headingPropertyNames )
.filter( h => ! company.profileContent[ h ] );
writePage( path.join( dirname, 'index.html' ), companyTemplate( {
stylesheets: stylesheets.concat( profileStylesheets ),
scripts,
company,
headingPropertyNames,
missingHeadings,
} ) );
} );
}
buildSite();

View file

@ -72,6 +72,15 @@ function stripExtraChars( text ) {
exports.stripExtraChars = stripExtraChars;
/**
* Other exports
*/
exports.headingPropertyNames = headingsAll.reduce( ( acc, val ) => {
acc[ toIdentifierCase( val ) ] = val;
return acc;
}, {} );
/**
* The main exported function
*
@ -111,6 +120,8 @@ exports.parseFromDirectory = contentPath => {
$( 'tr' ).each( ( i, tr ) => {
if ( i === 0 ) {
// Assign an ID to the table.
$( tr ).closest( 'table' ).attr( 'id', 'companies-table' );
// Skip the table header row.
return;
}
@ -123,13 +134,20 @@ exports.parseFromDirectory = contentPath => {
);
}
const websiteUrl = $td.eq( 1 ).text();
const websiteText = websiteUrl
.replace( /^https?:\/\//, '' )
.replace( /^www\./, '' )
.replace( /\/$/, '' );
const readmeEntry = {
// Strip out warning emoji indicating that this profile is incomplete
name: $td.eq( 0 ).text().replace( /\u26a0/, '' ).trim(),
// Detect warning emoji next to company name
isIncomplete: /\u26a0/.test( $td.eq( 0 ).text() ),
website: $td.eq( 1 ).text(),
shortRegion: $td.eq( 2 ).text(),
websiteUrl,
websiteText,
shortRegion: $td.eq( 2 ).text().trim(),
};
if ( ! readmeEntry.name ) {
@ -206,9 +224,28 @@ exports.parseFromDirectory = contentPath => {
);
}
// Rewrite company profile link to the correct URL for the static site
if ( $profileLink.length ) {
$profileLink.attr(
'href',
$profileLink.attr( 'href' )
.replace( /^\/company-profiles\//, '/' )
.replace( /\.md$/, '/' )
);
}
// Rewrite external website link (target="_blank" etc, shorter text)
const $websiteLink = $td.eq( 1 ).children().eq( 0 );
$websiteLink
.attr( 'target', '_blank' )
.attr( 'rel', 'noopener noreferrer' )
.text( websiteText );
readmeCompanies.push( readmeEntry );
} );
const readmeContent = $( 'body' ).html();
// Scan the individual Markdown files containing the company profiles.
const allProfileHeadings = {};
@ -444,5 +481,6 @@ exports.parseFromDirectory = contentPath => {
profileFilenames,
profileHeadingCounts,
companies: readmeCompanies,
readmeContent,
};
};

370
package-lock.json generated
View file

@ -5,8 +5,17 @@
"@types/node": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.5.1.tgz",
"integrity": "sha512-SrmAO+NhnsuG/6TychSl2VdxBZiw/d6V+8j+DFo8O3PwFi+QeYXWHhAw+b170aSc6zYab6/PjEWRZHIDN9mNUw==",
"dev": true
"integrity": "sha512-SrmAO+NhnsuG/6TychSl2VdxBZiw/d6V+8j+DFo8O3PwFi+QeYXWHhAw+b170aSc6zYab6/PjEWRZHIDN9mNUw=="
},
"align-text": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
"repeat-string": "^1.5.2"
}
},
"assertion-error": {
"version": "1.1.0",
@ -14,23 +23,25 @@
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
"dev": true
},
"async": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz",
"integrity": "sha1-trvgsGdLnXGXCMo43owjfLUmw9E="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -42,6 +53,20 @@
"integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
"dev": true
},
"camelcase": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz",
"integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk="
},
"center-align": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz",
"integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=",
"requires": {
"align-text": "^0.1.3",
"lazy-cache": "^1.0.3"
}
},
"chai": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz",
@ -66,7 +91,6 @@
"version": "1.0.0-rc.2",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz",
"integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=",
"dev": true,
"requires": {
"css-select": "~1.2.0",
"dom-serializer": "~0.1.0",
@ -76,6 +100,29 @@
"parse5": "^3.0.1"
}
},
"cliui": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz",
"integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=",
"requires": {
"center-align": "^0.1.1",
"right-align": "^0.1.1",
"wordwrap": "0.0.2"
},
"dependencies": {
"wordwrap": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz",
"integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8="
}
}
},
"colors": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz",
"integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=",
"dev": true
},
"commander": {
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz",
@ -85,20 +132,23 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"corser": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
"integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=",
"dev": true
},
"css-select": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz",
"integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=",
"dev": true,
"requires": {
"boolbase": "~1.0.0",
"css-what": "2.1",
@ -109,8 +159,7 @@
"css-what": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
"integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=",
"dev": true
"integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0="
},
"debug": {
"version": "3.1.0",
@ -121,6 +170,11 @@
"ms": "2.0.0"
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"deep-eql": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
@ -140,7 +194,6 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
"integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
"dev": true,
"requires": {
"domelementtype": "~1.1.1",
"entities": "~1.1.1"
@ -149,22 +202,19 @@
"domelementtype": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
"dev": true
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
}
}
},
"domelementtype": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
"integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=",
"dev": true
"integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI="
},
"domhandler": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz",
"integrity": "sha1-iS5HAAqZvlW783dP/qBWHYh5wlk=",
"dev": true,
"requires": {
"domelementtype": "1"
}
@ -173,17 +223,35 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
"integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=",
"dev": true,
"requires": {
"dom-serializer": "0",
"domelementtype": "1"
}
},
"ecstatic": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.2.1.tgz",
"integrity": "sha512-BAdHx9LOCG1fwxY8MIydUBskl8UUQrYeC3WE14FA1DPlBzqoG1aOgEkypcSpmiiel8RAj8gW1s40RrclfrpGUg==",
"dev": true,
"requires": {
"he": "^1.1.1",
"mime": "^1.6.0",
"minimist": "^1.1.0",
"url-join": "^2.0.5"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
}
}
},
"entities": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
"dev": true
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
},
"escape-string-regexp": {
"version": "1.0.5",
@ -191,11 +259,25 @@
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
"dev": true
},
"eventemitter3": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.0.tgz",
"integrity": "sha512-ivIvhpq/Y0uSjcHDcOIccjmYjGLcP09MFGE7ysAwkAvkXfpZlC985pH2/ui64DKazbTW/4kN3yqozUxlXzI6cA==",
"dev": true
},
"follow-redirects": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.2.tgz",
"integrity": "sha512-kssLorP/9acIdpQ2udQVTiCS5LQmdEz9mvdIfDcl1gYX2tPKFADHSyFdvJS040XdFsPzemWtgI3q8mFVCxtX8A==",
"dev": true,
"requires": {
"debug": "^3.1.0"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
"dev": true
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"get-func-name": {
"version": "2.0.0",
@ -207,7 +289,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz",
"integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@ -239,7 +320,6 @@
"version": "3.9.2",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz",
"integrity": "sha1-G9+HrMoPP55T+k/M6w9LTLsAszg=",
"dev": true,
"requires": {
"domelementtype": "^1.3.0",
"domhandler": "^2.3.0",
@ -249,11 +329,37 @@
"readable-stream": "^2.0.2"
}
},
"http-proxy": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.17.0.tgz",
"integrity": "sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g==",
"dev": true,
"requires": {
"eventemitter3": "^3.0.0",
"follow-redirects": "^1.0.0",
"requires-port": "^1.0.0"
}
},
"http-server": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/http-server/-/http-server-0.11.1.tgz",
"integrity": "sha512-6JeGDGoujJLmhjiRGlt8yK8Z9Kl0vnl/dQoQZlc4oeqaUoAKQg94NILLfrY3oWzSyFaQCVNTcKE5PZ3cH8VP9w==",
"dev": true,
"requires": {
"colors": "1.0.3",
"corser": "~2.0.0",
"ecstatic": "^3.0.0",
"http-proxy": "^1.8.1",
"opener": "~1.4.0",
"optimist": "0.6.x",
"portfinder": "^1.0.13",
"union": "~0.4.3"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dev": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@ -262,32 +368,56 @@
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
"dev": true
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"requires": {
"is-buffer": "^1.1.5"
}
},
"lazy-cache": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz",
"integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4="
},
"lodash": {
"version": "4.17.10",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz",
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==",
"dev": true
"integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg=="
},
"longest": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
},
"marked": {
"version": "0.3.12",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz",
"integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==",
"integrity": "sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA=="
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"dev": true
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -295,8 +425,7 @@
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"mkdirp": {
"version": "0.5.1",
@ -336,7 +465,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
"integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=",
"dev": true,
"requires": {
"boolbase": "~1.0.0"
}
@ -345,16 +473,29 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dev": true,
"requires": {
"wrappy": "1"
}
},
"opener": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.4.3.tgz",
"integrity": "sha1-XG2ixdflgx6P+jlklQ+NZnSskLg=",
"dev": true
},
"optimist": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
"requires": {
"minimist": "~0.0.1",
"wordwrap": "~0.0.2"
}
},
"parse5": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz",
"integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==",
"dev": true,
"requires": {
"@types/node": "*"
}
@ -362,8 +503,7 @@
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"dev": true
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
},
"pathval": {
"version": "1.1.0",
@ -371,17 +511,54 @@
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
"dev": true
},
"phin": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/phin/-/phin-2.9.1.tgz",
"integrity": "sha512-aRmHatimRP+73UipPJEK6AWHWjNcwssW6QmOpUcogYVgO8hbSi2Dv/yDWQKs/DmTjK3gCaf6CNsuYcIBWMnlVw=="
},
"portfinder": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.16.tgz",
"integrity": "sha512-icBXCFQxzlK2PMepOM0QeEdPPFSLAaXXeuKOv5AClJlMy1oVCBrkDGJ12IZYesI/BF8mpeVco3vRCmgeBb4+hw==",
"dev": true,
"requires": {
"async": "^1.5.2",
"debug": "^2.2.0",
"mkdirp": "0.5.x"
},
"dependencies": {
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=",
"dev": true
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
}
}
},
"process-nextick-args": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=",
"integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M="
},
"qs": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz",
"integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=",
"dev": true
},
"readable-stream": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz",
"integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@ -392,17 +569,47 @@
"util-deprecate": "~1.0.1"
}
},
"repeat-string": {
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz",
"integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc="
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
"dev": true
},
"right-align": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz",
"integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=",
"requires": {
"align-text": "^0.1.1"
}
},
"rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"requires": {
"glob": "^7.0.5"
}
},
"safe-buffer": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==",
"dev": true
"integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg=="
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w="
},
"string_decoder": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
"dev": true,
"requires": {
"safe-buffer": "~5.1.0"
}
@ -416,23 +623,82 @@
"has-flag": "^3.0.0"
}
},
"swig-templates": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/swig-templates/-/swig-templates-2.0.2.tgz",
"integrity": "sha1-0lAqcwMBk1b06nbqkGXU9Yr2q3U=",
"requires": {
"optimist": "~0.6",
"uglify-js": "2.6.0"
}
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"dev": true
},
"uglify-js": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.6.0.tgz",
"integrity": "sha1-JeqhzDVQ45QQzu+v0c+7a20V8AE=",
"requires": {
"async": "~0.2.6",
"source-map": "~0.5.1",
"uglify-to-browserify": "~1.0.0",
"yargs": "~3.10.0"
}
},
"uglify-to-browserify": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz",
"integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc="
},
"union": {
"version": "0.4.6",
"resolved": "https://registry.npmjs.org/union/-/union-0.4.6.tgz",
"integrity": "sha1-GY+9rrolTniLDvy2MLwR8kopWeA=",
"dev": true,
"requires": {
"qs": "~2.3.3"
}
},
"url-join": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=",
"dev": true
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=",
"dev": true
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
},
"window-size": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz",
"integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0="
},
"wordwrap": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
"integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
"dev": true
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"yargs": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz",
"integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=",
"requires": {
"camelcase": "^1.0.2",
"cliui": "^2.1.0",
"decamelize": "^1.0.0",
"window-size": "0.1.0"
}
}
}
}

View file

@ -1,11 +1,20 @@
{
"devDependencies": {
"chai": "^4.1.2",
"scripts": {
"validate": "bin/validate.js",
"test": "mocha",
"build": "bin/build-site.js",
"serve": "http-server site/build/"
},
"dependencies": {
"cheerio": "^1.0.0-rc.2",
"marked": "^0.3.12",
"mocha": "^5.2.0"
"phin": "^2.9.1",
"rimraf": "^2.6.2",
"swig-templates": "^2.0.2"
},
"scripts": {
"build": "bin/build-site.js"
"devDependencies": {
"chai": "^4.1.2",
"http-server": "^0.11.1",
"mocha": "^5.2.0"
}
}

53
site/README.md Normal file
View file

@ -0,0 +1,53 @@
# Static site generator
This folder contains the template files needed to generate the static site for
this repo ( https://remoteintech.company/ ).
The code that parses the site's data from the Markdown files in this repository
is located in `bin/build-site.js` and `lib/index.js`.
On each new change to `master` or to a GitHub pull request, if there are no
data validation errors, the site is built and deployed to Netlify (the domain
mentioned above for the `master` branch, or a temporary subdomain for pull
requests).
The static site uses a layout and CSS copied from
https://blog.remoteintech.company/ which is a site hosted on WordPress.com, and
the site builder code uses
[`swig`](https://github.com/node-swig/swig-templates)
as an HTML templating engine.
To develop against the site locally, you can run this command:
```sh
npm run build && npm run server
```
If you just want the data structure used to build the site, you can do this:
```js
~/code/remote-jobs $ node
> const { parseFromDirectory } = require( './lib' );
undefined
> const data = parseFromDirectory( '.' );
undefined
> Object.keys( data );
[ 'ok',
'profileFilenames',
'profileHeadingCounts',
'companies',
'readmeContent' ]
> Object.keys( data.companies[ 0 ] )
[ 'name',
'isIncomplete',
'websiteUrl',
'websiteText',
'shortRegion',
'linkedFilename',
'profileContent' ]
...
```
The

View file

@ -1,28 +1,49 @@
.markdown-body table {
display: table !important;
}
table td.company-name {
width: 34%;
}
table td.company-website {
width: 33%;
}
table td.company-region {
width: 33%;
}
#company-filter {
margin: 0 16px;
padding: 8px;
font-family: "Source Sans Pro", sans-serif;
font-size: 15px;
font-weight: normal;
font-size: 16px;
vertical-align: text-top;
vertical-align: middle;
}
#filters-explanation {
font-style: italic;
font-size: 15px;
}
@media screen and (min-width: 50em) {
#filters-explanation {
font-size: 16px;
}
}
table#companies-table.has-filter th {
padding: 0;
}
table#companies-table button.sort {
width: 100%;
border-width: 0;
border-radius: 0;
padding: 7px 3px 4px;
text-align: left;
font-weight: 700;
color: #666;
outline: none;
}
table#companies-table button.sort:hover,
table#companies-table button.sort:focus {
color: #c61610;
}
table#companies-table button.sort:hover {
background: #f4f4f4;
}
/* Sort indicators adapted from http://listjs.com/examples/table/ */
.sort.asc:after,
.sort.desc:after {
table#companies-table .sort.asc:after,
table#companies-table .sort.desc:after {
width: 0;
height: 0;
border-left: 6px solid transparent;
@ -31,13 +52,25 @@ table td.company-region {
display: inline-block;
position: relative;
left: 3px;
top: -1px;
top: -2px;
}
.sort.asc:after {
border-bottom: 6px solid #000;
table#companies-table .sort.asc:after {
border-bottom: 6px solid;
}
.sort.desc:after {
border-top: 6px solid #000;
table#companies-table .sort.desc:after {
border-top: 6px solid;
}
/* Keep table columns the same width after filtering */
table#companies-table td.company-name {
width: 40%;
}
table#companies-table td.company-website {
width: 35%;
}
table#companies-table td.company-region {
width: 25%;
}

View file

@ -1,46 +1,5 @@
function headAppendChild( node ) {
var head = document.head || document.getElementsByTagName( 'head' )[ 0 ];
head.appendChild( node );
}
function loadScript( src, callback ) {
var script = document.createElement( 'script' );
script.src = src;
script.async = false;
if ( typeof callback === 'function' ) {
script.addEventListener( 'load', callback );
}
headAppendChild( script );
}
function loadStylesheet( src ) {
var link = document.createElement( 'link' );
link.type = 'text/css';
link.rel = 'stylesheet';
link.href = src;
headAppendChild( link );
}
function maybeSetupFilters() {
// Get the main site URL from the link at the top of each page
var rootUrl = document.querySelector( 'body > div.markdown-body > h1 > a' ).href;
// If we're not on the main page, no need to do anything
if ( rootUrl !== document.location.href ) {
return;
}
loadStylesheet( '/remote-jobs/site/assets/companies-table.css' );
loadScript(
'https://cdnjs.cloudflare.com/ajax/libs/list.js/1.5.0/list.min.js',
setupFilters
);
}
function setupFilters() {
var table = document.querySelector( 'h2#companies + table' );
var table = document.querySelector( 'table#companies-table' );
var headerCells = table.querySelectorAll( 'thead tr th' );
headerCells[ 0 ].innerHTML =
@ -60,13 +19,6 @@ function setupFilters() {
tds[ 0 ].setAttribute( 'class', 'company-name' );
tds[ 1 ].setAttribute( 'class', 'company-website' );
tds[ 2 ].setAttribute( 'class', 'company-region' );
var websiteUrl = tds[ 1 ].innerText.trim();
tds[ 1 ].innerHTML =
'<a href="' + websiteUrl + '"'
+ '_target="blank" rel="noopener noreferrer">'
+ websiteUrl
+ '</a>';
} );
var filterInput = document.createElement( 'input' );
@ -75,15 +27,19 @@ function setupFilters() {
filterInput.id = 'company-filter';
filterInput.setAttribute( 'class', 'company-filter' );
var companiesAnchorLink = document.querySelector( '#companies .anchorjs-link' );
companiesAnchorLink.parentNode.insertBefore( filterInput, companiesAnchorLink );
companiesAnchorLink.parentNode.removeChild( companiesAnchorLink );
var companiesHeading = document.querySelector( 'h2#companies' );
companiesHeading.appendChild( filterInput );
document.querySelector( 'body > div.markdown-body' )
.setAttribute( 'id', 'company-list' );
var filtersExplanation = document.createElement( 'p' );
filtersExplanation.id = 'filters-explanation';
filtersExplanation.innerHTML = (
'Use the text box above to filter the list of companies, '
+ 'or click a column heading to sort by that column.'
);
table.parentNode.insertBefore( filtersExplanation, table );
window.tableFilter = new List(
'company-list',
'main', // element ID that contains everything
{
valueNames: [
'company-name',
@ -93,6 +49,10 @@ function setupFilters() {
searchClass: 'company-filter',
}
);
table.setAttribute( 'class', 'has-filter' );
}
maybeSetupFilters();
document.addEventListener( 'DOMContentLoaded', function( event ) {
setupFilters();
} );

View file

@ -0,0 +1,9 @@
h1.company-name {
margin-bottom: 8px;
}
.section-atAGlance {
font-size: 15px;
color: #999;
font-size: 85%;
}

135
site/templates/base.html Normal file
View file

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="en" class="wf-active">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% block pageTitle %}{% endblock %} &#8211; Remote In Tech</title>
{%- for stylesheet in stylesheets %}
<link rel="stylesheet" type="text/css" href="{{ stylesheet.url }}" />
{%- endfor %}
{%- for script in scripts %}
<script type="text/javascript" src="{{ script.url }}"></script>
{%- endfor %}
</head>
<body class="page no-sidebar custom-background">
<div id="page" class="hfeed site">
<a class="skip-link screen-reader-text" href="#content">Skip to content</a>
<header id="masthead" class="site-header" role="banner">
<div class="site-branding">
<p class="site-title">
<a href="/">Remote In Tech</a>
</p>
<p class="site-description">
A list of semi to fully remote-friendly companies in tech
</p>
</div>
<nav id="site-navigation" class="main-navigation" role="navigation">
<div class="menu-primary-container">
<ul id="menu-primary" class="menu">
<li class="{% block listItemClasses %}menu-item{% endblock %}">
<a href="/">The List</a>
</li>
<li class="menu-item">
<a href="https://blog.remoteintech.company/">About</a>
</li>
<li class="menu-item">
<a href="https://blog.remoteintech.company/contact/">Contact</a>
</li>
<li class="menu-item">
<a href="https://blog.remoteintech.company/posts/">Blog</a>
</li>
</ul>
</div>
</nav><!-- #site-navigation -->
</header><!-- #masthead -->
<div id="content" class="site-content">
<div id="primary" class="content-area">
<main id="main" class="site-main" role="main">
<article class="page hentry">
<div class="entry-content">
{% block pageContent %}{% endblock %}
</div><!-- .entry-content -->
<footer class="entry-footer">
</footer><!-- .entry-footer -->
</article><!-- .page -->
</main><!-- #main -->
</div><!-- #primary -->
</div><!-- #content -->
<footer id="colophon" class="site-footer" role="contentinfo">
<div class="site-info-wrapper clear">
<nav
class="jetpack-social-navigation jetpack-social-navigation-svg"
role="navigation"
aria-label="Social Links Menu"
>
<div class="menu-social-links-container">
<ul id="menu-social-links" class="menu">
<li class="menu-item">
<a href="https://twitter.com/RemoteInTechCo">
<span class="screen-reader-text">
RemoteInTechCo Twitter
</span>
<svg
class="icon icon-twitter"
aria-hidden="true"
role="img"
>
<use
href="#icon-twitter"
xlink:href="#icon-twitter"
></use>
</svg>
</a>
</li>
<li class="menu-item">
<a href="https://github.com/remoteintech/remote-jobs">
<span class="screen-reader-text">
GitHub Repo
</span>
<svg
class="icon icon-github"
aria-hidden="true"
role="img"
>
<use
href="#icon-github"
xlink:href="#icon-github"
></use>
</svg>
</a>
</li>
</ul>
</div><!-- .menu-social-links-container -->
</nav><!-- .jetpack-social-navigation -->
<div class="site-info">
Powered by
<a href="https://github.com/remoteintech/remote-jobs">
GitHub
</a>
and
<a href="https://www.netlify.com/">Netlify</a>
</div><!-- .site-info -->
</div><!-- .site-info-wrapper -->
</footer><!-- #colophon -->
</div><!-- #page -->
<svg
style="position: absolute; width: 0; height: 0; overflow: hidden;"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<symbol id="icon-github" viewBox="0 0 24 24">
<path d="M12,2C6.477,2,2,6.477,2,12c0,4.419,2.865,8.166,6.839,9.489c0.5,0.09,0.682-0.218,0.682-0.484 c0-0.236-0.009-0.866-0.014-1.699c-2.782,0.602-3.369-1.34-3.369-1.34c-0.455-1.157-1.11-1.465-1.11-1.465 c-0.909-0.62,0.069-0.608,0.069-0.608c1.004,0.071,1.532,1.03,1.532,1.03c0.891,1.529,2.341,1.089,2.91,0.833
c0.091-0.647,0.349-1.086,0.635-1.337c-2.22-0.251-4.555-1.111-4.555-4.943c0-1.091,0.39-1.984,1.03-2.682 C6.546,8.54,6.202,7.524,6.746,6.148c0,0,0.84-0.269,2.75,1.025C10.295,6.95,11.15,6.84,12,6.836 c0.85,0.004,1.705,0.114,2.504,0.336c1.909-1.294,2.748-1.025,2.748-1.025c0.546,1.376,0.202,2.394,0.1,2.646 c0.64,0.699,1.026,1.591,1.026,2.682c0,3.841-2.337,4.687-4.565,4.935c0.359,0.307,0.679,0.917,0.679,1.852 c0,1.335-0.012,2.415-0.012,2.741c0,0.269,0.18,0.579,0.688,0.481C19.138,20.161,22,16.416,22,12C22,6.477,17.523,2,12,2z"/>
</symbol>
<symbol id="icon-twitter" viewBox="0 0 24 24">
<path d="M22.23,5.924c-0.736,0.326-1.527,0.547-2.357,0.646c0.847-0.508,1.498-1.312,1.804-2.27 c-0.793,0.47-1.671,0.812-2.606,0.996C18.324,4.498,17.257,4,16.077,4c-2.266,0-4.103,1.837-4.103,4.103 c0,0.322,0.036,0.635,0.106,0.935C8.67,8.867,5.647,7.234,3.623,4.751C3.27,5.357,3.067,6.062,3.067,6.814 c0,1.424,0.724,2.679,1.825,3.415c-0.673-0.021-1.305-0.206-1.859-0.513c0,0.017,0,0.034,0,0.052c0,1.988,1.414,3.647,3.292,4.023 c-0.344,0.094-0.707,0.144-1.081,0.144c-0.264,0-0.521-0.026-0.772-0.074c0.522,1.63,2.038,2.816,3.833,2.85 c-1.404,1.1-3.174,1.756-5.096,1.756c-0.331,0-0.658-0.019-0.979-0.057c1.816,1.164,3.973,1.843,6.29,1.843 c7.547,0,11.675-6.252,11.675-11.675c0-0.178-0.004-0.355-0.012-0.531C20.985,7.47,21.68,6.747,22.23,5.924z"/>
</symbol>
</defs>
</svg>
</body>
</html>

View file

@ -0,0 +1,58 @@
{% extends 'base.html' %}
{% block pageTitle %}{{ company.name }}{% endblock %}
{% block pageContent %}
<h1 class="company-name">{{ company.name }}</h1>
<div class="section section-atAGlance">
<a
href="{{ company.websiteUrl }}"
target="_blank"
rel="noopener noreferrer"
>{{ company.websiteText }}</a>
{% if company.shortRegion %}
&bull; {{ company.shortRegion }}
{% endif %}
</div>
{% for headingProperty, headingText in headingPropertyNames %}
{% if company.profileContent[ headingProperty ] %}
<div class="section section-{{ headingProperty }}">
<h2>{{ headingText }}</h2>
{{ company.profileContent[ headingProperty ]|safe }}
</div>
{% endif %}
{% endfor %}
{# There are different "levels" of incomplete - if company.isIncomplete is
# true then this profile is "really" incomplete, with a warning icon and
# everything, so there is no point in showing this section.
#}
{% if missingHeadings.length and ! company.isIncomplete %}
<div class="section section-missingInfo">
<h2>Missing info</h2>
<p>
We're missing some information about this company! Here's what
else we need:
</p>
<ul>
{% for headingProperty in missingHeadings %}
<li>{{ headingPropertyNames[ headingProperty ] }}</li>
{% endfor %}
</ul>
<p>
<strong>Want to help?</strong>
<a
href="https://github.com/remoteintech/remote-jobs/edit/master/company-profiles/{{ company.linkedFilename }}"
target="_blank"
rel="noopener noreferrer"
>
Send us a pull request on GitHub
</a>
with your changes to this file!
</p>
</div>
{% endif %}
{% endblock %}

View file

@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block pageTitle %}Remote-friendly companies{% endblock %}
{% block listItemClasses %}menu-item current-menu-item current_page_item{% endblock %}
{% block pageContent %}{{ pageContent|safe }}{% endblock %}

View file

@ -3,6 +3,9 @@
This company table and its linked company profiles contain some companies that
have very little information, marked with a ⚠ symbol.
The website for AngularClass has been changed to test an edge case with company
website links.
## Companies
Name | Website | Region
@ -13,4 +16,4 @@ Name | Website | Region
[18F](/company-profiles/18f.md) | https://18f.gsa.gov/ | USA
[45royale](/company-profiles/45royale.md) ⚠ | http://45royale.com/ |
[Aerolab](/company-profiles/aerolab.md) ⚠ | https://aerolab.co/ |
[AngularClass](/company-profiles/angularclass.md) ⚠ | https://angularclass.com | PST Timezone
[AngularClass](/company-profiles/angularclass.md) ⚠ | http://www.wikihow.com/wikiHow:About-wikiHow | PST Timezone

View file

@ -0,0 +1,52 @@
<h1 id="test-data">Test data</h1>
<p>This company table and its linked company profiles contain some companies that
have very little information, marked with a &#x26A0; symbol.</p>
<p>The website for AngularClass has been changed to test an edge case with company
website links.</p>
<h2 id="companies">Companies</h2>
<table id="companies-table">
<thead>
<tr>
<th>Name</th>
<th>Website</th>
<th>Region</th>
</tr>
</thead>
<tbody>
<tr>
<td><a href="/and-yet/">&amp;yet</a></td>
<td><a href="https://andyet.com" target="_blank" rel="noopener noreferrer">andyet.com</a></td>
<td>Worldwide</td>
</tr>
<tr>
<td><a href="/10up/">10up</a></td>
<td><a href="https://10up.com/" target="_blank" rel="noopener noreferrer">10up.com</a></td>
<td>Worldwide</td>
</tr>
<tr>
<td><a href="/17hats/">17hats</a></td>
<td><a href="https://www.17hats.com/" target="_blank" rel="noopener noreferrer">17hats.com</a></td>
<td>Worldwide</td>
</tr>
<tr>
<td><a href="/18f/">18F</a></td>
<td><a href="https://18f.gsa.gov/" target="_blank" rel="noopener noreferrer">18f.gsa.gov</a></td>
<td>USA</td>
</tr>
<tr>
<td><a href="/45royale/">45royale</a> &#x26A0;</td>
<td><a href="http://45royale.com/" target="_blank" rel="noopener noreferrer">45royale.com</a></td>
<td></td>
</tr>
<tr>
<td><a href="/aerolab/">Aerolab</a> &#x26A0;</td>
<td><a href="https://aerolab.co/" target="_blank" rel="noopener noreferrer">aerolab.co</a></td>
<td></td>
</tr>
<tr>
<td><a href="/angularclass/">AngularClass</a> &#x26A0;</td>
<td><a href="http://www.wikihow.com/wikiHow:About-wikiHow" target="_blank" rel="noopener noreferrer">wikihow.com/wikiHow:About-wikiHow</a></td>
<td>PST Timezone</td>
</tr>
</tbody>
</table>

View file

@ -13,7 +13,7 @@ describe( 'content parsing and metadata', () => {
'fixtures',
'valid-incomplete',
'parsed-content',
profile + '.' + section + '.html'
profile + ( section ? '.' + section : '' ) + '.html'
);
}
@ -36,9 +36,13 @@ describe( 'content parsing and metadata', () => {
}
const result = parseFixtures( 'valid-incomplete' );
expect( Object.keys( result ) ).to.eql(
[ 'ok', 'profileFilenames', 'profileHeadingCounts', 'companies' ]
);
expect( Object.keys( result ) ).to.eql( [
'ok',
'profileFilenames',
'profileHeadingCounts',
'companies',
'readmeContent',
] );
expect( result ).to.deep.include( {
ok: true,
profileFilenames: [
@ -71,13 +75,22 @@ describe( 'content parsing and metadata', () => {
);
} );
} );
fs.writeFileSync(
getContentFilename( 'readme' ),
result.readmeContent
);
}
expect( result.readmeContent ).to.eql(
readContentFile( 'readme' ) + '\n'
);
expect( result.companies ).to.eql( [
{
name: '&yet',
isIncomplete: false,
website: 'https://andyet.com',
websiteUrl: 'https://andyet.com',
websiteText: 'andyet.com',
shortRegion: 'Worldwide',
linkedFilename: 'and-yet.md',
profileContent: getContent( 'and-yet', [
@ -92,7 +105,8 @@ describe( 'content parsing and metadata', () => {
}, {
name: '10up',
isIncomplete: false,
website: 'https://10up.com/',
websiteUrl: 'https://10up.com/',
websiteText: '10up.com',
shortRegion: 'Worldwide',
linkedFilename: '10up.md',
profileContent: getContent( '10up', [
@ -106,7 +120,8 @@ describe( 'content parsing and metadata', () => {
}, {
name: '17hats',
isIncomplete: false,
website: 'https://www.17hats.com/',
websiteUrl: 'https://www.17hats.com/',
websiteText: '17hats.com',
shortRegion: 'Worldwide',
linkedFilename: '17hats.md',
profileContent: getContent( '17hats', [
@ -119,7 +134,8 @@ describe( 'content parsing and metadata', () => {
}, {
name: '18F',
isIncomplete: false,
website: 'https://18f.gsa.gov/',
websiteUrl: 'https://18f.gsa.gov/',
websiteText: '18f.gsa.gov',
shortRegion: 'USA',
linkedFilename: '18f.md',
profileContent: getContent( '18f', [
@ -132,7 +148,8 @@ describe( 'content parsing and metadata', () => {
}, {
name: '45royale',
isIncomplete: true,
website: 'http://45royale.com/',
websiteUrl: 'http://45royale.com/',
websiteText: '45royale.com',
shortRegion: "",
linkedFilename: '45royale.md',
profileContent: getContent( '45royale', [
@ -141,7 +158,8 @@ describe( 'content parsing and metadata', () => {
}, {
name: 'Aerolab',
isIncomplete: true,
website: 'https://aerolab.co/',
websiteUrl: 'https://aerolab.co/',
websiteText: 'aerolab.co',
shortRegion: "",
linkedFilename: 'aerolab.md',
profileContent: getContent( 'aerolab', [
@ -150,7 +168,8 @@ describe( 'content parsing and metadata', () => {
}, {
name: 'AngularClass',
isIncomplete: true,
website: 'https://angularclass.com',
websiteUrl: 'http://www.wikihow.com/wikiHow:About-wikiHow',
websiteText: 'wikihow.com/wikiHow:About-wikiHow',
shortRegion: 'PST Timezone',
linkedFilename: 'angularclass.md',
profileContent: getContent( 'angularclass', [