#! /usr/bin/env node
const collectImages = require('./funcs/collectImages');
const toWebTitle = require('./funcs/toWebTitle');
const fs = require('fs-extra'); // get fileSystem
const concatStream = require('concat-stream'); //put a streaming chunked file into one glob
const q = require('q'); //so we can defer
const inquirer = require('inquirer'); //so we can ask questions
const openFile = require('open'); //so we can open files
const vinylFs = require('vinyl-fs');
const stripTags = require('striptags');
const vinylFtp = require('vinyl-ftp');
const removeMD = require('remove-markdown');
const entities = require('entities');
const ssmlVal = require('ssml-validator');
const typeset = require('typeset');
const { replaceQuotes } = require('curly-q');
const shell = require('shelljs');
const bloomfile = './bloom.json';
declare namespace NodeJS {
interface Global extends Bloom {}
}
interface Bloomfile {
title: string;
author: string;
subtitle: string;
words: boolean;
alphabetical: boolean;
hideIncomplete: boolean;
sequential: boolean;
ssml: boolean;
mp3: boolean;
ftp: boolean;
googleAnalyticsID: string;
}
interface UploadOptions {
hostname: string;
username: string;
password: string;
remotePath: string;
}
interface Bloom extends Bloomfile, UploadOptions {
headerString: string;
headerMarkup: string;
projectTitle: string;
projectSubtitle: string;
projectAuthor: string;
sequentialLinks: boolean;
showWords: boolean;
googleAnalyticsScript: string;
bloomFileSettings: Bloomfile;
useBloomFile: boolean;
makeBloomFile: boolean;
}
global.headerString = '';
global.headerMarkup = '';
global.projectTitle = '';
global.projectSubtitle = '';
global.projectAuthor = '';
global.sequentialLinks = false;
global.showWords = false;
global.hideIncomplete = true;
global.alphabetical = false;
global.ftp = false;
global.hostname = '';
global.username = '';
global.password = '';
global.remotePath = '';
global.ssml = false;
global.mp3 = false;
global.googleAnalyticsID = '';
global.googleAnalyticsScript = ``;
global.bloomFileSettings = {} as Bloomfile;
global.useBloomFile = false;
global.makeBloomFile = false;
const userArgs = process.argv.slice(2);
const coverFileLocation = 'images/cover.jpg';
let fileToBloomFrom = userArgs[0];
if (!fileToBloomFrom) {
fileToBloomFrom = 'index.html'; //assume it's index.html if they didn't provide a file
}
const isThereABloomFile = fs.existsSync(bloomfile);
if (isThereABloomFile) {
fs.readJson(bloomfile, (error, data) => {
if (error) {
console.log(error);
}
Object.assign(global.bloomFileSettings, data);
});
}
const inputFile = fs.createReadStream(fileToBloomFrom);
const headerFileExists = fs.existsSync(__dirname + '/header.html');
let headerFile = '';
if (headerFileExists) {
headerFile = fs.createReadStream(__dirname + '/header.html');
}
const cssFileExists = fs.existsSync('css/style.css');
let cssFile;
if (cssFileExists) {
cssFile = fs.createReadStream('css/style.css');
}
const indexStyles = ``;
const coverImageExists = fs.existsSync(coverFileLocation);
const outDirName = fileToBloomFrom.replace('.html', '') + '-bloomed';
const imageFolderExists = fs.existsSync(`./${outDirName}/images`);
let coverImage;
function makeFileAString(file) {
// make a new deferred object so we can chain
const deferred = q.defer();
file.pipe(
concatStream((data) => {
deferred.resolve(data.toString());
})
);
return deferred.promise;
}
function answersCallback(answers: Bloomfile & { makeBloomFile?: true }) {
global.alphabetical = answers.alphabetical;
global.projectTitle = answers.title;
global.projectSubtitle = answers.subtitle;
global.projectAuthor = answers.author;
global.mp3 = answers.mp3;
global.googleAnalyticsID = answers.googleAnalyticsID;
global.showWords = answers.words;
global.ssml = answers.ssml;
global.sequentialLinks = answers.sequential;
global.makeBloomFile = answers.makeBloomFile;
global.ftp = answers.ftp;
global.hideIncomplete = answers.hideIncomplete;
global.headerMarkup = global.headerString.replace(
'
',
`${answers.title}`
);
if (answers.ftp) {
inquirer
.prompt([
{
name: 'hostname',
message:
'Enter your hostname (exclude ftp:// or www prefixes)'
},
{
name: 'username',
message: 'Enter your username for that host'
},
{
name: 'password',
type: 'password',
message: 'Enter your password for that host'
},
{
name: 'remotePath',
message:
"Type a remote directory you'd like to bloom into (ex: html/project)"
}
])
.then((moreAnswers: UploadOptions) => {
global.hostname = moreAnswers.hostname;
global.username = moreAnswers.username;
global.password = moreAnswers.password;
global.remotePath = moreAnswers.remotePath;
runProgram();
});
} else {
runProgram();
}
}
function askTheQuestions() {
inquirer
.prompt([
{
name: 'title',
message: 'What title should appear on the index page?'
},
{
name: 'subtitle',
message: 'What subtitle should appear below the title?'
},
{
name: 'author',
message: 'Author name, if any?'
},
{
type: 'confirm',
name: 'words',
default: false,
message: 'Show word counts next to index links?'
},
{
type: 'confirm',
name: 'alphabetical',
default: false,
message: 'Should the links on the index page be alphabetized?'
},
{
type: 'confirm',
name: 'sequential',
default: false,
message:
'Should each page link to the next page instead of back to index page?'
},
{
type: 'confirm',
name: 'hideIncomplete',
default: true,
message: 'Hide incomplete stories with % in the title?'
},
{
type: 'confirm',
name: 'ssml',
default: false,
message:
'Generate a folder of SSML for using with Amazon Polly?'
},
{
type: 'confirm',
name: 'mp3',
default: false,
message:
'Use SSML to make MP3s and add an audio player to each page?'
},
{
name: 'googleAnalyticsID',
type: 'input',
default: '',
message:
'Google Analytics ID (if Bloom should add a script on every page).'
},
{
type: 'confirm',
name: 'makeBloomFile',
default: false,
message:
'Create/overwrite bloom.json file in this folder, with these answers?'
},
{
type: 'confirm',
name: 'ftp',
default: false,
message:
'Bloom can upload your files for you, if you provide FTP details. Yeah?'
}
])
.then((answers) => {
answersCallback(answers);
});
}
if (isThereABloomFile) {
inquirer
.prompt([
{
name: 'useBloomFile',
message: "Use the settings in this folder's bloomfile?",
type: 'confirm',
default: true
}
])
.then((answer: { useBloomFile: boolean }) => {
global.useBloomFile = answer.useBloomFile;
if (answer.useBloomFile) {
answersCallback(global.bloomFileSettings);
} else {
askTheQuestions();
}
});
} else {
askTheQuestions();
}
function generateBloomFile() {
const file = bloomfile;
const obj: Bloomfile = {
title: global.projectTitle,
author: global.projectAuthor,
subtitle: global.projectSubtitle,
words: global.showWords,
alphabetical: global.alphabetical,
hideIncomplete: global.hideIncomplete,
sequential: global.sequentialLinks,
ssml: global.ssml,
mp3: global.mp3,
ftp: global.ftp,
googleAnalyticsID: global.googleAnalyticsID
};
fs.writeJson(file, obj, { spaces: 2 }, (err) => {
if (err) {
console.log(err);
}
});
}
function numberWithCommas(x) {
if (!x) {
return '';
}
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
}
//PROGRAM STARTS
//make a folder
fs.ensureDir('./' + outDirName, (err) => {
console.log(err);
});
let jsonOut = { indexFile: { fileContents: '', projectAuthor: '' }, pages: [] };
if (headerFileExists) {
makeFileAString(headerFile).then((data) => {
global.headerString = data;
});
} else {
console.log('There is no header file');
}
if (cssFileExists) {
makeFileAString(cssFile).then((data) => {
fs.outputFile(outDirName + '/style.css', data, (err) => {
console.log(err);
});
});
}
function runProgram() {
if (global.makeBloomFile) {
generateBloomFile(); // before anything else, generate a bloomfile if we're supposed to
}
if (!imageFolderExists) {
fs.ensureDir(`./${outDirName}/images`, (err) => {
console.log(err);
});
}
if (global.ssml) {
fs.ensureDir(`./${outDirName}/ssml`, (err) => {
console.log(err);
});
}
makeFileAString(inputFile).then((data) => {
const splitter = ''; // we could have this be a prompted answer
const images = collectImages(data);
if (images !== null) {
images.forEach((img) => {
fs.copy('./' + img, outDirName + '/' + img, (err) => {
console.log(err);
});
});
}
const textArray = data.split(splitter);
const includeInIndex = '';
if (coverImageExists) {
fs.createReadStream(coverFileLocation).pipe(
fs.createWriteStream(outDirName + '/' + coverFileLocation)
);
coverImage = `
`;
} else {
coverImage = '';
}
const datetime = new Date();
const commentDate =
'';
let indexStr = commentDate + '';
const titleArray = [];
const webTitleArray = [];
const analytics =
global.googleAnalyticsID !== ''
? global.googleAnalyticsScript.replace(
'bloom-googleAnalyticsID',
global.googleAnalyticsID
)
: '';
for (let i = 0; i < textArray.length; i++) {
interface PageObject {
title: string;
lastTitle: string;
nextTitle: string;
webTitle: string;
nextWebTitle: string;
lastWebTitle: string;
nextButton: string;
backButton: string;
audioPlayer: string;
fileContents: string;
wordCount: number;
ssmlFile: string;
mp3File: string;
htmlFile: string;
}
let pageObject = {} as PageObject;
const next = i + 1;
const last = i - 1;
let title = i > 0 ? textArray[i].split('
')[0] : 'index';
let hideThis = false;
let nextTitle =
next < textArray.length
? textArray[next].split('')[0]
: 'index';
let lastTitle = last > 0 ? textArray[last].split('')[0] : '';
if (title.indexOf('%') > -1) {
if (!global.hideIncomplete) {
title = title.replace('%', '');
} else {
hideThis = true;
}
}
title = stripTags(title); //strip tags
lastTitle = stripTags(lastTitle);
nextTitle = stripTags(nextTitle);
title = title.replace(new RegExp('"', 'g'), '');
lastTitle = lastTitle.replace(new RegExp('"', 'g'), '');
nextTitle = nextTitle.replace(new RegExp('"', 'g'), '');
const webTitle = toWebTitle(title, textArray[i]);
const lastWebTitle =
lastTitle !== '' ? toWebTitle(lastTitle, textArray[last]) : '';
const nextWebTitle =
nextTitle !== 'index'
? toWebTitle(nextTitle, textArray[next])
: '';
if (title !== 'index') {
indexStr = hideThis
? indexStr
: indexStr +
`${title}`;
}
const backButtonMarkup =
global.sequentialLinks && lastWebTitle !== ''
? `
home
previous`
: "
back";
const nextButtonMarkup =
global.sequentialLinks && nextWebTitle !== ''
? `
next
`
: "
back
";
const audioMarkup = global.mp3
? `
`
: '';
const backButton =
title === 'index' ||
(lastWebTitle.indexOf('%') > -1 && global.sequentialLinks)
? ''
: backButtonMarkup;
const nextButton =
title === 'index' || nextWebTitle.indexOf('%') > -1
? ''
: nextButtonMarkup;
let finalText = global.projectAuthor
? textArray[i].replace(
'',
`by ${global.projectAuthor}
`
)
: textArray[i];
finalText = splitter + finalText;
finalText = replaceQuotes(finalText);
finalText = typeset(finalText);
let fileContents =
commentDate +
backButton +
audioMarkup +
finalText +
nextButton +
analytics +
'