2017-09-17 15:32:39 +00:00
|
|
|
import FileSaver from 'file-saver';
|
|
|
|
import TemplateWorker from 'worker-loader!./templateWorker.js'; // eslint-disable-line
|
|
|
|
import localDbSvc from './localDbSvc';
|
|
|
|
import markdownConversionSvc from './markdownConversionSvc';
|
|
|
|
import extensionSvc from './extensionSvc';
|
|
|
|
import utils from './utils';
|
|
|
|
import store from '../store';
|
|
|
|
import htmlSanitizer from '../libs/htmlSanitizer';
|
|
|
|
|
|
|
|
function groupHeadings(headings, level = 1) {
|
|
|
|
const result = [];
|
|
|
|
let currentItem;
|
|
|
|
|
|
|
|
function pushCurrentItem() {
|
|
|
|
if (currentItem) {
|
|
|
|
if (currentItem.children.length > 0) {
|
|
|
|
currentItem.children = groupHeadings(currentItem.children, level + 1);
|
|
|
|
}
|
|
|
|
result.push(currentItem);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
headings.forEach((heading) => {
|
|
|
|
if (heading.level !== level) {
|
|
|
|
currentItem = currentItem || {
|
|
|
|
children: [],
|
|
|
|
};
|
|
|
|
currentItem.children.push(heading);
|
|
|
|
} else {
|
|
|
|
pushCurrentItem();
|
|
|
|
currentItem = heading;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
pushCurrentItem();
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2017-11-04 16:59:48 +00:00
|
|
|
const containerElt = document.createElement('div');
|
|
|
|
containerElt.className = 'hidden-rendering-container';
|
|
|
|
document.body.appendChild(containerElt);
|
|
|
|
|
2017-09-17 15:32:39 +00:00
|
|
|
export default {
|
|
|
|
/**
|
|
|
|
* Apply the template to the file content
|
|
|
|
*/
|
|
|
|
applyTemplate(fileId, template = {
|
|
|
|
value: '{{{files.0.content.text}}}',
|
|
|
|
helpers: '',
|
2017-11-04 16:59:48 +00:00
|
|
|
}, pdf = false) {
|
2017-09-17 15:32:39 +00:00
|
|
|
const file = store.state.file.itemMap[fileId];
|
|
|
|
return localDbSvc.loadItem(`${fileId}/content`)
|
|
|
|
.then((content) => {
|
|
|
|
const properties = utils.computeProperties(content.properties);
|
|
|
|
const options = extensionSvc.getOptions(properties);
|
|
|
|
const converter = markdownConversionSvc.createConverter(options, true);
|
|
|
|
const parsingCtx = markdownConversionSvc.parseSections(converter, content.text);
|
|
|
|
const conversionCtx = markdownConversionSvc.convert(parsingCtx);
|
|
|
|
const html = conversionCtx.htmlSectionList.map(htmlSanitizer.sanitizeHtml).join('');
|
2017-11-04 16:59:48 +00:00
|
|
|
containerElt.innerHTML = html;
|
|
|
|
extensionSvc.sectionPreview(containerElt, options);
|
2017-09-17 15:32:39 +00:00
|
|
|
|
|
|
|
// Unwrap tables
|
2017-11-04 16:59:48 +00:00
|
|
|
containerElt.querySelectorAll('.table-wrapper').cl_each((wrapperElt) => {
|
2017-09-17 15:32:39 +00:00
|
|
|
while (wrapperElt.firstChild) {
|
2017-11-04 16:59:48 +00:00
|
|
|
wrapperElt.parentNode.insertBefore(wrapperElt.firstChild, wrapperElt.nextSibling);
|
2017-09-17 15:32:39 +00:00
|
|
|
}
|
|
|
|
wrapperElt.parentNode.removeChild(wrapperElt);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Make TOC
|
2017-11-04 16:59:48 +00:00
|
|
|
const headings = containerElt.querySelectorAll('h1,h2,h3,h4,h5,h6').cl_map(headingElt => ({
|
2017-09-17 15:32:39 +00:00
|
|
|
title: headingElt.textContent,
|
|
|
|
anchor: headingElt.id,
|
|
|
|
level: parseInt(headingElt.tagName.slice(1), 10),
|
|
|
|
children: [],
|
|
|
|
}));
|
|
|
|
const toc = groupHeadings(headings);
|
|
|
|
const view = {
|
2017-11-04 16:59:48 +00:00
|
|
|
pdf,
|
2017-09-17 15:32:39 +00:00
|
|
|
files: [{
|
|
|
|
name: file.name,
|
|
|
|
content: {
|
|
|
|
text: content.text,
|
|
|
|
properties,
|
|
|
|
yamlProperties: content.properties,
|
2017-11-04 16:59:48 +00:00
|
|
|
html: containerElt.innerHTML,
|
2017-09-17 15:32:39 +00:00
|
|
|
toc,
|
|
|
|
},
|
|
|
|
}],
|
|
|
|
};
|
2017-11-04 16:59:48 +00:00
|
|
|
containerElt.innerHTML = '';
|
2017-09-17 15:32:39 +00:00
|
|
|
|
|
|
|
// Run template conversion in a Worker to prevent attacks from helpers
|
|
|
|
const worker = new TemplateWorker();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const timeoutId = setTimeout(() => {
|
|
|
|
worker.terminate();
|
|
|
|
reject('Template generation timeout.');
|
|
|
|
}, 10000);
|
|
|
|
worker.addEventListener('message', (e) => {
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
worker.terminate();
|
|
|
|
// e.data can contain unsafe data if helpers attempts to call postMessage
|
|
|
|
const [err, result] = e.data;
|
|
|
|
if (err) {
|
2017-09-26 22:54:26 +00:00
|
|
|
reject(`${err}`);
|
2017-09-17 15:32:39 +00:00
|
|
|
} else {
|
2017-09-26 22:54:26 +00:00
|
|
|
resolve(`${result}`);
|
2017-09-17 15:32:39 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
worker.postMessage([template.value, view, template.helpers]);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
|
|
|
/**
|
|
|
|
* Export a file to disk.
|
|
|
|
*/
|
|
|
|
exportToDisk(fileId, type, template) {
|
|
|
|
const file = store.state.file.itemMap[fileId];
|
|
|
|
return this.applyTemplate(fileId, template)
|
2017-11-04 16:59:48 +00:00
|
|
|
.then((html) => {
|
|
|
|
const blob = new Blob([html], {
|
2017-09-17 15:32:39 +00:00
|
|
|
type: 'text/plain;charset=utf-8',
|
|
|
|
});
|
|
|
|
FileSaver.saveAs(blob, `${file.name}.${type}`);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
};
|