live-markdown
Version:
21st century markdown
135 lines (122 loc) • 5.21 kB
JavaScript
const { readFile, existsSync, writeFile, mkdirSync, createWriteStream } = require("fs");
const puppeteer = require('puppeteer');
const chalk = require('chalk');
const GIFEncoder = require('gifencoder');
const rimraf = require("rimraf");
const PNG = require('png-js');
const args = require('yargs').argv;
const VIEWPORT_WIDTH = 1024
const source = args.s;
const destination = args.d;
const folder = args.f || "readme-pics/";
function uuid() {
return Math.random().toString(36).substr(2, 9);
}
function decode(png) {
return new Promise(r => { png.decode(pixels => r(pixels)) });
}
async function gifAddFrame(page, encoder, clip) {
const pngBuffer = await page.screenshot({
omitBackground: true, clip: {
...clip,
width: VIEWPORT_WIDTH
}
});
const png = new PNG(pngBuffer);
await decode(png).then(pixels => encoder.addFrame(pixels));
}
if (!source) {
console.log(chalk.red("Error: You must define a source md file (livemd -s README.md)"));
return;
}
if (source && !source.includes(".md")) {
console.log(chalk.red("Error: Source must be an md file (README.md)"));
return;
}
if (!existsSync(source)) {
console.log(chalk.red(`Error: File ${source} not fould`));
return;
}
if (folder) {
if (existsSync(folder)) {
rimraf(folder, function () {
mkdirSync(folder);
GenerateLiveMarkdown();
});
} else {
mkdirSync(folder);
GenerateLiveMarkdown();
}
}
function GenerateLiveMarkdown() {
readFile(source, 'utf8', (err, data) => {
let content = data;
let newContent = "";
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.setContent(content, {
waitUntil: "networkidle0"
});
const children = await page.evaluate((folder, uuid) => {
let ffolder = "";
if (folder) {
ffolder = folder.slice(-1) === "/" ? folder : `${folder}/`;
}
return Array.from(document.body.children).map(child => {
const duration = Number(getComputedStyle(child).animationDuration.split("s")[0]) * 30;
const extension = duration ? "gif" : "png";
return ({
path: `${ffolder}${child.tagName.toLowerCase()}${uuid}.${extension}`,
outerHtml: child.outerHTML,
duration,
clip: {
width: child.getBoundingClientRect().width,
height: child.getBoundingClientRect().height,
x: child.getBoundingClientRect().x,
y: child.getBoundingClientRect().y,
}
})
})
}, folder, uuid());
newContent = await page.evaluate(() => {
Array.from(document.body.querySelectorAll("style")).map(style => style.remove());
return document.body.innerHTML
});
for (const child of children) {
if (child.duration) {
const encoder = new GIFEncoder(1024, child.clip.height);
encoder.createWriteStream().pipe(createWriteStream(child.path));
encoder.start()
encoder.setRepeat(0);
encoder.setFrameRate((child.duration / 30) * 2);
encoder.setTransparent(0x0000FF);
encoder.setQuality(10);
for (let i = 1; i <= child.duration; i++) {
process.stdout.write(`Generating gif: ${chalk.green(child.path + " " + Math.round(i * 100 / child.duration) + "%")}`);
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write(`Generating gif: ${chalk.green(child.path + " " + Math.round(i * 100 / child.duration) + "%")}`);
await gifAddFrame(page, encoder, child.clip);
}
encoder.finish();
} else {
process.stdout.write(`Generating png: ${chalk.green(child.path + " 100%")} \n`);
await page.screenshot({
path: child.path,
omitBackground: true,
clip: {
...child.clip,
width: VIEWPORT_WIDTH
},
});
}
newContent = newContent.replace(new RegExp(`${child.outerHtml}`, "gm"), `<img src="${child.path}" />`)
await writeFile(`${destination || (source.split(".md")[0] + 1 + ".md")}`, newContent, 'utf8', function (err) {
if (err) throw err;
});
}
await browser.close();
});
});
}