Browse Source

First working version

pull/4/head
Valentin Zulkower 6 years ago
commit
748bf6c6ca
  1. 61
      .gitignore
  2. 1
      .npmignore
  3. 8
      LICENCE.txt
  4. 40
      README.md
  5. 8745
      package-lock.json
  6. 28
      package.json
  7. 130
      src/converters.js
  8. 72
      src/index.js
  9. 16
      src/templates/flowchart.pug
  10. 2
      src/templates/mermaid.pug
  11. 6
      src/templates/vegalite.pug

61
.gitignore

@ -0,0 +1,61 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Typescript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next

1
.npmignore

@ -0,0 +1 @@
examples/

8
LICENCE.txt

@ -0,0 +1,8 @@
ISC License (ISC)
Copyright 2018, Zulko
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

40
README.md

@ -0,0 +1,40 @@
# ReLaXed
ReLaXed is a software to create PDF documents using the Pug language (a shorthand for HTML). It enables to define complex layouts with CSS and Javascript while writing the content in a friendly, minimal syntax close to Markdown or LaTeX.
## Getting started
Install ReLaXed with [NPM](https://www.npmjs.com/get-npm):
```
npm install -h relaxedjs
```
To start a project, create a new empty file ``my_document.pug`, and start ReLaXed from a terminal:
```
relaxed my_document.pug
```
Now ReLaXed is watching ``my_document.pug`` and all its directory. Everytime a file changes, ``my_document.pug`` will be compiled as ``my_document.pdf``. Tot est this, write the following in ``my_document.pug`` and save:
```pug
h1 My document's title
p A paragraph in my document
```
Now you know the basics and you can write your first documents ! To go further:
- Learn more about the capabilities of the [Pug language](https://pugjs.org/api/getting-started.html).
- Browse the [examples]()
- Read about our [recommended setup]() to use ReLaXed
- Learn some [advanced features]() of ReLaxed
- Read [this comparison]() of ReLaXed and other document editing systems
## How ReLaXed works
ReLaxed consists of few lines of code binding together other software. It uses [chokidar](https://github.com/paulmillr/chokidar) to watch the file system. when a file is changed, several javascript libraries are used to compile SCSS, Pug, Markdown, and [diagram files] into an HTML page which is then printed to a PDF file by a headless instance of Chromium (via [puppeteer](https://github.com/GoogleChrome/puppeteer)).
## Contribute
ReLaXed is an open source framework originally written by [Zulko](https://github.com/Zulko) and released on [Github](https://github.com/Zulko/relaxed) under the ISC licence. Everyone is welcome to contribute!

8745
package-lock.json
File diff suppressed because it is too large
View File

28
package.json

@ -0,0 +1,28 @@
{
"name": "relaxed-editor",
"version": "0.1.0",
"description": "Friendly HTML/PUG/CSS converter to beautiful PDF documents",
"main": "src/index.js",
"bin": {
"relaxed": "src/index.js"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Zulko",
"license": "ISC",
"dependencies": {
"cheerio": "^1.0.0-rc.2",
"chokidar": "^2.0.1",
"commander": "^2.14.1",
"jstransformer-highlight": "^1.0.1",
"jstransformer-markdown-it": "^2.0.0",
"jstransformer-scss": "^1.0.0",
"jstransformer-svgo": "^1.0.0",
"mathjax-node-page": "^2.0.0",
"mermaid": "^7.1.2",
"npm": "^5.6.0",
"pug": "^2.0.0-rc.4",
"puppeteer": "^1.0.0"
}
}

130
src/converters.js

@ -0,0 +1,130 @@
const fs = require('fs')
const util = require('util')
const mjpage = require('mathjax-node-page')
const pug = require('pug')
const writeFile = util.promisify(fs.writeFile)
const cheerio = require('cheerio')
const path = require('path')
function formatTemplate (tempName, data) {
return pug.renderFile(path.join(__dirname, 'templates', tempName + '.pug'), data)
}
exports.mermaidToSvg = async function (mermaidPath, page) {
var mermaidSpec = fs.readFileSync(mermaidPath, 'utf8')
var html = formatTemplate('mermaid', { mermaidSpec })
await page.setContent(html)
await page.waitForSelector('#graph svg')
var svg = await page.evaluate(function () {
var el = document.querySelector('#graph svg')
el.removeAttribute('height')
el.classList.add('mermaid-svg')
return el.outerHTML
})
var svgPath = mermaidPath.substr(0, mermaidPath.lastIndexOf('.')) + '.svg'
await writeFile(svgPath, svg)
}
exports.flowchartToSvg = async function (flowchartPath, page) {
var flowchartSpec = fs.readFileSync(flowchartPath, 'utf8')
var flowchartConf = '{}'
var possibleConfs = [
path.join(path.resolve(flowchartPath, '..'), 'flowchart.default.json'),
flowchartPath + '.json'
]
for (var myPath of possibleConfs) {
if (fs.existsSync(myPath)) {
flowchartConf = fs.readFileSync(myPath, 'utf8')
}
}
var html = formatTemplate('flowchart', { flowchartSpec, flowchartConf })
// console.log(html)
await page.setContent(html)
await page.waitForSelector('#chart svg')
var svg = await page.evaluate(function () {
var el = document.querySelector('#chart svg')
el.removeAttribute('height')
el.removeAttribute('width')
el.classList.add('flowchart-svg')
return el.outerHTML
})
var svgPath = flowchartPath.substr(0, flowchartPath.lastIndexOf('.')) + '.svg'
await writeFile(svgPath, svg)
}
exports.vegaliteToSvg = async function (vegalitePath, page) {
var vegaliteSpec = fs.readFileSync(vegalitePath, 'utf8')
var html = formatTemplate('vegalite', { vegaliteSpec })
console.log('file:' + vegalitePath)
var tempHTML = vegalitePath + '.htm'
// await writeFile(tempHTML, html)
// await page.goto('file:' + tempHTML);
await page.setContent(html)
await page.waitForSelector('#vis svg')
var svg = await page.evaluate(function () {
var el = document.querySelector('#vis svg')
el.removeAttribute('height')
el.removeAttribute('width')
return el.outerHTML
})
var svgPath = vegalitePath.substr(0, vegalitePath.length - '.vegalite.json'.length) + '.svg'
await writeFile(svgPath, svg)
}
function asyncMathjax (html) {
return new Promise(resolve => {
mjpage.mjpage(html, {
format: ['TeX']
}, {
mml: true,
css: true,
html: true
}, response => resolve(response))
})
}
function getMatch (string, query) {
var result = string.match(query)
if (result) {
result = result[1]
}
return result
}
exports.masterDocumentToPDF = async function (masterPath, page, tempHTML, outputPath) {
var html
if (masterPath.endsWith('.pug')) {
html = pug.renderFile(masterPath)
} else {
html = fs.readFileSync(masterPath, 'utf8')
}
html = await asyncMathjax(html)
var parsedHtml = cheerio.load(html)
html = parsedHtml.html() // adds html, body, head.
var headerTemplate = parsedHtml('template.header').html()
var footerTemplate = parsedHtml('template.footer').html()
// await page.setContent(html)
await writeFile(tempHTML, html)
await page.goto('file:' + tempHTML, {waitUntil: 'networkidle2'});
// await page.waitForNavigation({ waitUntil: 'networkidle2' })
var options = {
path: outputPath,
displayHeaderFooter: headerTemplate || footerTemplate,
headerTemplate,
footerTemplate,
printBackground: true
}
var width = getMatch(html, /-relaxed-page-width: (\S+);/m)
if (width) {
options.width = width
}
var height = getMatch(html, /-relaxed-page-height: (\S+);/m)
if (height) {
options.height = height
}
var size = getMatch(html, /-relaxed-page-size: (\S+);/m)
if (size) {
options.size = size
}
await page.pdf(options)
}

72
src/index.js

@ -0,0 +1,72 @@
#!/usr/bin/env node
const program = require('commander')
const chokidar = require('chokidar')
const puppeteer = require('puppeteer')
const { performance } = require('perf_hooks')
const path = require('path')
const converters = require('./converters.js')
var input, output
program
.version('0.0.1')
.usage('<input> [output] [options]')
.arguments('<input> [output] [options]')
.option('-w, --watch', 'option description')
.action(function (inp, out) {
input = inp
output = out
})
program.parse(process.argv)
if (!output) {
output = input.substr(0, input.lastIndexOf('.')) + '.pdf'
}
const inputPath = path.resolve(input)
const outputPath = path.resolve(output)
const inputDir = path.resolve(inputPath, '..')
const tempHTML = path.join(inputDir, '_temp.htm')
async function main () {
console.log('Watching ' + input + ' and its directory tree.')
const browser = await puppeteer.launch({
headless: true
})
const page = await browser.newPage()
page.on('pageerror', function (err) {
console.log('Page error: ' + err.toString())
}).on('error', function (err) {
console.log('Error: ' + err.toString())
})
chokidar.watch(inputDir, {
awaitWriteFinish: {
stabilityThreshold: 50,
pollInterval: 100
}
}).on('change', (filepath) => {
var t0 = performance.now()
var taskPromise = null
if (['.pug', '.md', '.html', '.css', '.scss', '.svg'].some(ext => filepath.endsWith(ext))) {
taskPromise = converters.masterDocumentToPDF(inputPath, page, tempHTML, outputPath)
} else if (filepath.endsWith('.mermaid')) {
taskPromise = converters.mermaidToSvg(filepath, page)
} else if (filepath.endsWith('.flowchart')) {
taskPromise = converters.flowchartToSvg(filepath, page)
} else if (filepath.endsWith('.flowchart.json')) {
var flowchartFile = filepath.substr(0, filepath.length - 5)
taskPromise = converters.flowchartToSvg(flowchartFile, page)
} else if (filepath.endsWith('.vegalite.json')) {
taskPromise = converters.vegaliteToSvg(filepath, page)
}
if (taskPromise) {
taskPromise.then(function () {
var duration = ((performance.now() - t0) / 1000).toFixed(2)
console.log(`Processed change in ${filepath} in ${duration}s`)
})
}
})
}
main()

16
src/templates/flowchart.pug

@ -0,0 +1,16 @@
script(src='http://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.0/raphael-min.js')
script(src='http://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.0/jquery.min.js')
script(src='http://flowchart.js.org/flowchart-latest.js')
script.
$(document).ready(function() {
var chart = flowchart.parse(`!{flowchartSpec}`)
var conf = {'symbols': []}
for (var c of ['start', 'end', 'operation', 'subroutine', 'condition', 'inputoutput']) {
conf.symbols[c] = {'class': c + '-element'}
}
conf = Object.assign({}, conf, !{flowchartConf})
console.log(conf)
chart.drawSVG('chart', conf)
});
body
#chart

2
src/templates/mermaid.pug

@ -0,0 +1,2 @@
script(src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/7.1.2/mermaid.min.js")
#graph.mermaid #{mermaidSpec}

6
src/templates/vegalite.pug

@ -0,0 +1,6 @@
script(src='https://cdn.jsdelivr.net/npm/vega@3.0.10')
script(src='https://cdn.jsdelivr.net/npm/vega-lite@2.1.3')
script(src='https://cdn.jsdelivr.net/npm/vega-embed@3.0.0')
#vis
script(type='text/javascript').
vegaEmbed('#vis', !{vegaliteSpec}, {'renderer': 'svg'});
Loading…
Cancel
Save