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
develop
Trivernis 5 years ago
parent 6f39c36f56
commit 62d75c3a27

1
.gitignore vendored

@ -5,3 +5,4 @@ test.md
test.html test.html
testchapter.md testchapter.md
git git
testfiles

@ -5,8 +5,10 @@
- Changelog - Changelog
- markdown parsing with markdown it - markdown parsing with markdown it
- command to include plugins `[!use]: plugin` - 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 start a new page `[!newpage]`
- command to include a stylesheet `[!stylesheet]: file.css`
- option to export to pdf `--pdf` - option to export to pdf `--pdf`
- option to watch (and export to html) `--watch` - option to watch (and export to html) `--watch`
- stylesheets supporting math and code highlighting (with highlightjs) - stylesheets supporting math and code highlighting (with highlightjs)
- auto base64 converting for images for a standalone html

@ -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. 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.

@ -3,9 +3,6 @@ const sass = require('gulp-sass');
const ts = require('gulp-typescript'); const ts = require('gulp-typescript');
const del = require('delete'); const del = require('delete');
const cleanCss = require('gulp-clean-css'); const cleanCss = require('gulp-clean-css');
const renderer = require('./dist/Renderer');
const fsx = require("fs-extra");
const path = require("path");
function clearDist(cb) { function clearDist(cb) {
del('dist/*', cb); del('dist/*', cb);
@ -25,18 +22,10 @@ function compileSass() {
.pipe(dest('dist/styles')); .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('cleanBuild', series(clearDist, compileTypescript, compileSass));
task('default', series(compileTypescript, compileSass)); task('default', series(compileTypescript, compileSass));
task('watch', () => { task('watch', () => {
series(compileSass, compileTypescript, renderMarkdown); series(compileSass, compileTypescript);
watch('src/**/*.sass', series(compileSass, renderMarkdown)); watch('src/**/*.sass', series(compileSass));
watch('**/*.ts', compileTypescript); watch('**/*.ts', compileTypescript);
watch('**/*.md', renderMarkdown);
}); });

14
package-lock.json generated

@ -67,6 +67,15 @@
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-12.6.8.tgz",
"integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==" "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": { "@types/puppeteer": {
"version": "1.19.0", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-1.19.0.tgz", "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-1.19.0.tgz",
@ -3753,6 +3762,11 @@
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=",
"dev": true "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": { "node-gyp": {
"version": "3.8.0", "version": "3.8.0",
"resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz",

@ -16,6 +16,7 @@
"@types/jsdom": "^12.2.4", "@types/jsdom": "^12.2.4",
"@types/markdown-it": "0.0.8", "@types/markdown-it": "0.0.8",
"@types/node": "^12.6.8", "@types/node": "^12.6.8",
"@types/node-fetch": "^2.5.0",
"@types/winston": "^2.4.4", "@types/winston": "^2.4.4",
"delete": "^1.1.0", "delete": "^1.1.0",
"gulp": "^4.0.2", "gulp": "^4.0.2",
@ -50,6 +51,7 @@
"markdown-it-smartarrows": "^1.0.1", "markdown-it-smartarrows": "^1.0.1",
"markdown-it-sub": "^1.0.0", "markdown-it-sub": "^1.0.0",
"markdown-it-toc-done-right": "^4.0.2", "markdown-it-toc-done-right": "^4.0.2",
"node-fetch": "^2.6.0",
"puppeteer": "^1.19.0", "puppeteer": "^1.19.0",
"uninstall": "0.0.0", "uninstall": "0.0.0",
"winston": "^3.2.1" "winston": "^3.2.1"

@ -5,14 +5,11 @@ import {markdownPlugins} from './plugins';
import {pageFormats} from "./formats"; import {pageFormats} from "./formats";
import {PDFFormat} from "puppeteer"; import {PDFFormat} from "puppeteer";
namespace Matchers {
export const commandMatcher: RegExp = /\[ *!(\w+) *]:? *(.*)/g;
}
export class CommandParser { export class CommandParser {
public projectFiles: string[]; public projectFiles: string[];
public pageFormat: PDFFormat; public pageFormat: PDFFormat;
public loadedPlugins: string[]; public loadedPlugins: string[];
public stylesheets: string[];
private resolvePath: {path: string, lines: number}[]; private resolvePath: {path: string, lines: number}[];
@ -20,9 +17,15 @@ export class CommandParser {
this.projectFiles = []; this.projectFiles = [];
this.loadedPlugins = []; this.loadedPlugins = [];
this.resolvePath = []; this.resolvePath = [];
this.stylesheets = [];
} }
async processCommands(doc: string, docpath: string, renderer: Renderer) { 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'); const inputLines: string[] = doc.split('\n');
let outputLines: string[] = []; let outputLines: string[] = [];
let mainDir: string = path.dirname(docpath); let mainDir: string = path.dirname(docpath);
@ -31,7 +34,7 @@ export class CommandParser {
this.resolvePath.push({path: docpath, lines: inputLines.length}); this.resolvePath.push({path: docpath, lines: inputLines.length});
while (inputLines.length > 0) { 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 let currentFile = this.resolvePath[this.resolvePath.length - 1]; // keeping track of the current file
if (currentFile.lines > 0) { if (currentFile.lines > 0) {
currentFile.lines--; currentFile.lines--;
@ -41,7 +44,7 @@ export class CommandParser {
currentFile = this.resolvePath[this.resolvePath.length - 1]; 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 if (match && match[0]) { // TODO: stylesheets
switch(match[1]) { switch(match[1]) {
@ -78,6 +81,9 @@ export class CommandParser {
this.addMarkdownPlugin('div', renderer); this.addMarkdownPlugin('div', renderer);
outputLines.push('::: .newpage \n:::'); outputLines.push('::: .newpage \n:::');
break; break;
case 'stylesheet':
await this.addStylesheet(match[2], mainDir);
break;
default: default:
outputLines.push(inputLine); outputLines.push(inputLine);
} }
@ -134,4 +140,20 @@ export class CommandParser {
throw new Error(`The file ${filepath} can not be included (not found).`); 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);
}
} }

@ -7,11 +7,12 @@ import {JSDOM} from 'jsdom';
import {CommandParser} from "./CommandParser"; import {CommandParser} from "./CommandParser";
import {EventEmitter} from "events"; import {EventEmitter} from "events";
import {PDFFormat} from "puppeteer"; import {PDFFormat} from "puppeteer";
import fetch from 'node-fetch';
export class Renderer extends EventEmitter { export class Renderer extends EventEmitter {
private md: MarkdownIt; private md: MarkdownIt;
private beforeRendering: Function[]; private readonly beforeRendering: Function[];
private afterRendering: Function[]; private readonly afterRendering: Function[];
private commandParser: CommandParser; private commandParser: CommandParser;
constructor() { constructor() {
@ -111,12 +112,54 @@ export class Renderer extends EventEmitter {
*/ */
private configure() { private configure() {
this.useBefore((a: string, b: string, c: Renderer) => this.commandParser.processCommands(a, b, c)); this.useBefore((a: string, b: string, c: Renderer) => this.commandParser.processCommands(a, b, c));
this.useAfter(async (doc: string) => { this.useAfter((doc: string) => new JSDOM(doc));
let dom: JSDOM = new JSDOM(doc); // include default style
this.useAfter(async (dom: JSDOM) => {
let styleTag = dom.window.document.createElement('style'); let styleTag = dom.window.document.createElement('style');
// append the default style
styleTag.innerHTML = await fsx.readFile(path.join(__dirname, 'styles/default.css'), 'utf-8'); styleTag.innerHTML = await fsx.readFile(path.join(__dirname, 'styles/default.css'), 'utf-8');
dom.window.document.head.appendChild(styleTag); 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());
} }
} }

@ -22,6 +22,10 @@ nav
border: 2px solid $backgroundSecondary border: 2px solid $backgroundSecondary
page-break-after: always page-break-after: always
img
width: 100%
height: auto
.page .page
page-break-before: always page-break-before: always
page-break-after: always page-break-after: always

Loading…
Cancel
Save