From 62d75c3a27d75958bd769b6e4980db51bea9f346 Mon Sep 17 00:00:00 2001 From: Trivernis Date: Sun, 28 Jul 2019 20:58:11 +0200 Subject: [PATCH] Fixes and stylesheets - added `[!stylesheet]: file.css` command to include custom stylesheets - changed images to be included as base64 url - removed markdown task from gulpfile - fixed commands not being recognized --- .gitignore | 1 + CHANGELOG.md | 4 +++- README.md | 12 ++++++++++ gulpfile.js | 15 ++---------- package-lock.json | 14 +++++++++++ package.json | 2 ++ src/CommandParser.ts | 34 +++++++++++++++++++++----- src/Renderer.ts | 53 +++++++++++++++++++++++++++++++++++++---- src/styles/default.sass | 4 ++++ 9 files changed, 114 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 0160678..5c5d3b5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ test.md test.html testchapter.md git +testfiles diff --git a/CHANGELOG.md b/CHANGELOG.md index cfc9292..fc3cff4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,8 +5,10 @@ - Changelog - markdown parsing with markdown it - command to include plugins `[!use]: plugin` -- command to include markdown files `[!include]: file` +- command to include markdown files `[!include]: file.md` - command to start a new page `[!newpage]` +- command to include a stylesheet `[!stylesheet]: file.css` - option to export to pdf `--pdf` - option to watch (and export to html) `--watch` - stylesheets supporting math and code highlighting (with highlightjs) +- auto base64 converting for images for a standalone html diff --git a/README.md b/README.md index fe5bb63..ec1624c 100644 --- a/README.md +++ b/README.md @@ -81,3 +81,15 @@ Warning: be careful not to put too much content in this environment because it will be rendered as only ONE page in the pdf output. ::: ``` + +## Stylesheets + +You can include your own stylesheet. It is applied after the default style. The stylesheets are applied in the order they are declared. + +```markdown +[!stylesheet]: path/to/style.css +``` + +## Other stuff + +Images and stylesheets are included within the html file. All image urls are converted into base64 urls. diff --git a/gulpfile.js b/gulpfile.js index 0f11239..8b653a8 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -3,9 +3,6 @@ const sass = require('gulp-sass'); const ts = require('gulp-typescript'); const del = require('delete'); const cleanCss = require('gulp-clean-css'); -const renderer = require('./dist/Renderer'); -const fsx = require("fs-extra"); -const path = require("path"); function clearDist(cb) { del('dist/*', cb); @@ -25,18 +22,10 @@ function compileSass() { .pipe(dest('dist/styles')); } -async function renderMarkdown() { - let filename = 'test.md'; - let rend = new renderer.Renderer(); - let html = await rend.render(filename); - await fsx.writeFile(filename.replace(path.extname(filename), '') + '.html', html); -} - task('cleanBuild', series(clearDist, compileTypescript, compileSass)); task('default', series(compileTypescript, compileSass)); task('watch', () => { - series(compileSass, compileTypescript, renderMarkdown); - watch('src/**/*.sass', series(compileSass, renderMarkdown)); + series(compileSass, compileTypescript); + watch('src/**/*.sass', series(compileSass)); watch('**/*.ts', compileTypescript); - watch('**/*.md', renderMarkdown); }); diff --git a/package-lock.json b/package-lock.json index 6587484..871937a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,6 +67,15 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz", "integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==" }, + "@types/node-fetch": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.0.tgz", + "integrity": "sha512-TLFRywthBgL68auWj+ziWu+vnmmcHCDFC/sqCOQf1xTz4hRq8cu79z8CtHU9lncExGBsB8fXA4TiLDLt6xvMzw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/puppeteer": { "version": "1.19.0", "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-1.19.0.tgz", @@ -3753,6 +3762,11 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, "node-gyp": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", diff --git a/package.json b/package.json index 2b9f8ca..47ac167 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@types/jsdom": "^12.2.4", "@types/markdown-it": "0.0.8", "@types/node": "^12.6.8", + "@types/node-fetch": "^2.5.0", "@types/winston": "^2.4.4", "delete": "^1.1.0", "gulp": "^4.0.2", @@ -50,6 +51,7 @@ "markdown-it-smartarrows": "^1.0.1", "markdown-it-sub": "^1.0.0", "markdown-it-toc-done-right": "^4.0.2", + "node-fetch": "^2.6.0", "puppeteer": "^1.19.0", "uninstall": "0.0.0", "winston": "^3.2.1" diff --git a/src/CommandParser.ts b/src/CommandParser.ts index 94cbdb1..a466b29 100644 --- a/src/CommandParser.ts +++ b/src/CommandParser.ts @@ -5,14 +5,11 @@ import {markdownPlugins} from './plugins'; import {pageFormats} from "./formats"; import {PDFFormat} from "puppeteer"; -namespace Matchers { - export const commandMatcher: RegExp = /\[ *!(\w+) *]:? *(.*)/g; -} - export class CommandParser { public projectFiles: string[]; public pageFormat: PDFFormat; public loadedPlugins: string[]; + public stylesheets: string[]; private resolvePath: {path: string, lines: number}[]; @@ -20,9 +17,15 @@ export class CommandParser { this.projectFiles = []; this.loadedPlugins = []; this.resolvePath = []; + this.stylesheets = []; } async processCommands(doc: string, docpath: string, renderer: Renderer) { + if (process.platform === 'darwin') { + doc = doc.replace(/\r/g, '\n'); // mac uses \r as linebreak + } else { + doc = doc.replace(/\r/g, ''); // linux uses \n, windows \r\n + } const inputLines: string[] = doc.split('\n'); let outputLines: string[] = []; let mainDir: string = path.dirname(docpath); @@ -31,7 +34,7 @@ export class CommandParser { this.resolvePath.push({path: docpath, lines: inputLines.length}); while (inputLines.length > 0) { - let inputLine = inputLines.shift().replace(/\r/, ''); + let inputLine = inputLines.shift(); let currentFile = this.resolvePath[this.resolvePath.length - 1]; // keeping track of the current file if (currentFile.lines > 0) { currentFile.lines--; @@ -41,7 +44,7 @@ export class CommandParser { currentFile = this.resolvePath[this.resolvePath.length - 1]; } } - let match: RegExpExecArray = Matchers.commandMatcher.exec(inputLine); + let match: RegExpExecArray = /\[ *!(\w+) *\]:? *(.*)/gu.exec(inputLine.replace(/\s/, '')); if (match && match[0]) { // TODO: stylesheets switch(match[1]) { @@ -78,6 +81,9 @@ export class CommandParser { this.addMarkdownPlugin('div', renderer); outputLines.push('::: .newpage \n:::'); break; + case 'stylesheet': + await this.addStylesheet(match[2], mainDir); + break; default: outputLines.push(inputLine); } @@ -134,4 +140,20 @@ export class CommandParser { throw new Error(`The file ${filepath} can not be included (not found).`); } } + + /** + * Adds a stylesheet to the result markdown. + * @param filepath + * @param mainDir + */ + private async addStylesheet(filepath: string, mainDir: string) { + let stylepath: string; + if (path.isAbsolute(filepath)) { + stylepath = path.normalize(filepath); + } else { + stylepath = path.join(mainDir, filepath); + } + if ((await fsx.pathExists(stylepath))) + this.stylesheets.push(stylepath); + } } diff --git a/src/Renderer.ts b/src/Renderer.ts index ab3dd6f..99eb802 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -7,11 +7,12 @@ import {JSDOM} from 'jsdom'; import {CommandParser} from "./CommandParser"; import {EventEmitter} from "events"; import {PDFFormat} from "puppeteer"; +import fetch from 'node-fetch'; export class Renderer extends EventEmitter { private md: MarkdownIt; - private beforeRendering: Function[]; - private afterRendering: Function[]; + private readonly beforeRendering: Function[]; + private readonly afterRendering: Function[]; private commandParser: CommandParser; constructor() { @@ -111,12 +112,54 @@ export class Renderer extends EventEmitter { */ private configure() { this.useBefore((a: string, b: string, c: Renderer) => this.commandParser.processCommands(a, b, c)); - this.useAfter(async (doc: string) => { - let dom: JSDOM = new JSDOM(doc); + this.useAfter((doc: string) => new JSDOM(doc)); + // include default style + this.useAfter(async (dom: JSDOM) => { let styleTag = dom.window.document.createElement('style'); + // append the default style styleTag.innerHTML = await fsx.readFile(path.join(__dirname, 'styles/default.css'), 'utf-8'); dom.window.document.head.appendChild(styleTag); - return dom.serialize(); + return dom; }); + // include user defined styles + this.useAfter(async (dom: JSDOM) => { + let userStyles = dom.window.document.createElement('style'); + userStyles.setAttribute('id', 'user-style'); + // append all user defined stylesheets + for (let stylesheet of this.commandParser.stylesheets) { + userStyles.innerHTML += await fsx.readFile(stylesheet, 'utf-8'); + } + dom.window.document.head.appendChild(userStyles); + return dom; + }); + // include all images as base64 + this.useAfter(async (dom: JSDOM, mainfile: string) => { + let document = dom.window.document; + let mainFolder = path.dirname(mainfile); + let imgs = document.querySelectorAll('img'); + for (let img of imgs) { + let source = img.src; + let filepath = source; + let base64Url = source; + if (!path.isAbsolute(filepath)) + filepath = path.join(mainFolder, filepath); + if (await fsx.pathExists(filepath)) { + let type = path.extname(source).slice(1); + base64Url = `data:image/${type};base64,`; + base64Url += (await fsx.readFile(filepath)).toString('base64'); + } else { + try { + let response = await fetch(source); + base64Url = `data:${response.headers.get('content-type')};base64,`; + base64Url += (await response.buffer()).toString('base64'); + } catch (error) { + console.error(error); + } + } + img.src = base64Url; + } + return dom; + }); + this.useAfter((dom: JSDOM) => dom.serialize()); } } diff --git a/src/styles/default.sass b/src/styles/default.sass index 71f20f9..ebd0d92 100644 --- a/src/styles/default.sass +++ b/src/styles/default.sass @@ -22,6 +22,10 @@ nav border: 2px solid $backgroundSecondary page-break-after: always +img + width: 100% + height: auto + .page page-break-before: always page-break-after: always