├── .gitattributes ├── generators ├── common │ ├── templates │ │ ├── gitkeep │ │ ├── browserslistrc │ │ └── gitignore │ └── index.js ├── page │ ├── templates │ │ ├── data.json │ │ ├── defaultImage.jpg │ │ ├── gulp │ │ │ ├── tasks │ │ │ │ ├── watchify.js │ │ │ │ ├── browserify.js │ │ │ │ ├── assets.js │ │ │ │ ├── templates.js │ │ │ │ ├── plain-images.js │ │ │ │ ├── scss.js │ │ │ │ ├── server.js │ │ │ │ ├── optimize-images.js │ │ │ │ ├── clear-test.js │ │ │ │ ├── aws-test.js │ │ │ │ ├── jsify.js │ │ │ │ ├── resize-images.js │ │ │ │ └── aws.js │ │ │ └── index.js │ │ ├── styles.scss │ │ ├── buttonLeft.svg │ │ ├── buttonRight.svg │ │ ├── img.html │ │ ├── gulpfile.js │ │ ├── package.json │ │ └── README.md │ ├── config.json │ └── index.js ├── graphic-module │ ├── templates │ │ ├── dist │ │ │ ├── data │ │ │ │ ├── create.json │ │ │ │ └── update.json │ │ │ └── index.html │ │ ├── src │ │ │ ├── scss │ │ │ │ ├── _variables.scss │ │ │ │ ├── _chart-styles.scss │ │ │ │ └── styles.scss │ │ │ └── js │ │ │ │ ├── global-chart.js │ │ │ │ └── chart.js │ │ ├── preview.png │ │ ├── gulp │ │ │ ├── index.js │ │ │ └── tasks │ │ │ │ ├── server.js │ │ │ │ ├── sass.js │ │ │ │ └── browserify.js │ │ ├── gulpfile.js │ │ ├── package.json │ │ └── README │ ├── config.json │ └── index.js ├── embeddable-graphic │ ├── templates │ │ ├── gulp │ │ │ ├── tasks │ │ │ │ ├── watchify.js │ │ │ │ ├── browserify.js │ │ │ │ ├── html.js │ │ │ │ ├── data.js │ │ │ │ ├── server.js │ │ │ │ ├── sass.js │ │ │ │ ├── jsify.js │ │ │ │ └── aws.js │ │ │ └── index.js │ │ ├── gulpfile.js │ │ ├── src │ │ │ ├── sass │ │ │ │ ├── styles.scss │ │ │ │ ├── _ooyala.scss │ │ │ │ └── _base.scss │ │ │ ├── js │ │ │ │ └── scripts.js │ │ │ ├── embed.html │ │ │ └── index.html │ │ ├── package.json │ │ └── README.md │ ├── config.json │ └── index.js ├── nvm │ └── index.js ├── gitsecrets │ └── index.js ├── app │ └── index.js └── linters │ └── index.js ├── .eslintrc.json ├── package.json ├── .gitignore ├── README.md └── CHANGELOG.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /generators/common/templates/gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /generators/page/templates/data.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "root": true 4 | } 5 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/dist/data/create.json: -------------------------------------------------------------------------------- 1 | [20, 40, 60] 2 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/dist/data/update.json: -------------------------------------------------------------------------------- 1 | [40, 30, 20, 30] 2 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/src/scss/_variables.scss: -------------------------------------------------------------------------------- 1 | $<%= objName %>-container: '#chart'; 2 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulp/tasks/watchify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./jsify')(true); 4 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulp/tasks/browserify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./jsify')(false); 4 | -------------------------------------------------------------------------------- /generators/graphic-module/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "verboseName": "Graphic module", 3 | "description": "TK", 4 | "priority": 5 5 | } -------------------------------------------------------------------------------- /generators/graphic-module/templates/src/js/global-chart.js: -------------------------------------------------------------------------------- 1 | import chart from './chart.js'; 2 | 3 | 4 | window.<%= objName %> = chart; 5 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/src/scss/_chart-styles.scss: -------------------------------------------------------------------------------- 1 | #{$<%= objName %>-container} { 2 | 3 | /*Your chart styles here*/ 4 | 5 | } 6 | -------------------------------------------------------------------------------- /generators/common/templates/browserslistrc: -------------------------------------------------------------------------------- 1 | # Browsers that we support 2 | # see https://github.com/ai/browserslist 3 | 4 | > 2% in US 5 | Last 2 versions 6 | -------------------------------------------------------------------------------- /generators/page/templates/defaultImage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DallasMorningNews/generator-dmninteractives/HEAD/generators/page/templates/defaultImage.jpg -------------------------------------------------------------------------------- /generators/graphic-module/templates/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DallasMorningNews/generator-dmninteractives/HEAD/generators/graphic-module/templates/preview.png -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/watchify.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | module.exports = require('./jsify')(true); 8 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/browserify.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | module.exports = require('./jsify')(false); 8 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "verboseName": "Embeddable graphic", 3 | "description": "An embeddable interactive region, to be inserted into stories.", 4 | "priority": 4 5 | } -------------------------------------------------------------------------------- /generators/graphic-module/templates/src/scss/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'variables'; 2 | @import 'chart-styles'; 3 | 4 | #{$<%= objName %>-container} { 5 | border: 1px dashed red; 6 | width: 50%; 7 | min-width: 290px; 8 | height:400px; 9 | } 10 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/gulp/index.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | 3 | module.exports = (tasks) => { 4 | tasks.forEach((name) => { 5 | gulp.task(name, require(`./tasks/${name}`)); // eslint-disable-line global-require 6 | }); 7 | 8 | return gulp; 9 | }; 10 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/assets.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const gulp = require('gulp'); 8 | 9 | 10 | module.exports = () => 11 | gulp.src('./src/data/**/*') 12 | .pipe(gulp.dest('./dist/data')); 13 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulp/tasks/html.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const gulp = require('gulp'); 8 | 9 | 10 | module.exports = () => 11 | gulp.src('./src/*.html') 12 | .pipe(gulp.dest('./dist/')); 13 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulp/tasks/data.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const gulp = require('gulp'); 8 | 9 | 10 | module.exports = () => 11 | gulp.src('./src/data/**/*') 12 | .pipe(gulp.dest('./dist/data/')); 13 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulp/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const gulp = require('gulp'); 4 | 5 | 6 | module.exports = (tasks) => { 7 | tasks.forEach((name) => { 8 | gulp.task(name, require(`./tasks/${name}`)); // eslint-disable-line global-require 9 | }); 10 | 11 | return gulp; 12 | }; 13 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/gulpfile.js: -------------------------------------------------------------------------------- 1 | const util = require('gulp-util'); 2 | 3 | process.env.NODE_ENV = !!util.env.production ? 'production' : 'development'; 4 | 5 | const gulp = require('./gulp')([, 6 | 'sass', 7 | 'browserify', 8 | 'server', 9 | ]); 10 | 11 | gulp.task('build', ['sass', 'browserify', 'server']); 12 | gulp.task('default', ['build']); 13 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const gulp = require('gulp'); 8 | 9 | module.exports = (tasks) => { 10 | tasks.forEach((name) => { 11 | gulp.task(name, require(`./tasks/${name}`)); // eslint-disable-line global-require 12 | }); 13 | 14 | return gulp; 15 | }; 16 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/gulp/tasks/server.js: -------------------------------------------------------------------------------- 1 | const browserSync = require('browser-sync').create(); 2 | const gulp = require('gulp'); 3 | 4 | module.exports = () => { 5 | browserSync.init({ 6 | files: ['./dist/**/*'], 7 | server: { 8 | baseDir: './dist/', 9 | }, 10 | ghostMode: false, 11 | }); 12 | 13 | gulp.watch(['./src/scss/**/*.scss'], ['sass']); 14 | }; 15 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/gulp/tasks/sass.js: -------------------------------------------------------------------------------- 1 | const gulp = require('gulp'); 2 | const sass = require('gulp-sass'); 3 | const sourcemaps = require('gulp-sourcemaps'); 4 | 5 | module.exports = () => 6 | gulp.src('src/scss/*.scss') 7 | .pipe(sourcemaps.init()) 8 | .pipe(sass({ outputStyle: 'compressed' }).on('error', sass.logError)) 9 | .pipe(sourcemaps.write()) 10 | .pipe(gulp.dest('dist/css')); 11 | -------------------------------------------------------------------------------- /generators/page/templates/styles.scss: -------------------------------------------------------------------------------- 1 | /* */ 2 | /* Default style imports */ 3 | /* */ 4 | 5 | @import 'base'; 6 | 7 | 8 | // Should get Hancock to embed these in base, at some point: 9 | 10 | @import 'header'; 11 | @import 'footer'; 12 | 13 | 14 | /* */ 15 | /* Project-specific styles */ 16 | /* */ 17 | 18 | // Enter your custom styles here. -------------------------------------------------------------------------------- /generators/nvm/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mix this into your generator by adding a composeWith('nvm') to your 3 | * generator's initializing() method to add auto-creation of .nvmrc 4 | */ 5 | const semver = require('semver'); 6 | const yeoman = require('yeoman-generator'); 7 | 8 | 9 | module.exports = yeoman.Base.extend({ 10 | writing() { 11 | if (semver.valid(process.version) !== null) { 12 | this.fs.write('.nvmrc', `${semver.major(process.version)}.${semver.minor(process.version)}`); 13 | } 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulp/tasks/server.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browserSync = require('browser-sync').create(); 4 | const gulp = require('gulp'); 5 | 6 | module.exports = () => { 7 | browserSync.init({ 8 | files: ['./dist/**/*'], 9 | server: { 10 | baseDir: './dist/', 11 | index: 'embed.html', 12 | }, 13 | ghostMode: false, 14 | }); 15 | 16 | gulp.watch(['./src/sass/*.scss'], ['sass']); 17 | gulp.watch(['./src/data/**/*'], ['data']); 18 | gulp.watch(['./src/*.html'], ['html']); 19 | }; 20 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const gulp = require('./gulp')([ 4 | 'sass', 5 | 'browserify', 6 | 'watchify', 7 | 'server', 8 | 'aws', 9 | 'html', 10 | 'data', 11 | ]); 12 | const runSequence = require('run-sequence'); 13 | 14 | 15 | gulp.task('build', ['data', 'html', 'sass', 'browserify']); 16 | gulp.task('publish', (cb) => { runSequence('build', 'aws', cb); }); 17 | gulp.task('dev-server', ['watchify', 'server']); 18 | gulp.task('default', (cb) => { runSequence('build', 'dev-server', cb); }); 19 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/src/sass/styles.scss: -------------------------------------------------------------------------------- 1 | @import 'normalize/import-now'; 2 | 3 | @import 'mixins'; 4 | @import 'variables'; 5 | @import 'base'; 6 | 7 | html.embedded, html { 8 | // Applied when embedded using Pym.js 9 | font-size: 10px; 10 | } 11 | 12 | body { 13 | // This helps Pym.js more more accurately measure our 14 | &:before, 15 | &:after { 16 | content: " "; 17 | display: table; 18 | } 19 | } 20 | 21 | 22 | // NOTE!!!! If you need ooyala code, add @import 'ooyala'; to your list of imports above 23 | 24 | // Your styles go here 25 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/src/js/scripts.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import pym from 'pym.js'; 3 | 4 | const pymChild = new pym.Child(); 5 | 6 | // Your graphic code goes here 7 | // 8 | // | 9 | // + +--+ +--+ 10 | // | | +---+ | | 11 | // | | || | | | 12 | // + | || +----+ | 13 | // | | || || || | 14 | // | | || || || | 15 | // + | || || || | 16 | // | | || || || | 17 | // | | || || || | 18 | // +-------------------------+ 19 | 20 | // Call this every time you need to resize the iframe, after your 21 | // graphic is drawn, etc. 22 | pymChild.sendHeight(); 23 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulp/tasks/sass.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const autoprefixer = require('autoprefixer'); 4 | const eyeglass = require('eyeglass'); 5 | const gulp = require('gulp'); 6 | const postcss = require('gulp-postcss'); 7 | const sass = require('gulp-sass'); 8 | const sourcemaps = require('gulp-sourcemaps'); 9 | 10 | 11 | module.exports = () => 12 | gulp.src('src/sass/*.scss') 13 | .pipe(sourcemaps.init()) 14 | .pipe(sass(eyeglass({ outputStyle: 'compressed' })).on('error', sass.logError)) 15 | .pipe(postcss([autoprefixer({ grid: true })])) 16 | .pipe(sourcemaps.write('./')) 17 | .pipe(gulp.dest('dist/css')); 18 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/templates.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const gulp = require('gulp'); 8 | const nunjucksRender = require('gulp-nunjucks-render'); 9 | 10 | 11 | const meta = require('./../../meta.json'); 12 | 13 | 14 | module.exports = () => { 15 | nunjucksRender.nunjucks.configure(['src/templates/'], { watch: false }); 16 | 17 | return gulp.src([ 18 | './src/templates/**/*.html', 19 | '!./src/templates/base.html', 20 | '!./src/templates/partials/**/*.html', 21 | '!./src/templates/adblock*.html', 22 | ]) 23 | .pipe(nunjucksRender(meta)) 24 | .pipe(gulp.dest('./dist')); 25 | }; 26 | -------------------------------------------------------------------------------- /generators/page/templates/buttonLeft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /generators/page/templates/buttonRight.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/plain-images.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const changed = require('gulp-changed'); 8 | const gulp = require('gulp'); 9 | const merge = require('merge-stream'); 10 | 11 | 12 | module.exports = () => { 13 | // Copies over SVGs or other image files 14 | const other = gulp.src([ 15 | './src/images/**/*', 16 | '!./src/images/**/*.{png,jpg,JPG}', 17 | '!./src/images/opt/**/*', 18 | ],{ nodir: true }) 19 | .pipe(changed('./dist/images')) 20 | .pipe(gulp.dest('./dist/images')); 21 | 22 | // Copy imgs that weren't resized 23 | const copied = gulp.src('./src/images/opt/**/_*.{png,jpg,JPG}') 24 | .pipe(changed('./dist/images')) 25 | .pipe(gulp.dest('./dist/images')); 26 | 27 | return merge(other, copied); 28 | }; 29 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/scss.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const autoprefixer = require('autoprefixer'); 8 | const eyeglass = require('eyeglass'); 9 | const gulp = require('gulp'); 10 | const rename = require('gulp-rename'); 11 | const postcss = require('gulp-postcss'); 12 | const sass = require('gulp-sass'); 13 | const sourcemaps = require('gulp-sourcemaps'); 14 | 15 | 16 | module.exports = () => 17 | gulp.src(['./src/scss/**/*.scss', './src/scss/**/*.css']) 18 | .pipe(sourcemaps.init()) 19 | .pipe(sass(eyeglass({ outputStyle: 'compressed' })).on('error', sass.logError)) 20 | .pipe(postcss([autoprefixer({ grid: true })])) 21 | // eslint-disable-next-line no-param-reassign 22 | .pipe(rename((filePath) => { filePath.basename += '-bundle'; })) 23 | .pipe(sourcemaps.write()) 24 | .pipe(gulp.dest('./dist/css')); 25 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/src/embed.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 |
27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "version": "1.0.0", 4 | "description": "", 5 | "author": "", 6 | "main": "src/js/chart.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "license": "UNLICENSED", 11 | "devDependencies": { 12 | "babel-preset-env": "^1.6.1", 13 | "babelify": "^7.3.0", 14 | "browser-sync": "^2.17.5", 15 | "browserify": "^13.1.0", 16 | "event-stream": "<=3.3.4", 17 | "gulp": "^3.9.1", 18 | "gulp-rename": "^1.2.2", 19 | "gulp-sass": "^2.3.2", 20 | "gulp-sourcemaps": "^2.1.1", 21 | "gulp-uglify": "^2.0.0", 22 | "gulp-util": "^3.0.7", 23 | "vinyl-buffer": "^1.0.0", 24 | "vinyl-source-stream": "^1.1.0", 25 | "watchify": "^3.7.0" 26 | }, 27 | "dependencies": { 28 | "d3": "^4.2.8" 29 | }, 30 | "browserify": { 31 | "transform": [["babelify", { "presets": ["es2015"] }]] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | <% _.each(fonts, function( font ) { %> 7 | 8 | <% }); %> 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Embed title

16 |

Chatter text

17 |
18 |
19 | 20 | 21 | 22 |

Source goes here

23 |

Credit goes here

24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /generators/page/templates/img.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | {% macro jpg(filename, alt, class='', id='', sizes='(min-width: 1px) 100vw, 100vw', srcSize='1800') %} 4 | {{alt}} 12 | {% endmacro %} 13 | 14 | 15 | {% macro png(filename, alt, class='', id='', sizes='(min-width: 1px) 100vw, 100vw', srcSize='1800') %} 16 | {{alt}} 24 | {% endmacro %} -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const batch = require('gulp-batch'); 8 | const browserSync = require('browser-sync').create(); 9 | const gulp = require('gulp'); 10 | const runSequence = require('run-sequence'); 11 | const watch = require('gulp-watch'); 12 | 13 | 14 | module.exports = () => { 15 | browserSync.init({ 16 | files: ['./dist/**/*'], 17 | server: { 18 | baseDir: './dist/', 19 | }, 20 | ghostMode: false, 21 | }); 22 | 23 | watch('./src/data/**/*', () => { runSequence('assets'); }); 24 | 25 | watch('./src/scss/**/*.scss', () => { runSequence('scss'); }); 26 | 27 | watch('./src/templates/**/*.{html,svg}', () => { runSequence('templates'); }); 28 | 29 | watch( 30 | [ 31 | './src/images/**/*', 32 | '!./src/images/opt/**/*', 33 | '!**/*.crdownload', // Ignore chrome's temp file 34 | ], 35 | () => { runSequence('img'); } // eslint-disable-line comma-dangle 36 | // batch((e, cb) => { gulp.start('img', cb); }) 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-dmninteractives", 3 | "version": "0.8.9", 4 | "description": "Yeoman generator for interactive pages at The Dallas Morning News", 5 | "license": "MIT", 6 | "main": "app/index.js", 7 | "repository": "DallasMorningNews/generator-dmninteractives", 8 | "author": { 9 | "name": "John Hancock", 10 | "email": "newsapps@dallasnews.com", 11 | "url": "https://github.com/DallasMorningNews" 12 | }, 13 | "files": [ 14 | "generators" 15 | ], 16 | "keywords": [ 17 | "yeoman-generator" 18 | ], 19 | "dependencies": { 20 | "chalk": "^1.1.3", 21 | "google-url-helper": "^1.0.3", 22 | "lodash": "^4.17.11", 23 | "mkdirp": "^0.5.1", 24 | "octonode": "^0.9.3", 25 | "semver": "^5.5.0", 26 | "underscore.string": "^3.3.5", 27 | "valid-url": "^1.0.9", 28 | "yeoman-generator": "^0.19.2" 29 | }, 30 | "devDependencies": { 31 | "eslint": "^3.14.1", 32 | "eslint-config-airbnb": "^14.0.0", 33 | "eslint-plugin-import": "^2.13.0", 34 | "eslint-plugin-jsx-a11y": "^3.0.2", 35 | "eslint-plugin-react": "^6.9.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "version": "1.0.0", 4 | "main": "src/js/scripts.js", 5 | "license": "UNLICENSED", 6 | "repository": "DallasMorningNews/<%= appName %>", 7 | "private": true, 8 | "dependencies": { 9 | "jquery": "^3", 10 | "normalize-scss": "*", 11 | "pym.js": "^1.x" 12 | }, 13 | "devDependencies": { 14 | "autoprefixer": "^7.2.2", 15 | "babel-preset-env": "^1.6.1", 16 | "babelify": "^7.3.0", 17 | "browser-sync": "^2.24.0", 18 | "browserify": "^14.0.0", 19 | "event-stream": "3.3.4", 20 | "eyeglass": "^1.2.1", 21 | "gulp": "^3.9.1", 22 | "gulp-awspublish": "^3.3.0", 23 | "gulp-awspublish-router": "^0.1.5", 24 | "gulp-cloudfront-invalidate-aws-publish": "^0.2.0", 25 | "gulp-confirm": "^1.0.4", 26 | "gulp-postcss": "^7.0.0", 27 | "gulp-rename": "^1.2.2", 28 | "gulp-sass": "^4.0.1", 29 | "gulp-sourcemaps": "^2.1.1", 30 | "gulp-uglify": "^2.0.0", 31 | "run-sequence": "^1.2.2", 32 | "vinyl-buffer": "^1.0.0", 33 | "vinyl-source-stream": "^1.1.0", 34 | "watchify": "^3.7.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /generators/page/templates/gulpfile.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const runSequence = require('run-sequence'); 8 | const S = require('string'); 9 | 10 | 11 | const gulp = require('./gulp')([ 12 | 'assets', 13 | 'aws', 14 | 'aws-test', 15 | 'browserify', 16 | 'clear-test', 17 | 'plain-images', 18 | 'optimize-images', 19 | 'resize-images', 20 | 'scss', 21 | 'templates', 22 | 'server', 23 | 'watchify', 24 | ]); 25 | const meta = require('./meta.json'); 26 | 27 | 28 | const appName = S(meta.name).slugify().s; 29 | 30 | 31 | gulp.task('img', (cb) => { 32 | runSequence('optimize-images', 'resize-images', 'plain-images', cb); 33 | }); 34 | 35 | gulp.task('default', [ 36 | 'assets', 37 | 'img', 38 | 'scss', 39 | 'watchify', 40 | 'templates', 41 | 'server', 42 | ], () => {}); 43 | 44 | 45 | gulp.task('build', ['assets', 'img', 'scss', 'templates', 'browserify']); 46 | 47 | 48 | gulp.task('publish', (cb) => { runSequence('build', 'aws', 'clear-test', cb); }); 49 | 50 | 51 | gulp.task('publish-test', (cb) => { runSequence('build', 'aws-test', cb); }); 52 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/optimize-images.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const changed = require('gulp-changed'); 8 | const gulp = require('gulp'); 9 | const imagemin = require('gulp-imagemin'); 10 | const imageminJpegRecompress = require('imagemin-jpeg-recompress'); 11 | const merge = require('merge-stream'); 12 | 13 | 14 | module.exports = () => { 15 | const pngs = gulp.src([ 16 | './src/images/**/*.png', 17 | '!./src/images/opt/**/*', 18 | ]) 19 | .pipe(changed('./src/images/opt')) 20 | .pipe( 21 | imagemin({ 22 | optimizationLevel: 4, 23 | progressive: true, 24 | svgoPlugins: [{removeViewBox: false}], 25 | }) 26 | ) 27 | .pipe(gulp.dest('./src/images/opt')); 28 | 29 | const jpgs = gulp.src([ 30 | './src/images/**/*.{jpg,JPG}', 31 | '!./src/images/opt/**/*', 32 | ]) 33 | .pipe(changed('./src/images/opt')) 34 | .pipe(imageminJpegRecompress({ 35 | loops: 3, 36 | min: 50, 37 | max: 75, 38 | target: 0.9999, 39 | progressive: true 40 | })() 41 | ) 42 | .pipe(gulp.dest('./src/images/opt')); 43 | 44 | return merge(pngs, jpgs); 45 | }; 46 | -------------------------------------------------------------------------------- /generators/common/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mix this generator in to get common files that we should have in all of our 3 | * projects. For now it's just a .gitignore, but anything that lives in all 4 | * projects is a good fit for this subgenerator. 5 | */ 6 | 7 | const mkdirp = require('mkdirp'); 8 | const yeoman = require('yeoman-generator'); 9 | 10 | 11 | module.exports = yeoman.Base.extend({ 12 | writing: { 13 | git() { 14 | this.fs.copy( 15 | this.templatePath('gitignore'), 16 | this.destinationPath('.gitignore') // eslint-disable-line comma-dangle 17 | ); 18 | 19 | this.fs.copy( 20 | this.templatePath('gitkeep'), 21 | this.destinationPath('./src/assets/.gitkeep') // eslint-disable-line comma-dangle 22 | ); 23 | 24 | this.fs.copy( 25 | this.templatePath('gitkeep'), 26 | this.destinationPath('./src/data/.gitkeep') // eslint-disable-line comma-dangle 27 | ); 28 | }, 29 | 30 | directories() { 31 | mkdirp('./dist'); 32 | }, 33 | 34 | browserCompat() { 35 | this.fs.copy( 36 | this.templatePath('browserslistrc'), 37 | this.destinationPath('.browserslistrc') // eslint-disable-line comma-dangle 38 | ); 39 | }, 40 | }, 41 | }); 42 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/clear-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const aws = require('aws-sdk'); 8 | const gulp = require('gulp'); 9 | const S = require('string'); 10 | 11 | 12 | const awsJson = require('./../../aws.json'); 13 | const meta = require('./../../meta.json'); 14 | 15 | 16 | const appName = S(meta.name).slugify().s; 17 | 18 | 19 | module.exports = () => { 20 | aws.config.update({ 21 | accessKeyId: awsJson.accessKeyId, 22 | secretAccessKey: awsJson.secretAccessKey, 23 | region: 'us-east-1', 24 | }); 25 | 26 | const s3 = new aws.S3(); 27 | 28 | let params = { 29 | Bucket: 'interactives.dallasnews.com.test', 30 | // Do not change this! 31 | Prefix: `test/${appName}`, 32 | }; 33 | 34 | s3.listObjects(params, (err, data) => { 35 | if (err) { 36 | console.log(err); 37 | } else { 38 | params = { Bucket: awsJson.params.Bucket }; 39 | params.Delete = {}; 40 | params.Delete.Objects = []; 41 | 42 | data.Contents.forEach((content) => { 43 | params.Delete.Objects.push({ Key: content.Key }); 44 | }); 45 | 46 | if (params.Delete.Objects.length > 0) { 47 | s3.deleteObjects(params, (errMsg, fileData) => console.log(fileData.Deleted.length)); 48 | } 49 | } 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /generators/page/templates/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "<%= appName %>", 3 | "version": "1.0.0", 4 | "main": "app.js", 5 | "license": "UNLICENSED", 6 | "private": true, 7 | "dependencies": { 8 | "autoprefixer": "^7.2.2", 9 | "aws-sdk": "^2.10.0", 10 | "babel-preset-env": "^1.6.1", 11 | "babelify": "^7.3.0", 12 | "browserify": "^14.0.0", 13 | "browser-sync": "^2.24.0", 14 | "deline": "^1.0.4", 15 | "event-stream": "3.3.4", 16 | "eyeglass": "^1.2.1", 17 | "gulp": "^3.9.0", 18 | "gulp-awspublish": "^3.0.1", 19 | "gulp-awspublish-router": "^0.1.5", 20 | "gulp-batch": "^1.0.5", 21 | "gulp-changed": "^2.0.0", 22 | "gulp-cloudfront-invalidate-aws-publish": "^0.2.0", 23 | "gulp-confirm": "^1.0.4", 24 | "gulp-image-resize": "^0.12.0", 25 | "gulp-imagemin": "^3.1.1", 26 | "gulp-nunjucks-render": "^1.0.0", 27 | "gulp-postcss": "^7.0.0", 28 | "gulp-rename": "^1.2.2", 29 | "gulp-sass": "^4.0.1", 30 | "gulp-sourcemaps": "^1.6.0", 31 | "gulp-uglify": "^2.0.0", 32 | "gulp-util": "^3.0.8", 33 | "gulp-watch": "^4.3.5", 34 | "imagemin-jpeg-recompress": "^4.3.0", 35 | "jquery": "^3.2.1", 36 | "merge-stream": "^1.0.0", 37 | "run-sequence": "^1.1.5", 38 | "string": "^3.3.1", 39 | "vinyl-buffer": "^1.0.0", 40 | "vinyl-source-stream": "^1.1.0", 41 | "watchify": "^3.7.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /generators/page/templates/README.md: -------------------------------------------------------------------------------- 1 | # interactive_<%= slug %> 2 | 3 | This is an interactive presentation graphic built using the [`dmninteractives` Yeoman generator](https://github.com/DallasMorningNews/generator-dmninteractives). 4 | 5 | ## Requirements 6 | 7 | - Node - `brew install node` 8 | - Gulp - `npm install -g gulp-cli` 9 | 10 | ## Local development 11 | 12 | #### Installation 13 | 14 | 1. `npm install` to install development tooling 15 | 2. `gulp` to open a local development server 16 | 17 | #### What's inside 18 | 19 | - `src/index.html` - HTML markup, which gets processed by Nunjucks 20 | - `src/js/*.js` - Graphic scripts, written in ES2015 (it'll be transpiled with Babel) 21 | - `src/scss/*.scss` - Graphic styles in SCSS 22 | - `src/data/*` - files that should be both published and committed to the repository (probably CSVs, JSON, etc.); copied to `dist/data/*` by Gulp 23 | - `src/assets/*` - assets (probably media assets, such as Illustrator files) that don’t get copied by Gulp but are tracked by `git` 24 | - `dist/*` - All of the above, transpiled 25 | 26 | _Important caveat:_ Video, audio and ZIP files are ignored by `git` regardless of where they're saved. You'll need to manually alter the [`.gitignore`](.gitignore) file to have them committed to Github. 27 | 28 | #### Publishing 29 | 30 | `gulp publish` will upload your [`dist/`](dist/) folder to the `<%= year %>/<%= slug %>/` folder on our interactives S3 bucket. 31 | 32 | ## Copyright 33 | 34 | ©<%= year %> The Dallas Morning News 35 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/aws-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const awspublish = require('gulp-awspublish'); 8 | const confirm = require('gulp-confirm'); 9 | const deline = require('deline'); 10 | const gulp = require('gulp'); 11 | const gutil = require('gulp-util'); 12 | const path = require('path'); 13 | const rename = require('gulp-rename'); 14 | const S = require('string'); 15 | 16 | 17 | const awsJson = require('./../../aws.json'); 18 | const meta = require('./../../meta.json'); 19 | 20 | 21 | const appName = S(meta.name).slugify().s; 22 | 23 | const awsDirectory = `test/${appName}/`; 24 | 25 | 26 | module.exports = () => { 27 | const publisher = awspublish.create(Object.assign(awsJson, { 28 | params: { 29 | Bucket: 'interactives.dallasnews.com.test' 30 | } 31 | })); 32 | 33 | return gulp.src('./dist/**/*') 34 | .pipe(confirm({ 35 | question: deline`You're about to publish this project to AWS 36 | under the directory '${awsDirectory}'. 37 | Are you sure you want to do this?`, 38 | input: '_key:y', 39 | })) 40 | .pipe(rename((filePath) => { 41 | // eslint-disable-next-line no-param-reassign 42 | filePath.dirname = awsDirectory + filePath.dirname.replace('.\\', ''); 43 | })) 44 | .pipe(publisher.publish({}, { force: false, noAcl: true })) 45 | .pipe(publisher.cache()) 46 | .pipe(awspublish.reporter()); 47 | }; 48 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/src/sass/_ooyala.scss: -------------------------------------------------------------------------------- 1 | // ----------------------------------------------------------------------------- 2 | // OOYALA CODE 3 | // ----------------------------------------------------------------------------- 4 | 5 | .video-block { 6 | width: 55%; 7 | margin: 4.8rem 0; 8 | font-size: 15px; 9 | img { 10 | max-width: 170px !important; 11 | max-height: 18px !important; 12 | } 13 | } 14 | .video-wrapper { 15 | position: relative; 16 | padding-bottom: 56.25%; 17 | padding-top: 25px; 18 | height: 0; 19 | } 20 | .video-wrapper iframe, .video-wrapper embed { 21 | position: absolute; 22 | top: 0; 23 | left: 0; 24 | width: 100%; 25 | height: 100%; 26 | } 27 | 28 | @media (max-width: 900px) { 29 | .video-block { 30 | width: 100%; 31 | } 32 | } 33 | 34 | @media (max-width: 767px) { 35 | .video-block { 36 | margin: 2.4rem 0; 37 | } 38 | } 39 | 40 | @media (max-width: 681px) { 41 | .video-block { 42 | img { 43 | max-width: 130px !important; 44 | } 45 | } 46 | } 47 | 48 | @media (max-width: 500px) { 49 | .video-block { 50 | img { 51 | max-width: 80px !important; 52 | max-height: 14px !important; 53 | } 54 | } 55 | } 56 | 57 | .oo-state-screen-info { 58 | font-size: 1.6rem !important; 59 | } 60 | 61 | .oo-state-screen-description { 62 | font-size: 1.1rem !important; 63 | } 64 | 65 | .oo-player-container { 66 | min-width: 0px !important; 67 | } 68 | 69 | .oo-state-screen-title, .oo-state-screen-description { 70 | max-width: 90% !important; 71 | } 72 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/src/sass/_base.scss: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | } 4 | 5 | h1, h2, h3, h4 { 6 | margin-bottom: 1.2rem; 7 | } 8 | h5, h6 { 9 | margin-bottom: .6rem; 10 | } 11 | h1 { 12 | font-size: 6rem; 13 | line-height: 7.2rem; 14 | } 15 | h2 { 16 | font-size: 4.8rem; 17 | line-height: 6rem; 18 | } 19 | h3 { 20 | font-size: 3.6rem; 21 | line-height: 4.2rem; 22 | } 23 | h4 { 24 | font-size: 2.4rem; 25 | line-height: 3rem; 26 | } 27 | h5 { 28 | font-size: 2rem; 29 | line-height: 2.4rem; 30 | } 31 | h6 { 32 | font-size: 1.6rem; 33 | line-height: 1.8rem; 34 | } 35 | 36 | .clearfix:after { 37 | display: table; 38 | content: ""; 39 | clear: both; 40 | } 41 | 42 | #embed__container { 43 | font-family: $sans; 44 | max-width: 900px; 45 | width: 100%; 46 | margin: 0 auto; 47 | background-color: $black245; 48 | padding: 2.4rem; 49 | display: block; 50 | font-weight: 300; 51 | a { 52 | color: $dmnblue; 53 | text-decoration: underline; 54 | font-weight: 700; 55 | } 56 | } 57 | 58 | .embed__chatter { 59 | margin-bottom: 2.4rem; 60 | p { 61 | @include type(1.4rem, 1.8rem, 1.2rem); 62 | &:last-of-type { 63 | margin-bottom: 0; 64 | } 65 | } 66 | h4 { 67 | margin-top: 0; 68 | } 69 | } 70 | 71 | .embed__content { 72 | p { 73 | @include type(1.4rem, 1.8rem, 1.2rem); 74 | &:last-of-type { 75 | margin-bottom: 0; 76 | } 77 | } 78 | .source, .credit { 79 | @include type(1.2rem, 1.5rem, .6rem); 80 | } 81 | .credit { 82 | text-align: right; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulp/tasks/jsify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browserify = require('browserify'); 4 | const gulp = require('gulp'); 5 | const source = require('vinyl-source-stream'); 6 | const uglify = require('gulp-uglify'); 7 | const buffer = require('vinyl-buffer'); 8 | const sourcemaps = require('gulp-sourcemaps'); 9 | const watchify = require('watchify'); 10 | const gutil = require('gulp-util'); 11 | const babelify = require('babelify'); 12 | const es = require('event-stream'); 13 | 14 | module.exports = (watch) => { 15 | const wrapper = watch ? watchify : (b) => b; 16 | 17 | return () => { 18 | const files = [ 19 | 'scripts.js', 20 | ]; 21 | 22 | const tasks = files.map((entry) => { 23 | const props = { 24 | entries: `./src/js/${entry}`, 25 | extensions: ['.js'], 26 | cache: {}, 27 | packageCache: {}, 28 | debug: true, 29 | }; 30 | 31 | const bundler = wrapper(browserify(props).transform(babelify, { 32 | presets: ['env'], 33 | })); 34 | 35 | function bundle() { 36 | return bundler.bundle() 37 | .on('error', gutil.log.bind(gutil, 'Browserify Error')) 38 | .pipe(source(entry)) 39 | .pipe(buffer()) 40 | .pipe(sourcemaps.init({ loadMaps: true })) 41 | .pipe(uglify({ mangle: false, compress: true }).on('error', gutil.log)) 42 | .pipe(sourcemaps.write('./')) 43 | .pipe(gulp.dest('./dist/js/')); 44 | } 45 | 46 | bundler.on('log', gutil.log); 47 | bundler.on('update', bundle); 48 | 49 | return bundle(); 50 | }); 51 | return es.merge.apply(null, tasks); 52 | } 53 | }; 54 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/gulp/tasks/browserify.js: -------------------------------------------------------------------------------- 1 | const browserify = require('browserify'); 2 | const gulp = require('gulp'); 3 | const source = require('vinyl-source-stream'); 4 | const uglify = require('gulp-uglify'); 5 | const buffer = require('vinyl-buffer'); 6 | const sourcemaps = require('gulp-sourcemaps'); 7 | const watchify = require('watchify'); 8 | const gutil = require('gulp-util'); 9 | const babelify = require('babelify'); 10 | const util = require('gulp-util'); 11 | const es = require('event-stream'); 12 | 13 | module.exports = () => { 14 | const files = [ 15 | 'chart.js', 16 | 'global-chart.js', 17 | ]; 18 | 19 | const tasks = files.map((entry) => { 20 | const props = { 21 | entries: `./src/js/${entry}`, 22 | extensions: ['.js'], 23 | cache: {}, 24 | packageCache: {}, 25 | debug: true, 26 | }; 27 | 28 | const bundler = watchify(browserify(props).transform(babelify, { 29 | presets: ['env'], 30 | })); 31 | 32 | function bundle() { 33 | return bundler.bundle() 34 | .on('error', gutil.log.bind(gutil, 'Browserify Error')) 35 | .pipe(source(entry)) 36 | .pipe(buffer()) 37 | .pipe(!!util.env.production ? sourcemaps.init({ loadMaps: true }) : util.noop()) 38 | .pipe(!!util.env.production ? 39 | uglify({ mangle: false, compress: true }).on('error', gutil.log) : util.noop() 40 | ) 41 | .pipe(!!util.env.production ? sourcemaps.write('./') : util.noop()) 42 | .pipe(gulp.dest('./dist/js/')); 43 | } 44 | 45 | bundler.on('log', gutil.log); 46 | bundler.on('update', bundle); 47 | 48 | return bundle(); 49 | }); 50 | return es.merge.apply(null, tasks); 51 | }; 52 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/README: -------------------------------------------------------------------------------- 1 | # <%= appName %> 2 | 3 | Chart module for _{your chart type here}_. 4 | 5 | ![](preview.png) 6 | 7 | ### Install 8 | ```bash 9 | $ npm install --save <%= appName %> 10 | ``` 11 | 12 | ##### Requirements 13 | 14 | This module uses ES6 syntax. To use as a pre-compiled module, you'll need a compiler like [babel](https://babeljs.io/). 15 | 16 | ### Use 17 | 18 | In the client, include the `global-chart.js` bundle, which defines a global chart object, `<%= objName %>`: 19 | 20 | ```html 21 | 22 | ``` 23 | 24 | To use as a module, simply import the chart object: 25 | ```javascript 26 | import <%= objName %> from '<%= appName %>'; 27 | ``` 28 | 29 | The chart object has two methods, one to create the chart, initially, and another to update it. 30 | 31 | ```javascript 32 | var myChart = new <%= objName %>(); 33 | 34 | // create needs a selection string and prefectched data 35 | myChart.create('#chart', data); 36 | 37 | // update needs only new data 38 | myChart.update(newData); 39 | ``` 40 | 41 | To apply this chart's default styles when using SCSS, simply define the variable `$<%= objName %>-container` to represent the ID or class of the chart's container(s) and import the `_chart-styles.scss` partial. 42 | 43 | ```CSS 44 | $<%= objName %>-container: '#chart'; 45 | 46 | @import 'path/to/<%= appName %>/src/scss/_chart-styles'; 47 | ``` 48 | 49 | 50 | ### Developing 51 | 52 | Write your chart code in `chart.js` and add custom styles to `_chart-styles.scss`. 53 | 54 | Then, just run gulp: 55 | ```bash 56 | $ gulp 57 | ``` 58 | 59 | Or to minimize javascript before publishing: 60 | ```bash 61 | $ gulp --production 62 | ``` 63 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/README.md: -------------------------------------------------------------------------------- 1 | # <%= slug %> 2 | 3 | This is an embeddable graphic built using the [`dmninteractives` Yeoman generator](https://github.com/DallasMorningNews/generator-dmninteractives). It's designed to be embedded using [Pym.js](http://blog.apps.npr.org/pym.js/) as a responsive `iframe`. 4 | 5 | ## Requirements 6 | 7 | - Node - `brew install node` 8 | - Gulp - `npm install -g gulp-cli` 9 | 10 | ## Local development 11 | 12 | #### Installation 13 | 14 | 1. `npm install` to install development tooling 15 | 2. `gulp` to open a local development server 16 | 17 | #### What's inside 18 | 19 | - `src/index.html` - Graphic HTML markup; there's no Nunjucks, etc. so this is just straight HTML 20 | - `src/embed.html` - A page to test your embed 21 | - `src/js/*.js` - Graphic scripts, written in ES2015 (it'll be transpiled with Babel) 22 | - `src/sass/*.scss` - Graphic styles in SCSS 23 | - `dist/*` - All of the above, transpiled 24 | 25 | _Important caveat:_ Video, audio and ZIP files are ignored by `git` regardless of where they're saved. You'll need to manually alter the [`.gitignore`](.gitignore) file to have them committed to Github. 26 | 27 | #### Publishing 28 | 29 | `gulp publish` will upload your [`dist/`](dist/) folder to the `embeds/<%= year %>/<%= slug %>/` folder on our interactives S3 bucket. 30 | 31 | ## Usage 32 | 33 | #### Embedding in Serif 34 | 35 | The below embed code can be pasted into a Serif "code block": 36 | 37 | ```html 38 |
39 | 40 | 41 | 42 | ``` 43 | 44 | ## Copyright 45 | 46 | ©<%= year %> The Dallas Morning News 47 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/jsify.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const babelify = require('babelify'); 8 | const browserify = require('browserify'); 9 | const buffer = require('vinyl-buffer'); 10 | const es = require('event-stream'); 11 | const gulp = require('gulp'); 12 | const gutil = require('gulp-util'); 13 | const rename = require('gulp-rename'); 14 | const source = require('vinyl-source-stream'); 15 | const sourcemaps = require('gulp-sourcemaps'); 16 | const uglify = require('gulp-uglify'); 17 | const watchify = require('watchify'); 18 | 19 | 20 | module.exports = (watch) => { 21 | const wrapper = watch ? watchify : b => b; 22 | 23 | return () => { 24 | const files = [ 25 | 'scripts.js', 26 | ]; 27 | 28 | const tasks = files.map((entry) => { 29 | const props = { 30 | entries: `./src/js/${entry}`, 31 | extensions: ['.js'], 32 | cache: {}, 33 | packageCache: {}, 34 | debug: true, 35 | }; 36 | 37 | const bundler = wrapper(browserify(props).transform(babelify, { 38 | presets: ['env'], 39 | })); 40 | 41 | function bundle() { 42 | return bundler.bundle() 43 | .on('error', gutil.log.bind(gutil, 'Browserify Error')) 44 | .pipe(source(entry)) 45 | .pipe(buffer()) 46 | // eslint-disable-next-line no-param-reassign 47 | .pipe(rename((filePath) => { filePath.basename += '-bundle'; })) 48 | .pipe(sourcemaps.init({ loadMaps: true })) 49 | .pipe(uglify({ mangle: false, compress: true }).on('error', gutil.log)) 50 | .pipe(sourcemaps.write('./')) 51 | .pipe(gulp.dest('./dist/js/')); 52 | } 53 | 54 | bundler.on('log', gutil.log); 55 | bundler.on('update', bundle); 56 | 57 | return bundle(); 58 | }); 59 | return es.merge.apply(null, tasks); 60 | }; 61 | }; 62 | -------------------------------------------------------------------------------- /generators/gitsecrets/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mix this into your generator by adding a composeWith('gitsecrets') to your 3 | * generator's initializing() method to add optional run of `git init` and 4 | * secrets protection with git-secrets 5 | */ 6 | const chalk = require('chalk'); 7 | const yeoman = require('yeoman-generator'); 8 | 9 | 10 | module.exports = yeoman.Base.extend({ 11 | prompting() { 12 | const done = this.async(); 13 | 14 | this.prompt([{ 15 | type: 'confirm', 16 | name: 'doit', 17 | message: 'Would you like to try to prevent AWS credentials from being committed to this project\'s git repository (we\'ll create a new repo if one doesn\'t exist)?', 18 | }], (props) => { 19 | this.doit = props.doit; 20 | done(); 21 | }); 22 | }, 23 | 24 | writing() { 25 | this.failed = false; 26 | 27 | if (!this.doit) { 28 | return; 29 | } 30 | 31 | const done = this.async(); 32 | 33 | this.spawnCommand('git', ['init', '--quiet']).on('close', () => { 34 | this.spawnCommand('git', ['secrets', '--install']).on('close', (installCode) => { 35 | if (installCode !== 0) { 36 | this.failed = true; 37 | done(); 38 | return; 39 | } 40 | 41 | this.spawnCommand('git', ['secrets', '--register-aws']).on('close', (awsInstallCode) => { 42 | if (awsInstallCode !== 0) { 43 | this.failed = true; 44 | } 45 | }); 46 | 47 | done(); 48 | }); 49 | }); 50 | }, 51 | 52 | end() { 53 | if (this.failed === true) { 54 | this.log(`${chalk.bold(chalk.red('Enabling AWS secrets protection failed.'))}`); 55 | this.log(` (1) Ensure you have git-secrets installed on your computer by running with ${chalk.magenta('brew install git-secrets')}.`); 56 | this.log(` (2) Then run ${chalk.magenta('git secrets --install && git secrets --register-aws')} to manually protect this repo.`); 57 | } 58 | }, 59 | }); 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/yeoman,node,osx 3 | 4 | ### Node ### 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | 24 | # nyc test coverage 25 | .nyc_output 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # Bower dependency directory (https://bower.io/) 31 | bower_components 32 | 33 | # node-waf configuration 34 | .lock-wscript 35 | 36 | # Compiled binary addons (http://nodejs.org/api/addons.html) 37 | build/Release 38 | 39 | # Dependency directories 40 | node_modules/ 41 | jspm_packages/ 42 | 43 | # Typescript v1 declaration files 44 | typings/ 45 | 46 | # Optional npm cache directory 47 | .npm 48 | 49 | # Optional eslint cache 50 | .eslintcache 51 | 52 | # Optional REPL history 53 | .node_repl_history 54 | 55 | # Output of 'npm pack' 56 | *.tgz 57 | 58 | # Yarn Integrity file 59 | .yarn-integrity 60 | 61 | # dotenv environment variables file 62 | .env 63 | 64 | 65 | ### OSX ### 66 | *.DS_Store 67 | .AppleDouble 68 | .LSOverride 69 | 70 | # Icon must end with two \r 71 | Icon 72 | 73 | # Thumbnails 74 | ._* 75 | 76 | # Files that might appear in the root of a volume 77 | .DocumentRevisions-V100 78 | .fseventsd 79 | .Spotlight-V100 80 | .TemporaryItems 81 | .Trashes 82 | .VolumeIcon.icns 83 | .com.apple.timemachine.donotpresent 84 | 85 | # Directories potentially created on remote AFP share 86 | .AppleDB 87 | .AppleDesktop 88 | Network Trash Folder 89 | Temporary Items 90 | .apdisk 91 | 92 | ### Yeoman ### 93 | bower_components/ 94 | 95 | build/ 96 | dist/ 97 | 98 | # End of https://www.gitignore.io/api/yeoman,node,osx 99 | 100 | .yo-rc.json 101 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/templates/gulp/tasks/aws.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const confirm = require('gulp-confirm'); 4 | const rename = require('gulp-rename'); 5 | const awspublish = require('gulp-awspublish'); 6 | const awspublishRouter = require("gulp-awspublish-router"); 7 | const cloudfront = require('gulp-cloudfront-invalidate-aws-publish'); 8 | const gulp = require('gulp'); 9 | 10 | const awsJson = require('../../aws.json'); 11 | const meta = require('../../meta.json'); 12 | 13 | const cfSettings = { 14 | distribution: 'E3QK9W6AW5LAVH', 15 | accessKeyId: awsJson.accessKeyId, 16 | secretAccessKey: awsJson.secretAccessKey, 17 | wait: false, 18 | indexRootPath: true, 19 | }; 20 | 21 | const oneDayInMS = 60 * 60 * 24; 22 | 23 | const routes = { 24 | routes: { 25 | // Cache video and audio for 1 week on user's computer, one month in CloudFront 26 | '^.*\\.(aif|iff|m3u|m4a|mid|mp3|mpa|ra|wav|wma|3g2|3gp|asf|asx|avi|flv|mov|mp4|mpg|rm|swf|vob|wmv)': { 27 | cacheTime: oneDayInMS * 7, 28 | sharedCacheTime: oneDayInMS * 30, 29 | }, 30 | // Cache images 2 days on user's computer, one month in CloudFront 31 | '^.*\\.(jpg|jpeg|svg|bmp|png|tiff|gif)': { 32 | cacheTime: oneDayInMS * 2, 33 | sharedCacheTime: oneDayInMS * 30, 34 | }, 35 | // Cache images 5 minutes on user's computer, one month in CloudFront 36 | '^.*\\.(html|js|css)': { 37 | cacheTime: 60 * 5, 38 | sharedCacheTime: oneDayInMS, 39 | }, 40 | // Catch everything else in an empty route, or we'll get errors 41 | '.*': {}, 42 | }, 43 | }; 44 | 45 | module.exports = () => { 46 | const publisher = awspublish.create(awsJson); 47 | const awsDirectory = `embeds/${meta.publishYear}/${meta.name}/`; 48 | 49 | return gulp.src('./dist/**/*') 50 | .pipe(confirm({ 51 | question: `You're about to publish this project to AWS under directory ${ 52 | awsDirectory 53 | }. Are you sure you want to do this?`, 54 | input: '_key:y', 55 | })) 56 | .pipe(rename((path) => { 57 | // eslint-disable-next-line no-param-reassign 58 | path.dirname = awsDirectory + path.dirname.replace('.\\', ''); 59 | })) 60 | .pipe(awspublishRouter(routes)) 61 | .pipe(publisher.publish({}, { force: false })) 62 | .pipe(cloudfront(cfSettings)) 63 | .pipe(publisher.cache()) 64 | .pipe(awspublish.reporter()); 65 | }; 66 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/resize-images.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const changed = require('gulp-changed'); 8 | const fs = require('fs'); 9 | const gulp = require('gulp'); 10 | const imageResize = require('gulp-image-resize'); 11 | const merge = require('merge-stream'); 12 | const path = require('path'); 13 | const rename = require('gulp-rename'); 14 | 15 | 16 | module.exports = () => { 17 | function resize(size){ 18 | /** 19 | * From gulp-changed source: 20 | * https://github.com/sindresorhus/gulp-changed/blob/master/index.js#L9 21 | */ 22 | function fsOperationFailed(stream, sourceFile, err) { 23 | if (err) { 24 | if (err.code !== 'ENOENT') { 25 | stream.emit('error', new gutil.PluginError('gulp-changed', err, { 26 | fileName: sourceFile.path 27 | })); 28 | } 29 | stream.push(sourceFile); 30 | } 31 | return err; 32 | } 33 | 34 | /** 35 | * Custom comparator that takes into account filename changes. 36 | */ 37 | function cacheCompare(stream, cb, sourceFile, targetPath) { 38 | const dir = path.dirname(targetPath); 39 | const ext = path.extname(targetPath); 40 | 41 | const base = path.basename(targetPath, ext); 42 | 43 | targetPath = path.join(dir, (base + ('-' + size.toString()) + ext.toLowerCase())); 44 | 45 | fs.stat(targetPath, (err, targetStat) => { 46 | if (!fsOperationFailed(stream, sourceFile, err)) { 47 | if (sourceFile.stat.mtime > targetStat.mtime) { 48 | stream.push(sourceFile); 49 | } 50 | } 51 | cb(); 52 | }); 53 | } 54 | 55 | return gulp.src([ 56 | './src/images/opt/**/*.{png,jpg,JPG}', 57 | '!./src/images/opt/**/_*.{png,jpg,JPG}', 58 | ]) 59 | .pipe(changed('./dist/images', { hasChanged: cacheCompare })) 60 | .pipe(imageResize({ width : size, upscale : false, imageMagick : true })) 61 | .pipe(rename(path => { 62 | path.basename += ('-' + size.toString()); 63 | path.extname = path.extname.toLowerCase(); 64 | return path; 65 | })) 66 | .pipe(gulp.dest('./dist/images')); 67 | } 68 | 69 | // Create and copy resized pngs 70 | const s1 = resize(3000); 71 | const s2 = resize(2400); 72 | const s3 = resize(1800); 73 | const s4 = resize(1200); 74 | const s5 = resize(600); 75 | 76 | return merge(s1, s2, s3, s4, s5); 77 | }; 78 | -------------------------------------------------------------------------------- /generators/app/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const _ = require('lodash'); 8 | const fs = require('fs'); 9 | const path = require('path'); 10 | const yeoman = require('yeoman-generator'); 11 | 12 | 13 | module.exports = yeoman.Base.extend({ 14 | prompting() { 15 | const done = this.async(); 16 | 17 | this.log(' ____ __ ____ _____\n'); 18 | this.log(' / __ \\/ |/ / | / / | ____ ____ _____\n'); 19 | this.log(' / / / / /|_/ / |/ / /| | / __ \\/ __ \\/ ___/\n'); 20 | this.log(' / /_/ / / / / /| / ___ |/ /_/ / /_/ (__ )\n'); 21 | this.log('/_____/_/ /_/_/ |_/_/ |_/ .___/ .___/____/\n'); 22 | this.log(' /_/ /_/ \n'); 23 | 24 | const generatorDir = path.join(this.sourceRoot(), '../..'); 25 | const generatorSubdirs = _.filter( 26 | fs.readdirSync(generatorDir), 27 | // eslint-disable-next-line comma-dangle 28 | pathName => fs.statSync(path.join(generatorDir, pathName)).isDirectory() 29 | ); 30 | 31 | let subGeneratorConfigs = []; 32 | _.each(generatorSubdirs, (dirName) => { 33 | const rawConfigPath = path.join(generatorDir, dirName, 'config.json'); 34 | 35 | if (fs.existsSync(rawConfigPath)) { 36 | // eslint-disable-next-line import/no-dynamic-require,global-require 37 | const rawConfig = require(rawConfigPath); 38 | 39 | const finalConfig = _.clone(rawConfig); 40 | 41 | finalConfig.typeSlug = dirName; 42 | 43 | subGeneratorConfigs.push(finalConfig); 44 | } 45 | }); 46 | 47 | subGeneratorConfigs = _.sortBy(subGeneratorConfigs, ['priority']); 48 | 49 | this.subGeneratorConfigs = subGeneratorConfigs; 50 | 51 | const prompts = [ 52 | { 53 | type: 'list', 54 | name: 'module', 55 | message: 'Welcome. What would you like to make today?', 56 | choices: _.map( 57 | this.subGeneratorConfigs, 58 | // eslint-disable-next-line comma-dangle 59 | config => ({ name: config.verboseName, value: config.typeSlug }) 60 | ), 61 | }, 62 | ]; 63 | 64 | this.prompt(prompts, (props) => { 65 | this.props = props; 66 | done(); 67 | }); 68 | }, 69 | 70 | subgen() { 71 | if (_.includes(_.map(this.subGeneratorConfigs, 'typeSlug'), this.props.module)) { 72 | this.composeWith( 73 | `dmninteractives:${this.props.module}`, 74 | { 75 | options: { 76 | baseConfig: _.find(this.subGeneratorConfigs, { typeSlug: this.props.module }), 77 | }, 78 | } // eslint-disable-line comma-dangle 79 | ); 80 | } 81 | }, 82 | }); 83 | -------------------------------------------------------------------------------- /generators/linters/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Mix this into your generator by adding a composeWith('linters') to your 3 | * generator's initializing() method to add ESLint configuration 4 | */ 5 | 6 | const yeoman = require('yeoman-generator'); 7 | const chalk = require('chalk'); 8 | 9 | 10 | module.exports = yeoman.Base.extend({ 11 | CHOICE_AIRBNB: 'airbnb', 12 | CHOICE_ES6_RECOMMENDED: 'es6-recommended', 13 | CHOICE_NO_ESLINT: 'none', 14 | 15 | prompting() { 16 | const done = this.async(); 17 | 18 | const prompts = [{ 19 | type: 'list', 20 | name: 'lintProfile', 21 | choices: [{ 22 | value: this.CHOICE_AIRBNB, 23 | name: `${chalk.bold('eslint-config-airbnb')} [strict enforcement of ES2015]`, 24 | }, { 25 | value: this.CHOICE_ES6_RECOMMENDED, 26 | name: `${chalk.bold('es6-recommended')} [encourages ES2015 syntax, but more forgiving]`, 27 | }, { 28 | value: this.CHOICE_NO_ESLINT, 29 | name: `${chalk.bold('No ESLint')} [skips ESLint installation altogether]`, 30 | }], 31 | message: 'Would you like to add an ESLint configuration?', 32 | store: true, 33 | default: 0, 34 | }]; 35 | 36 | this.prompt(prompts, (answers) => { 37 | this.lintProfile = answers.lintProfile; 38 | done(); 39 | }); 40 | }, 41 | 42 | writing() { 43 | if (this.lintProfile === this.CHOICE_NO_ESLINT) return; 44 | 45 | const esLintConfig = { 46 | root: true, 47 | parser: 'babel-eslint', 48 | rules: { 49 | 'no-console': 0, 50 | }, 51 | env: { 52 | browser: true, 53 | }, 54 | }; 55 | 56 | switch (this.lintProfile) { 57 | case (this.CHOICE_AIRBNB): 58 | Object.assign(esLintConfig, { 59 | extends: 'airbnb', 60 | }); 61 | break; 62 | case (this.CHOICE_ES6_RECOMMENDED): 63 | Object.assign(esLintConfig, { 64 | extends: 'eslint:recommended', 65 | plugins: ['es6-recommended'], 66 | }); 67 | break; 68 | default: 69 | } 70 | 71 | this.fs.writeJSON('./src/.eslintrc.json', esLintConfig); 72 | }, 73 | 74 | install() { 75 | if (this.lintProfile === this.CHOICE_NO_ESLINT) return; 76 | 77 | const npmDeps = [ 78 | 'eslint@4', 79 | 'babel-eslint', 80 | ]; 81 | 82 | switch (this.lintProfile) { 83 | case this.CHOICE_AIRBNB: 84 | npmDeps.push( 85 | 'eslint-plugin-import@^2.6', 86 | 'eslint-plugin-react@^7.1', 87 | 'eslint-plugin-jsx-a11y@^5.1', 88 | 'eslint-config-airbnb@^15.0'); 89 | break; 90 | case this.CHOICE_ES6_RECOMMENDED: 91 | npmDeps.push('eslint-plugin-es6-recommended'); 92 | break; 93 | default: 94 | } 95 | 96 | this.npmInstall(npmDeps, { 'save-dev': true }); 97 | }, 98 | }); 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-dmninteractives [![NPM version][npm-image]][npm-url] 2 | 3 | A [Yeoman](http://yeoman.io) generator for DMN-flavored "interactive" pages with easy publishing. 4 | 5 | Translation: A simple app that helps speed up developing a custom "interactive" page using our DMN house template. 6 | 7 | ### What it does: 8 | 9 | - Scaffolds your project's development directory, shortcutting setup time. 10 | - Compiles and bundles SCSS and JS files. 11 | - Populates meta tags from a JSON file. 12 | - Creates responsive image sets optimized for mobile devices. 13 | - Publishes your project to an Amazon S3 bucket. 14 | 15 | **See the [wiki](https://github.com/DallasMorningNews/generator-dmninteractives/wiki) for complete instructions on using the app.** 16 | 17 | ## Requirements 18 | 19 | - Node - `brew install node` 20 | - Git - `brew install git` 21 | - _Recommended:_ `git-secrets` - `brew install git-secrets` 22 | 23 | ## Installation 24 | 25 | Install global dependencies, including Yeoman and the generator. 26 | 27 | ```bash 28 | $ npm install -g gulp-cli yo 29 | $ npm install -g --production generator-dmninteractives 30 | ``` 31 | 32 | _(The `--production` flag is optional, but prevents your global Node modules folder from getting confused by the dev tooling for the generator)_ 33 | 34 | ## Usage 35 | 36 | #### Starting a new project 37 | 38 | Create a clean directory for your project in your terminal. 39 | 40 | ```bash 41 | $ mkdir your-app-directory 42 | $ cd your-app-directory 43 | ``` 44 | 45 | Run the generator in your new project directory. 46 | 47 | ```bash 48 | $ yo dmninteractives 49 | ``` 50 | 51 | The generator will set up your working directory, install dependencies, copy template files and scripts, start a local webserver and open your browser. 52 | 53 | Be sure to fill out the `meta.json` file to correctly complete metatags in the template. 54 | 55 | #### Developing your project 56 | 57 | The generator uses [gulp](http://gulpjs.com/), a node-based task runner, to watch your directories for changes as you code, render templates, prepare static files and start a local webserver to preview your project in the browser. 58 | 59 | To work on your project, launch gulp in your app's root directory: 60 | 61 | ```bash 62 | $ gulp 63 | ``` 64 | 65 | Your project is separated into two main directories: 66 | - `src` 67 | - `dist` 68 | 69 | The `src` directory is your working directory. You'll write all your code and place all necessary static assets in this directory. 70 | 71 | The `dist` directory includes transpiled SCSS, minified JavaScript and responsive images. Gulp serves a live preview of your page from this folder. 72 | 73 | #### Publishing your project 74 | 75 | Execute one of the gulp publish commands to publish to either the test or production directory of the bucket: 76 | - `gulp publish` 77 | - `gulp publish-test` 78 | 79 | [npm-image]: https://badge.fury.io/js/generator-dmninteractives.svg 80 | [npm-url]: https://npmjs.org/package/generator-dmninteractives 81 | -------------------------------------------------------------------------------- /generators/graphic-module/templates/src/js/chart.js: -------------------------------------------------------------------------------- 1 | import * as d3 from "d3"; 2 | 3 | 4 | // This is the chart function that will be exported 5 | export default () => ({ 6 | 7 | // Develop the reusable function for you chart in this init function. 8 | // cf. https://bost.ocks.org/mike/chart/ 9 | init: function() { 10 | 11 | // Default chart properties 12 | var stroke = '#eee'; 13 | 14 | // Inner chart function 15 | function chart(selection){ 16 | selection.each(function(data){ 17 | 18 | var bbox = this.getBoundingClientRect(); 19 | var width = bbox.width < bbox.height ? bbox.width : bbox.height; 20 | var height = width; 21 | var t = d3.transition() 22 | .duration(750); 23 | 24 | 25 | // Check if we've already appended our SVG. 26 | var g = d3.select(this).select('svg').select('g').size() === 0 ? 27 | d3.select(this).append("svg") 28 | .attr("width", width) 29 | .attr("height", height) 30 | .style("display", "block") 31 | .style("margin", "auto") 32 | .append("g") : 33 | d3.select(this).select('svg').select('g'); 34 | 35 | var circles = g.selectAll("circle") 36 | .data(data, function(d, i){ return i; }); 37 | 38 | circles.transition(t) 39 | .attr("fill", "#0f516e"); 40 | 41 | circles.enter().append('circle') // Enter 42 | .attr("fill","#FF7216") 43 | .attr("cy", "60") 44 | .attr("stroke", stroke) 45 | .attr('cx',function(d, i){ 46 | function add(a,b){return a + b;} 47 | return data.slice(0, i).reduce(add, 0) + d/2; 48 | }) 49 | .merge(circles) 50 | .transition(t) 51 | .attr('cx',function(d, i){ 52 | function add(a,b){return a + b;} 53 | return data.slice(0, i).reduce(add, 0) + d/2; 54 | }) 55 | .attr("r", function(d){ 56 | return d/2; 57 | }); 58 | 59 | }); 60 | } 61 | 62 | // Getter-setters 63 | chart.stroke = function(_){ 64 | if (!arguments.length) return stroke; 65 | stroke = _; 66 | return chart; 67 | }; 68 | 69 | return chart; 70 | }, 71 | 72 | 73 | // This function actually draws the chart using the 74 | // reusable init function. 75 | draw: function(){ 76 | var chart = this.init() 77 | .stroke(this._stroke); 78 | 79 | d3.select(this._selection) 80 | .datum(this._data) 81 | .call(chart); 82 | }, 83 | 84 | // Call this function to initially create the chart. 85 | create: function(selection, data, stroke){ 86 | this._selection = selection; 87 | this._data = data; 88 | this._stroke = stroke; 89 | 90 | this.draw(); 91 | }, 92 | 93 | // This updates the data and elements. 94 | update: function(data){ 95 | this._data = data; 96 | 97 | this.draw(); 98 | } 99 | }); 100 | -------------------------------------------------------------------------------- /generators/page/templates/gulp/tasks/aws.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable strict */ 2 | 3 | 'use strict'; 4 | 5 | /* eslint-enable strict */ 6 | 7 | const awspublish = require('gulp-awspublish'); 8 | const awspublishRouter = require("gulp-awspublish-router"); 9 | const cloudfront = require('gulp-cloudfront-invalidate-aws-publish'); 10 | const confirm = require('gulp-confirm'); 11 | const deline = require('deline'); 12 | const gulp = require('gulp'); 13 | const gutil = require('gulp-util'); 14 | const rename = require('gulp-rename'); 15 | const S = require('string'); 16 | 17 | 18 | const awsJson = require('./../../aws.json'); 19 | const meta = require('./../../meta.json'); 20 | 21 | 22 | const appName = S(meta.name).slugify().s; 23 | const publisher = awspublish.create(awsJson); 24 | const year = meta.publishYear; 25 | 26 | 27 | const awsDirectory = `${year}/${appName}/`; 28 | 29 | const cfSettings = { 30 | distribution: 'E3QK9W6AW5LAVH', 31 | accessKeyId: awsJson.accessKeyId, 32 | secretAccessKey: awsJson.secretAccessKey, 33 | wait: false, 34 | indexRootPath: true, 35 | }; 36 | 37 | const oneDayInMS = 60 * 60 * 24; 38 | 39 | const routes = { 40 | routes: { 41 | // Cache video and audio for 1 week on user's computer, one month in CloudFront 42 | '^.*\\.(aif|iff|m3u|m4a|mid|mp3|mpa|ra|wav|wma|3g2|3gp|asf|asx|avi|flv|mov|mp4|mpg|rm|swf|vob|wmv)': { 43 | cacheTime: oneDayInMS * 7, 44 | sharedCacheTime: oneDayInMS * 30, 45 | }, 46 | // Cache images 2 days on user's computer, one month in CloudFront 47 | '^.*\\.(jpg|jpeg|svg|bmp|png|tiff|gif)': { 48 | cacheTime: oneDayInMS * 2, 49 | sharedCacheTime: oneDayInMS * 30, 50 | }, 51 | // Cache HTML/CSS/JS 5 minutes on user's computer, one month in CloudFront 52 | '^.*\\.(html|js|css)': { 53 | cacheTime: 60 * 5, 54 | sharedCacheTime: oneDayInMS, 55 | }, 56 | // Catch everything else in an empty route, or we'll get errors 57 | '.*': {}, 58 | }, 59 | }; 60 | 61 | 62 | module.exports = () => 63 | gulp.src('./dist/**/*') 64 | .pipe(confirm({ 65 | question: deline`You're about to publish this project to AWS 66 | under the directory '${awsDirectory}'. 67 | In the process, we'll also wipe out any 68 | uploads to the test directory. 69 | Are you sure you want to do this?`, 70 | input: '_key:y', 71 | })) 72 | .pipe(rename((filePath) => { 73 | // eslint-disable-next-line no-param-reassign 74 | filePath.dirname = awsDirectory + filePath.dirname.replace('.\\', ''); 75 | })) 76 | .pipe(awspublishRouter(routes)) 77 | .pipe(publisher.publish({}, { force: false })) 78 | .pipe(cloudfront(cfSettings)) 79 | .pipe(publisher.cache()) 80 | .pipe(awspublish.reporter()) 81 | .on( 82 | 'end', 83 | gutil.log.bind( 84 | gutil, 85 | // eslint-disable-next-line comma-dangle 86 | `Published at 'http://interactives.dallasnews.com/${awsDirectory}'.` 87 | ) // eslint-disable-line comma-dangle 88 | ); 89 | -------------------------------------------------------------------------------- /generators/common/templates/gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/osx,windows,linux,node,sass,git,video,audio 3 | 4 | ### Audio ### 5 | *.aif 6 | *.iff 7 | *.m3u 8 | *.m4a 9 | *.mid 10 | *.mp3 11 | *.mpa 12 | *.ra 13 | *.wav 14 | *.wma 15 | 16 | ### Git ### 17 | *.orig 18 | 19 | ### Linux ### 20 | *~ 21 | 22 | # temporary files which can be created if a process still has a handle open of a deleted file 23 | .fuse_hidden* 24 | 25 | # KDE directory preferences 26 | .directory 27 | 28 | # Linux trash folder which might appear on any partition or disk 29 | .Trash-* 30 | 31 | # .nfs files are created when an open file is removed but is still being accessed 32 | .nfs* 33 | 34 | ### Node ### 35 | # Logs 36 | logs 37 | *.log 38 | npm-debug.log* 39 | yarn-debug.log* 40 | yarn-error.log* 41 | 42 | # Runtime data 43 | pids 44 | *.pid 45 | *.seed 46 | *.pid.lock 47 | 48 | # Directory for instrumented libs generated by jscoverage/JSCover 49 | lib-cov 50 | 51 | # Coverage directory used by tools like istanbul 52 | coverage 53 | 54 | # nyc test coverage 55 | .nyc_output 56 | 57 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 58 | .grunt 59 | 60 | # Bower dependency directory (https://bower.io/) 61 | bower_components 62 | 63 | # node-waf configuration 64 | .lock-wscript 65 | 66 | # Compiled binary addons (http://nodejs.org/api/addons.html) 67 | build/Release 68 | 69 | # Dependency directories 70 | node_modules/ 71 | jspm_packages/ 72 | 73 | # Typescript v1 declaration files 74 | typings/ 75 | 76 | # Optional npm cache directory 77 | .npm 78 | 79 | # Optional eslint cache 80 | .eslintcache 81 | 82 | # Optional REPL history 83 | .node_repl_history 84 | 85 | # Output of 'npm pack' 86 | *.tgz 87 | 88 | # Yarn Integrity file 89 | .yarn-integrity 90 | 91 | # dotenv environment variables file 92 | .env 93 | 94 | 95 | ### OSX ### 96 | *.DS_Store 97 | .AppleDouble 98 | .LSOverride 99 | 100 | # Icon must end with two \r 101 | Icon 102 | 103 | # Thumbnails 104 | ._* 105 | 106 | # Files that might appear in the root of a volume 107 | .DocumentRevisions-V100 108 | .fseventsd 109 | .Spotlight-V100 110 | .TemporaryItems 111 | .Trashes 112 | .VolumeIcon.icns 113 | .com.apple.timemachine.donotpresent 114 | 115 | # Directories potentially created on remote AFP share 116 | .AppleDB 117 | .AppleDesktop 118 | Network Trash Folder 119 | Temporary Items 120 | .apdisk 121 | 122 | ### Sass ### 123 | .sass-cache/ 124 | *.css.map 125 | 126 | ### Video ### 127 | *.3g2 128 | *.3gp 129 | *.asf 130 | *.asx 131 | *.avi 132 | *.flv 133 | *.mov 134 | *.mp4 135 | *.mpg 136 | *.rm 137 | *.swf 138 | *.vob 139 | *.wmv 140 | 141 | ### Windows ### 142 | # Windows thumbnail cache files 143 | Thumbs.db 144 | ehthumbs.db 145 | ehthumbs_vista.db 146 | 147 | # Folder config file 148 | Desktop.ini 149 | 150 | # Recycle Bin used on file shares 151 | $RECYCLE.BIN/ 152 | 153 | # Windows Installer files 154 | *.cab 155 | *.msi 156 | *.msm 157 | *.msp 158 | 159 | # Windows shortcuts 160 | *.lnk 161 | 162 | # End of https://www.gitignore.io/api/osx,windows,linux,node,sass,git,video,audio 163 | 164 | ### DMN-specific items 165 | 166 | # Secrets 167 | .env 168 | aws.json 169 | 170 | # gulp-awspublish cache 171 | .awspublish* 172 | 173 | # Vendored JavaScript 174 | src/vendor/* 175 | 176 | # Transpiled assets 177 | dist/* 178 | 179 | # Optimized images in src/ 180 | src/images/opt 181 | 182 | # Original media assets 183 | src/assets/* 184 | 185 | # ZIP files 186 | *.zip 187 | 188 | # .gitkeeps (used by some subgenerators) 189 | !src/assets/.gitkeep 190 | -------------------------------------------------------------------------------- /generators/page/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "verboseName": "Interactive page", 3 | "description": "TK", 4 | "priority": 1, 5 | "filesFromRepos": { 6 | "DallasMorningNews/interactives_starterkit": [ 7 | { 8 | "source": "templates/base.html", 9 | "dest": { "path": "./src/templates/" } 10 | }, 11 | { 12 | "source": "templates/index.html", 13 | "dest": { "path": "./src/templates/" } 14 | }, 15 | { 16 | "source": "templates/adblock1.html", 17 | "dest": { "path": "./src/templates/" } 18 | }, 19 | { 20 | "source": "templates/adblock2.html", 21 | "dest": { "path": "./src/templates/" } 22 | }, 23 | { 24 | "source": "templates/adblock3.html", 25 | "dest": { "path": "./src/templates/" } 26 | }, 27 | { 28 | "source": "css/theme.scss", 29 | "dest": { "path": "./src/scss/", "name": "_base.scss" } 30 | }, 31 | { 32 | "source": "css/_mixins.scss", 33 | "dest": { "path": "./src/scss/" } 34 | }, 35 | { 36 | "source": "css/_variables.scss", 37 | "dest": { "path": "./src/scss/" } 38 | }, 39 | { 40 | "source": "css/header.scss", 41 | "dest": { "path": "./src/scss/", "name": "_header.scss" } 42 | }, 43 | { 44 | "source": "css/footer.scss", 45 | "dest": { "path": "./src/scss/", "name": "_footer.scss" } 46 | }, 47 | { 48 | "source": "css/components/_breakouts.scss", 49 | "dest": { "path": "./src/scss/components/", "name": "_breakouts.scss" } 50 | }, 51 | { 52 | "source": "css/components/_charts.scss", 53 | "dest": { "path": "./src/scss/components/", "name": "_charts.scss" } 54 | }, 55 | { 56 | "source": "css/components/_pullquotes.scss", 57 | "dest": { "path": "./src/scss/components/", "name": "_pullquotes.scss" } 58 | }, 59 | { 60 | "source": "css/components/_reporter-bios.scss", 61 | "dest": { "path": "./src/scss/components/", "name": "_reporter-bios.scss" } 62 | }, 63 | { 64 | "source": "css/components/_side-blocks.scss", 65 | "dest": { "path": "./src/scss/components/", "name": "_side-blocks.scss" } 66 | }, 67 | { 68 | "source": "css/components/_slideshows.scss", 69 | "dest": { "path": "./src/scss/components/", "name": "_slideshows.scss" } 70 | }, 71 | { 72 | "source": "css/components/_subscribe.scss", 73 | "dest": { "path": "./src/scss/components/", "name": "_subscribe.scss" } 74 | }, 75 | { 76 | "source": "css/components/_videos.scss", 77 | "dest": { "path": "./src/scss/components/", "name": "_videos.scss" } 78 | }, 79 | { 80 | "source": "js/furniture.js", 81 | "dest": { "path": "./src/js/", "name": "furniture.js" } 82 | }, 83 | { 84 | "source": "js/customES6.js", 85 | "dest": { "path": "./src/js/", "name": "scripts.js" } 86 | }, 87 | { 88 | "source": "js/slideshow.js", 89 | "dest": { "path": "./src/js/", "name": "slideshow.js" } 90 | }, 91 | { 92 | "source": "style-guide/style-guide.md", 93 | "dest": { "path": "./src/style-guide/", "name": "style-guide.md" } 94 | } 95 | ] 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /generators/graphic-module/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const camelize = require('underscore.string/camelize'); 4 | 5 | var yeoman = require('yeoman-generator'), 6 | mkdirp = require('mkdirp'); 7 | 8 | 9 | module.exports = yeoman.Base.extend({ 10 | initializing: function() { 11 | this.composeWith('dmninteractives:linters'); 12 | this.composeWith('dmninteractives:common'); 13 | this.composeWith('dmninteractives:gitsecrets'); 14 | }, 15 | 16 | prompting: function () { 17 | var done = this.async(); 18 | 19 | this.log('Starting up a GRAPHIC MODULE...'); 20 | 21 | var prompts = [{ 22 | name:'appName', 23 | message: 'What\'s your npm project name, e.g., "dmn-chart-scatterplot"?' 24 | },{ 25 | name:'objName', 26 | message: 'What\'s the name of the chart class users will call, e.g., "TexasChoropleth"?' 27 | }]; 28 | 29 | this.prompt(prompts, function (props) { 30 | 31 | var features = props.features; 32 | 33 | function hasFeature(feat) { 34 | return features && features.indexOf(feat) !== -1; 35 | } 36 | 37 | this.objName = camelize(props.objName); 38 | this.appName = props.appName; 39 | done(); 40 | }.bind(this)); 41 | }, 42 | 43 | writing: { 44 | appFiles: function () { 45 | this.fs.copyTpl( 46 | this.templatePath('package.json'), 47 | this.destinationPath('./package.json'), 48 | { appName: this.appName } 49 | ); 50 | this.fs.copyTpl( 51 | this.templatePath('README'), 52 | this.destinationPath('./README.md'), 53 | { 54 | appName: this.appName, 55 | objName: this.objName 56 | } 57 | ); 58 | this.fs.copy( 59 | this.templatePath('gulpfile.js'), 60 | this.destinationPath('./gulpfile.js') 61 | ); 62 | this.fs.copy( 63 | this.templatePath('preview.png'), 64 | this.destinationPath('./preview.png') 65 | ); 66 | }, 67 | 68 | srcFiles: function () { 69 | this.fs.copy( 70 | this.templatePath('src/js/chart.js'), 71 | this.destinationPath('./src/js/chart.js') 72 | ); 73 | this.fs.copyTpl( 74 | this.templatePath('src/js/global-chart.js'), 75 | this.destinationPath('./src/js/global-chart.js'), 76 | { objName: this.objName } 77 | ); 78 | this.fs.copyTpl( 79 | this.templatePath('src/scss/_variables.scss'), 80 | this.destinationPath('./src/scss/_variables.scss'), 81 | { objName: this.objName } 82 | ); 83 | this.fs.copyTpl( 84 | this.templatePath('src/scss/_chart-styles.scss'), 85 | this.destinationPath('./src/scss/_chart-styles.scss'), 86 | { objName: this.objName } 87 | ); 88 | this.fs.copyTpl( 89 | this.templatePath('src/scss/styles.scss'), 90 | this.destinationPath('./src/scss/styles.scss'), 91 | { objName: this.objName } 92 | ); 93 | }, 94 | 95 | gulpFiles: function () { 96 | this.fs.copy( 97 | this.templatePath('gulp/index.js'), 98 | this.destinationPath('./gulp/index.js') 99 | ); 100 | this.fs.copy( 101 | this.templatePath('gulp/tasks/browserify.js'), 102 | this.destinationPath('./gulp/tasks/browserify.js') 103 | ); 104 | this.fs.copy( 105 | this.templatePath('gulp/tasks/sass.js'), 106 | this.destinationPath('./gulp/tasks/sass.js') 107 | ); 108 | this.fs.copy( 109 | this.templatePath('gulp/tasks/server.js'), 110 | this.destinationPath('./gulp/tasks/server.js') 111 | ); 112 | }, 113 | 114 | distFiles: function() { 115 | this.fs.copyTpl( 116 | this.templatePath('dist/index.html'), 117 | this.destinationPath('./dist/index.html'), 118 | { objName: this.objName } 119 | ); 120 | this.fs.copy( 121 | this.templatePath('dist/data/create.json'), 122 | this.destinationPath('./dist/data/create.json') 123 | ); 124 | this.fs.copy( 125 | this.templatePath('dist/data/update.json'), 126 | this.destinationPath('./dist/data/update.json') 127 | ); 128 | mkdirp('./dist/css'); 129 | mkdirp('./dist/js'); 130 | } 131 | }, 132 | 133 | install: function () { 134 | this.installDependencies({ 135 | callback: function() { 136 | this.emit('dependenciesInstalled'); 137 | }.bind(this) 138 | }); 139 | 140 | this.on('dependenciesInstalled', function() { 141 | this.spawnCommand('gulp'); 142 | }); 143 | 144 | }, 145 | 146 | }); 147 | -------------------------------------------------------------------------------- /generators/embeddable-graphic/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const chalk = require('chalk'); 4 | const slugify = require('underscore.string/slugify'); 5 | const yeoman = require('yeoman-generator'); 6 | 7 | const STARTERKIT = 'https://raw.githubusercontent.com/DallasMorningNews/' + 8 | 'interactives_starterkit/master/'; 9 | 10 | 11 | module.exports = yeoman.Base.extend({ 12 | initializing() { 13 | this.composeWith('dmninteractives:linters'); 14 | this.composeWith('dmninteractives:common'); 15 | this.composeWith('dmninteractives:nvm'); 16 | this.composeWith('dmninteractives:gitsecrets'); 17 | }, 18 | 19 | prompting() { 20 | const done = this.async(); 21 | 22 | this.log( 23 | 'Starting up an "Embeddable Graphic". See the generated README.md file for usage info.'); 24 | 25 | const prompts = [{ 26 | type: 'input', 27 | name: 'slug', 28 | message: 'What\'s your graphics\'s slug?', 29 | default: process.cwd().split(path.sep).pop(), 30 | filter: answer => slugify(answer), 31 | }, { 32 | type: 'checkbox', 33 | message: 'Which fonts would you like to include?', 34 | name: 'fonts', 35 | choices: [{ 36 | name: 'PT Serif', 37 | value: 'https://fonts.googleapis.com/css?family=PT+Serif:400,400italic,700,700italic', 38 | }, { 39 | name: 'Montserrat', 40 | value: 'https://fonts.googleapis.com/css?family=Montserrat:400,700', 41 | checked: true, 42 | }, { 43 | name: 'FontAwesome', 44 | value: 'https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css', 45 | }], 46 | }, { 47 | type: 'input', 48 | name: 'year', 49 | message: 'What year will this graphic publish?', 50 | default: () => (new Date()).getFullYear(), 51 | }, { 52 | type: 'input', 53 | name: 'awsAccessKey', 54 | message: 'What\'s your AWS access key?', 55 | store: true, 56 | }, { 57 | type: 'input', 58 | name: 'awsSecretKey', 59 | message: 'What\'s your AWS secret key?', 60 | store: true, 61 | }]; 62 | 63 | this.prompt(prompts, (answers) => { 64 | this.prefs = answers; 65 | this.prefs.projectName = `embed_${answers.slug}`; 66 | 67 | done(); 68 | }); 69 | }, 70 | 71 | writing: { 72 | app() { 73 | this.fs.copyTpl( 74 | this.templatePath('package.json'), 75 | this.destinationPath('./package.json'), 76 | { appName: this.prefs.projectName }); 77 | 78 | this.fs.copy( 79 | this.templatePath('gulpfile.js'), 80 | this.destinationPath('./gulpfile.js')); 81 | 82 | this.fs.copy( 83 | this.templatePath('gulp/**/*.js'), 84 | this.destinationPath('./gulp/')); 85 | }, 86 | 87 | projectfiles() { 88 | const done = this.async(); 89 | 90 | // SCSS 91 | this.fetch(`${STARTERKIT}css/_mixins.scss`, './src/sass/', () => { 92 | this.fetch(`${STARTERKIT}css/_variables.scss`, './src/sass/', done); 93 | }); 94 | 95 | this.fs.copy( 96 | this.templatePath('src/sass/**.scss'), 97 | this.destinationPath('./src/sass/')); 98 | 99 | // JavaScript 100 | this.fs.copy( 101 | this.templatePath('src/js/scripts.js'), 102 | this.destinationPath('./src/js/scripts.js')); 103 | 104 | // HTML 105 | this.fs.copyTpl( 106 | this.templatePath('src/index.html'), 107 | this.destinationPath('./src/index.html'), 108 | { fonts: this.prefs.fonts }); 109 | this.fs.copyTpl( 110 | this.templatePath('src/embed.html'), 111 | this.destinationPath('./src/embed.html'), 112 | { slug: this.prefs.slug, fonts: this.prefs.fonts }); 113 | }, 114 | 115 | aws() { 116 | const awsJson = { 117 | accessKeyId: this.prefs.awsAccessKey, 118 | secretAccessKey: this.prefs.awsSecretKey, 119 | params: { 120 | Bucket: 'interactives.dallasnews.com', 121 | }, 122 | }; 123 | 124 | this.fs.writeJSON('aws.json', awsJson); 125 | }, 126 | 127 | meta() { 128 | const metaJson = { 129 | name: this.prefs.slug, 130 | publishYear: this.prefs.year, 131 | }; 132 | this.fs.writeJSON('meta.json', metaJson); 133 | }, 134 | 135 | git() { 136 | this.fs.copyTpl( 137 | this.templatePath('README.md'), 138 | this.destinationPath('./README.md'), 139 | { slug: this.prefs.slug, year: this.prefs.year }); 140 | }, 141 | }, 142 | 143 | install() { 144 | this.installDependencies({ bower: false }); 145 | }, 146 | 147 | end() { 148 | const done = this.async(); 149 | const buildProcess = this.spawnCommand('gulp', ['build']); 150 | 151 | buildProcess.on('close', () => { 152 | this.log(`\n${chalk.bold('Done!')}`); 153 | this.log(` See the generated ${chalk.yellow('README.md')} for usage info.`); 154 | this.log(` Run ${chalk.magenta('gulp')} to start developing.\n`); 155 | 156 | done(); 157 | }); 158 | }, 159 | 160 | }); 161 | -------------------------------------------------------------------------------- /generators/page/index.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const camelize = require('underscore.string/camelize'); 3 | const chalk = require('chalk'); 4 | const fs = require('fs'); 5 | const github = require('octonode'); 6 | const googleURL = require('google-url-helper'); 7 | const mkdirp = require('mkdirp'); 8 | const path = require('path'); 9 | const slugify = require('underscore.string/slugify'); 10 | const validURL = require('valid-url'); 11 | const yeoman = require('yeoman-generator'); 12 | 13 | 14 | const githubClient = github.client(); 15 | 16 | 17 | module.exports = yeoman.Base.extend({ 18 | initializing() { 19 | this.composeWith('dmninteractives:linters'); 20 | this.composeWith('dmninteractives:common'); 21 | this.composeWith('dmninteractives:nvm'); 22 | this.composeWith('dmninteractives:gitsecrets'); 23 | }, 24 | 25 | prompting() { 26 | const done = this.async(); 27 | 28 | this.log('Starting up an INTERACTIVE PAGE with BROWSERIFY...'); 29 | 30 | this.baseConfig = this.options.baseConfig; 31 | 32 | const prompts = [ 33 | { 34 | name: 'directoryName', 35 | message: 'What\'s your directory name?', 36 | }, 37 | { 38 | name: 'awsAccessKey', 39 | message: 'What\'s your AWS access key?', 40 | }, 41 | { 42 | name: 'awsSecretKey', 43 | message: 'What\'s your AWS secret key?', 44 | }, 45 | { 46 | name: 'hotCopyID', 47 | message: '[Optional] Enter the ID or URL of this page\'s Google Doc copy.', 48 | }, 49 | ]; 50 | 51 | this.prompt(prompts, (props) => { 52 | this.directoryName = slugify(props.directoryName); 53 | this.appName = camelize(props.directoryName); 54 | this.awsAccessKey = props.awsAccessKey; 55 | this.awsSecretKey = props.awsSecretKey; 56 | 57 | if (props.hotCopyID === '') { 58 | this.hotCopyID = null; 59 | } else { 60 | this.hotCopyID = ( 61 | validURL.isUri(props.hotCopyID) 62 | ) ? ( 63 | googleURL.parseId(props.hotCopyID) 64 | ) : ( 65 | props.hotCopyID 66 | ); 67 | } 68 | 69 | done(); 70 | }); 71 | }, 72 | 73 | writing: { 74 | app() { 75 | this.fs.copyTpl( 76 | this.templatePath('package.json'), 77 | this.destinationPath('./package.json'), 78 | { appName: this.appName } // eslint-disable-line comma-dangle 79 | ); 80 | }, 81 | 82 | gulpfiles() { 83 | this.fs.copy( 84 | this.templatePath('gulpfile.js'), 85 | this.destinationPath('./gulpfile.js') // eslint-disable-line comma-dangle 86 | ); 87 | 88 | this.fs.copy( 89 | this.templatePath('./gulp/**/*'), 90 | this.destinationPath('./gulp/') // eslint-disable-line comma-dangle 91 | ); 92 | }, 93 | 94 | projectfiles() { 95 | // --------------------------------------- 96 | // Fetch remote template files from github 97 | // hosted in interactives_starterkit 98 | if (!_.isUndefined(this.baseConfig.filesFromRepos)) { 99 | _.each(this.baseConfig.filesFromRepos, (repoFiles, repoName) => { 100 | const repo = githubClient.repo(repoName); 101 | 102 | _.each(repoFiles, (file) => { 103 | const destFilePath = ( 104 | _.has(file.dest, 'name') 105 | ) ? ( 106 | path.join(file.dest.path, file.dest.name) 107 | ) : ( 108 | path.join(file.dest.path, path.basename(file.source)) 109 | ); 110 | 111 | mkdirp(path.dirname(destFilePath)); 112 | 113 | repo.contents(file.source, (error, contents) => { 114 | const fileContents = new Buffer(contents.content, 'base64'); 115 | 116 | fs.writeFileSync(destFilePath, fileContents); 117 | }); 118 | }); 119 | }); 120 | } 121 | 122 | // --------------------------- 123 | // Copy rest of template files 124 | 125 | this.fs.copy( 126 | this.templatePath('styles.scss'), 127 | // eslint-disable-next-line comma-dangle 128 | this.destinationPath('./src/scss/styles.scss') 129 | ); 130 | 131 | this.fs.copy( 132 | this.templatePath('data.json'), 133 | // eslint-disable-next-line comma-dangle 134 | this.destinationPath('./src/data/data.json') 135 | ); 136 | 137 | this.fs.copy( 138 | this.templatePath('img.html'), 139 | // eslint-disable-next-line comma-dangle 140 | this.destinationPath('./src/templates/partials/img.html') 141 | ); 142 | 143 | this.fs.copy( 144 | this.templatePath('defaultImage.jpg'), 145 | // eslint-disable-next-line comma-dangle 146 | this.destinationPath('./src/images/_defaultImage.jpg') 147 | ); 148 | 149 | this.fs.copy( 150 | this.templatePath('buttonLeft.svg'), 151 | // eslint-disable-next-line comma-dangle 152 | this.destinationPath('./src/images/buttonLeft.svg') 153 | ); 154 | 155 | this.fs.copy( 156 | this.templatePath('buttonRight.svg'), 157 | // eslint-disable-next-line comma-dangle 158 | this.destinationPath('./src/images/buttonRight.svg') 159 | ); 160 | 161 | this.fs.copyTpl( 162 | this.templatePath('README.md'), 163 | this.destinationPath('./README.md'), 164 | // eslint-disable-next-line comma-dangle 165 | { slug: this.directoryName, year: (new Date()).getFullYear() } 166 | ); 167 | 168 | // ------------------------- 169 | // Create output directories 170 | mkdirp('./dist'); 171 | }, 172 | 173 | aws() { 174 | const awsJson = { 175 | accessKeyId: this.awsAccessKey, 176 | secretAccessKey: this.awsSecretKey, 177 | params: { 178 | Bucket: 'interactives.dallasnews.com', 179 | }, 180 | }; 181 | 182 | this.fs.writeJSON('aws.json', awsJson); 183 | }, 184 | 185 | meta() { 186 | const timestamp = new Date(); 187 | const rawMonth = (timestamp.getMonth() + 1).toString(); 188 | const rawDate = timestamp.getDate().toString(); 189 | 190 | const month = rawMonth.length === 1 ? `0${rawMonth}` : rawMonth; 191 | const date = rawDate.length === 1 ? `0${rawDate}` : rawDate; 192 | 193 | const defaultKeywords = [ 194 | 'interactives', 'dallas', 'dallas news', 'dfw news', 'dallas newspaper', 195 | 'dallas morning news', 'dallas morning news newspaper', 196 | ]; // Archie-able 197 | const metaJson = { 198 | id: (Math.floor(Math.random() * 100000000000) + 1).toString(), 199 | name: this.directoryName, 200 | pageTitle: '', // Archie-able 201 | shareTitle: '<Title>', // Archie-able 202 | shareText: '<Text>', // Archie-able 203 | tweetText: '<Text>', // Archie-able 204 | publishYear: timestamp.getFullYear(), 205 | publishDate: `${ 206 | timestamp.getFullYear() 207 | }-${ 208 | month 209 | }-${ 210 | date 211 | }T00:00:00Z`, 212 | url: `https://interactives.dallasnews.com/${timestamp.getFullYear()}/${this.directoryName}/`, 213 | authors: ['<Author1>', '<Author2>'], // Archie-able 214 | desk: '<Desk>', // Archie-able 215 | section: '<Section>', // Archie-able 216 | keywords: defaultKeywords, // Archie-able 217 | imgURL: `https://interactives.dallasnews.com/${ 218 | timestamp.getFullYear() 219 | }/${ 220 | this.appName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() 221 | }/images/${ 222 | this.shareImage 223 | }`, 224 | imgWidth: '<Width - w/out "px">', 225 | imgHeight: '<Height - w/out "px">', 226 | sectionTwitter: '<handle - w/out "@">', // Archie-able 227 | authorTwitter: '<handle - w/out "@">', // Archie-able 228 | authorFBProfile: '<Url for author FB profile. Leave empty string if none>', 229 | }; 230 | this.fs.writeJSON('meta.json', metaJson); 231 | }, 232 | }, 233 | 234 | install() { 235 | this.installDependencies({ 236 | bower: false, 237 | // eslint-disable-next-line comma-dangle 238 | callback: () => { this.emit('dependenciesInstalled'); } 239 | }); 240 | 241 | // this.on('dependenciesInstalled', () => { 242 | // this.spawnCommand('gulp', ['img']); 243 | // this.spawnCommand('gulp'); 244 | // }); 245 | }, 246 | 247 | end() { 248 | const done = this.async(); 249 | const imageProcess = this.spawnCommand('gulp', ['img']); 250 | 251 | imageProcess.on('close', () => { 252 | const buildProcess = this.spawnCommand('gulp', ['build']); 253 | 254 | buildProcess.on('close', () => { 255 | this.log(`\n${chalk.bold('Done!')}`); 256 | this.log(` See the generated ${chalk.yellow('README.md')} for usage info.`); 257 | this.log(` Run ${chalk.magenta('gulp')} to start developing.\n`); 258 | 259 | done(); 260 | }); 261 | }); 262 | }, 263 | 264 | }); 265 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/) from v0.5.0 forward. 6 | 7 | ## [Unreleased] 8 | ### Fixed 9 | - Version bump [lodash](https://lodash.com/) to `^4.17.11` to mitigate [CVE-2018-16487](https://nvd.nist.gov/vuln/detail/CVE-2018-16487) 10 | 11 | ## [0.8.9] - 2020-07-20 12 | ### Changed 13 | - Added reporter-bios.scss to config.json 14 | 15 | ## [0.8.8] - 2019-11-18 16 | ### Changed 17 | - Changed default Montserrat weight for interactive embeds from 300 to 400 18 | - Bump esLint to @4 for interactive pages 19 | 20 | ## [0.8.7] - 2019-01-22 21 | ### Changed 22 | - Pin `event-stream` dependency to 3.3.4 in generated projects due to [security vulnerability](https://github.com/dominictarr/event-stream/issues/116) 23 | - Bump `underscore-string`, per recommendation from `npm audit` 24 | 25 | ## [0.8.6] - 2018-11-27 26 | ### Changed 27 | - Changes `/graphic-module/templates/package.json` event-stream to <=3.3.4 to address security vulnerability 28 | 29 | ## [0.8.5] - 2018-10-11 30 | ### Added 31 | - Adds call to new 'slideshow.js' file in starterkit to page/config.json 32 | - Adds call to new 'charts.scss' and 'subscribe.scss' files in starterkit to page/config.json 33 | - Adds call to new 'styleguide.md' file in starterkit to page/config.json 34 | ### Removed 35 | - Removed some calls to outdated scss files in page/config.json 36 | 37 | 38 | ## [0.8.4] - 2018-10-09 39 | ### Fixed 40 | - Include `/images/` in URL for share image in meta.json 41 | - Also invalidate root paths (`year/slug/`) in Cloudfront [#67](https://github.com/DallasMorningNews/generator-dmninteractives/issues/67) 42 | 43 | ## [0.8.3] - 2018-08-21 44 | ### Fixed 45 | - Fixed an error where files that didn't match a ["route"](https://github.com/DallasMorningNews/generator-dmninteractives/blob/b84e0bbe16f70f7ef469fd1a010df26f9759aad6/generators/page/templates/gulp/tasks/aws.js#L38-L56) pattern in our AWS publishing stream would throw an error 46 | 47 | ## [0.8.2] - 2018-08-2 48 | ### Changed 49 | - Only include 300 and 700 weight Monsterrat 50 | 51 | ## [0.8.1] - 2018-07-26 52 | ### Changed 53 | - Changes base sans-serif font to Montserrat from Gotham. 54 | 55 | ## [0.8.0] - 2018-07-26 56 | ### Added 57 | - All generators now offer to create a new repo with [`git-secrets`](https://github.com/awslabs/git-secrets) protection 58 | - Auto-create an [.nvmrc file](https://github.com/creationix/nvm#nvmrc) to store Node version at time of project creation 59 | 60 | ### Changed 61 | - Drop `string` module due to [CVE-2017-16116](https://nvd.nist.gov/vuln/detail/CVE-2017-16116) and replace with `underscore.string` 62 | 63 | ### Fixed 64 | - Bumped `node-sass` version, switched to [SCSS version](https://github.com/JohnAlbin/normalize-scss) of Normalize.css and added [Eyeglass](https://github.com/sass-eyeglass/eyeglass) support due to pending deprecation of CSS imports in `node-sass` [#65](https://github.com/DallasMorningNews/generator-dmninteractives/issues/65) 65 | 66 | ## [0.7.7] - 2018-07-23 67 | ### Added 68 | - Invalidate Cloudfront for published objects during publish [#59](https://github.com/DallasMorningNews/generator-dmninteractives/issues/59) 69 | - Add cache headers to images and videos [#59](https://github.com/DallasMorningNews/generator-dmninteractives/issues/59) 70 | 71 | ### Changed 72 | - Publishes test content as private to new password-protected test bucket 73 | - Enabled grid support to autoprefixer for pages and embeddable graphics. 74 | 75 | ### Fixed 76 | - Also watch SVGs in `src/` and rerun the Nunjucks renderer when they change [#58](https://github.com/DallasMorningNews/generator-dmninteractives/issues/58) 77 | - Update `node-mime` due to [CVE-2017-16138](https://nvd.nist.gov/vuln/detail/CVE-2017-16138) 78 | 79 | ## [0.7.6] - 2018-04-30 80 | ### Fixed 81 | - Bump BrowserSync version in generated apps to close [vulnerability](https://github.com/BrowserSync/browser-sync/issues/1546) in `localtunnel` dependency, which relies on a vulnerable version of `hoek` 82 | - Bump `octonode` version in this repo to fix a similar dependency on `hoek` 83 | - Repair CHANGELOG 84 | 85 | ## [0.7.5] - 2018-03-01 86 | ### Changed 87 | - Updates font weights for new font stack 88 | 89 | ## [0.7.4] - 2018-02-26 90 | ### Changed 91 | - Switches typography.com call to DMN house account 92 | - Pin frontend dependencies (jQuery, etc.) at major version so we get the latest and greatest 93 | - Include [package-lock.json](package-lock.json) file in VC 94 | 95 | ## [0.7.3] - 2018-02-14 96 | ### Fixed 97 | - Correctly prepends leading `0` in dates in meta.json 98 | 99 | ## [0.7.2] - 2017-12-27 100 | ### Fixed 101 | - Resolves correct version number for npm and github 102 | 103 | ## [0.7.1] - 2017-12-27 104 | ### Fixed 105 | - Corrected esLintConfig parser in linters/index.js 106 | 107 | ## [0.7.0] - 2017-12-12 108 | ### Added 109 | - There's now a [`.browserslistrc` config file](https://github.com/ai/browserslist), which specifies the browsers we support (for now, anything with greater than 2% usage within the US or that is one of the last major versions). JS transpilers and CSS post-processors will now reference this file. 110 | - Transpiled CSS is now auto-prefixed using [Autoprefixer](https://github.com/postcss/autoprefixer)'s [PostCSS](https://github.com/postcss/postcss) plugin to be compatible with our list of supported browsers (see above). 111 | 112 | ### Changed 113 | - Switch from `babel-preset-es2015` to its replacement, [`babel-preset-env`](https://github.com/babel/babel/tree/master/packages/babel-preset-env) 114 | - Bumped the 3.x version of `gulp-sass` for the page generator (the embeddable already had it). It `npm install`s much, much quicker than the 2.x version. 115 | - Deprecate the non-Browserify-ed, original interactives generator. RIP. 116 | - All output package.json files now specify `UNLICENSED` instead of `ISC` in the license field and are marked private 117 | - Clarified language about what is tracked by `git` and how the assets and data folders and handled 118 | 119 | ### Fixed 120 | - Render HTML and copy all static assets during initial build (see #56) 121 | - Fix for error during template rendering in page generator caused by meta.json author property being a string instead of an array 122 | 123 | ## [0.6.3] - 2017-12-11 124 | ### Changed 125 | - Update Parse.ly tags to newer JSON+LD format 126 | 127 | ## [0.6.2] - 2017-09-15 128 | ### Changed 129 | - Fine-tunes styles from 0.6.1. 130 | 131 | ## [0.6.1] - 2017-09-15 132 | ### Added 133 | - Add `_ooyala.scss` sass file for controlling ooyala styles. 134 | - Add base styles for `p` and `a` tags, along with `.source` and `.credit` lines 135 | 136 | ### Changed 137 | - More semantic class names on the embeddable container, chatter and body class. 138 | 139 | ## [0.6.0] - 2017-07-28 140 | ### Added 141 | - Add a README file to Browserify-ed projects (much like the embeddable one) 142 | - Soft-pin (`^`) the versions for `eslint-config-airbnb` and its peer dependencies because installing the latest version of it and its peers often results in ESlint errors (see #53) 143 | 144 | ### Changed 145 | - Use a single, universal `.gitignore` file that properly excludes various system files, video and audio 146 | - Changes the default project structure, creating separate directories for original media assets, data, etc. 147 | - Embeddables now follow the same directory structure as interactives, housing all relevant files in the `src` directory 148 | - Don't track video, audio and ZIP files in `git` 149 | 150 | ### Fixed 151 | - URLs in the meta.json are now https 152 | 153 | ## [0.5.2] - 2017-06-21 154 | ### Changed 155 | - Provided ESLint config now sets the environment to browser so `window`, `document`, etc. shouldn't cause warnings anymore 156 | 157 | ### Fixed 158 | - Pins ESlint at version 3 because our configs aren't yet 4.x.x-compatible 159 | 160 | ## [0.5.1] - 2017-04-13 161 | ### Changed 162 | - Gulp templates in `page-browserify` generator now have `'use strict'` declarations, which enable backward-compatibility with older Node versions on users' systems. 163 | - The 'page-browserify' subgenerator now knows to copy 'furniture.js' and 'components/\*.scss' files from the `DallasMorningNews/interactives_starterkit` repo. This reflects a refactoring of the files within `interactives_starterkit`. 164 | 165 | ## [0.5.0] - 2017-04-05 166 | ### Changed 167 | - New system for configuring sub-generators' names, descriptions and ordering in the "What would you like to make today?" prompt. See `./generators/page-browserify/config.json` for an example, and `./generators/app/index.js` for implementation. 168 | - New system for retrieving needed JS, CSS and HTML files from `DallasMorningNews/interactives_starterkit` repo for `page-browserify` generator projects. See `./generators/page-browserify/config.json` for example and `./generators/page-browserify/index.js` for implementation. 169 | 170 | ### Added 171 | - `page-browserify` generator for ES6-enabled interactive pages. 172 | - NPM installability for `page-browserify` projects' dependencies. 173 | - Google Analytics tracking script for `embeddable-graphic` projects. 174 | 175 | ## [0.4.2] - 2017-03-28 176 | ### Changed 177 | - `page` generator now copies over component scss files via `index.js`. 178 | 179 | ## [0.4.1] - 2017-03-21 180 | ### Added 181 | - Base styles and default chatter structure for `embeddable-graphic` generator. 182 | 183 | ### Changed 184 | - `page` generator now uses correct syntax for `authorFBProfile` tag in `meta.json`. 185 | 186 | ### Fixed 187 | - Resolved an errant `body *:after` selector in `embeddable-graphic` styles. 188 | 189 | ## [0.4.0] - 2017-02-10 190 | ### Added 191 | - `page` generator now has Facebook author and publisher tags in `meta.json`. 192 | 193 | ## [0.3.9] - 2017-02-09 194 | ### Fixed 195 | - New release number for distribution. 196 | 197 | ## [0.3.8] - 2017-02-03 198 | ### Added 199 | - New generator: `embeddable-graphic` for graphics that get placed within Serif pages. 200 | 201 | ## [0.3.7] - 2017-01-31 202 | ### Changed 203 | - `graphic` generator now calls header and footer CSS separately (as other generators do). 204 | 205 | ## [0.3.6] - 2017-01-31 206 | ### Changed 207 | - `page` generator now calls header and footer CSS (and furniture JS) from separate files. 208 | 209 | ## [0.3.5] - 2016-10-27 210 | ### Changed 211 | - Update instructions for placing multiple authors' names in `page` generator's `meta.json` file. 212 | 213 | ## [0.3.4] - 2016-10-23 214 | ### Changed 215 | - `graphic` generator now uses the supplied JS class name in the generated README, `package.json` and CSS files. 216 | - Added new SCSS styles to `graphic` generator, and removed default Chartwerk styles for fewer assumptions. 217 | 218 | ## [0.3.3] - 2016-10-22 219 | ### Added 220 | - `graphic` generator now has explicit babelify transform properties. 221 | 222 | ## [0.3.2] - 2016-10-22 223 | ### Added 224 | - New generator: `graphic` for reusable graphics; created in the context of 2016 general election coverage and useful in many other scenarios. (Note: a `graphic` generator nominally existed prior to this version, but was incomplete and mostly not usable.) 225 | 226 | ## [0.3.1] - 2016-09-19 227 | ### Changed 228 | - `page` generator now includes `build/static/images/opt` directory in `.gitignore`. 229 | - `page` generator now _excludes_ `build/static/vendor` directory from `.gitignore` (so the directory is uploaded to git), but ignores all files within that directory except a `.gitkeep` helper file to prevent vendored (that is, bower-installed) code from being uploaded to git. 230 | - `page` generator now creates a `.gitkeep` file in `build/static/vendor`, to enable the behavior described above. 231 | 232 | ## [0.3.0] - 2016-09-12 233 | ### Changed 234 | - `page` and `graphic` generators no longer upload ZIP files of the entire project to S3 when publishing. (We'll store the project code via Git instead from here on.) 235 | 236 | 237 | ## [0.2.5] - 2016-08-13 238 | **Description to be added** 239 | 240 | ## [0.2.4] - 2016-05-27 241 | **Description to be added** 242 | 243 | ## [0.2.3] - 2016-04-12 244 | **Description to be added** 245 | 246 | ## [0.2.2] - 2016-04-08 247 | **Description to be added** 248 | 249 | ## [0.2.1] - 2016-04-08 250 | **Description to be added** 251 | 252 | ## [0.2.0] - 2016-04-06 253 | **Description to be added** 254 | 255 | ## [0.1.3] - 2016-01-31 256 | **Description to be added** 257 | 258 | ## [0.1.2] - 2016-01-05 259 | **Description to be added** 260 | 261 | ## [0.1.1] - 2016-01-05 262 | **Description to be added** 263 | 264 | ## [0.1.0] - 2016-12-15 265 | **Description to be added** 266 | 267 | ## [0.0.9] - 2015-12-10 268 | **Description to be added** 269 | 270 | ## [0.0.8] - 2015-11-17 271 | **Description to be added** 272 | 273 | ## [0.0.7] - 2015-11-09 274 | **Description to be added** 275 | 276 | ## [0.0.6] - 2015-11-05 277 | **Description to be added** 278 | 279 | ## [0.0.5] - 2015-10-27 280 | **Description to be added** 281 | 282 | ## [0.0.4] - 2015-10-25 283 | **Description to be added** 284 | 285 | ## [0.0.3] - 2015-10-24 286 | **Description to be added** 287 | 288 | ## [0.0.2] - 2015-10-24 289 | **Description to be added** 290 | 291 | ## 0.0.1 - 2015-10-12 292 | ### Added 293 | - Initial working versions of files. 294 | 295 | [Unreleased]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.8...HEAD 296 | [0.8.9]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.8...v0.8.9 297 | [0.8.8]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.7...v0.8.8 298 | [0.8.7]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.6...v0.8.7 299 | [0.8.6]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.5...v0.8.6 300 | [0.8.5]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.4...v0.8.5 301 | [0.8.4]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.3...v0.8.4 302 | [0.8.3]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.2...v0.8.3 303 | [0.8.2]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.1...v0.8.2 304 | [0.8.1]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.8.0...v0.8.1 305 | [0.8.0]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.7.7...v0.8.0 306 | [0.7.7]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.7.6...v0.7.7 307 | [0.7.6]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.7.5...v0.7.6 308 | [0.7.5]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.7.4...v0.7.5 309 | [0.7.4]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.7.3...v0.7.4 310 | [0.7.3]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.7.2...v0.7.3 311 | [0.7.2]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.7.1...v0.7.2 312 | [0.7.1]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.7.0...v0.7.1 313 | [0.7.0]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.6.3...v0.7.0 314 | [0.6.3]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.6.2...v0.6.3 315 | [0.6.2]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.6.1...v0.6.2 316 | [0.6.1]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.6.0...v0.6.1 317 | [0.6.0]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.5.2...v0.6.0 318 | [0.5.2]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.5.1...v0.5.2 319 | [0.5.1]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.5.0...v0.5.1 320 | [0.5.0]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.4.2...v0.5.0 321 | [0.4.2]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.4.1...v0.4.2 322 | [0.4.1]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.4.0...v0.4.1 323 | [0.4.0]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.3.9...v0.4.0 324 | [0.3.9]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.3.7...v0.3.9 325 | [0.3.7]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.3.6...v0.3.7 326 | [0.3.6]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.3.4...v0.3.6 327 | [0.3.4]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.3.3...v0.3.4 328 | [0.3.3]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.3.2...v0.3.3 329 | [0.3.2]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.3.1...v0.3.2 330 | [0.3.1]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.3.0...v0.3.1 331 | [0.3.0]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.2.5...v0.3.0 332 | [0.2.5]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.2.4...v0.2.5 333 | [0.2.4]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.2.3...v0.2.4 334 | [0.2.3]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.2.2...v0.2.3 335 | [0.2.2]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.2.1...v0.2.2 336 | [0.2.1]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.2.0...v0.2.1 337 | [0.2.0]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.1.3...v0.2.0 338 | [0.1.3]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.1.2...v0.1.3 339 | [0.1.2]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.1.1...v0.1.2 340 | [0.1.1]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.1.0...v0.1.1 341 | [0.1.0]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.0.9...v0.1.0 342 | [0.0.9]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.0.8...v0.0.9 343 | [0.0.8]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.0.7...v0.0.8 344 | [0.0.7]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.0.6...v0.0.7 345 | [0.0.6]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.0.5...v0.0.6 346 | [0.0.5]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.0.4...v0.0.5 347 | [0.0.4]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.0.3...v0.0.4 348 | [0.0.3]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.0.2...v0.0.3 349 | [0.0.2]: https://github.com/DallasMorningNews/generator-dmninteractives/compare/v0.0.1...v0.0.2 350 | --------------------------------------------------------------------------------