aboutsummaryrefslogblamecommitdiff
path: root/generator/postrenderer.js
blob: f3cf46b2135e822c80c683dca4ea066525c4d273 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14

                            











                                                                         




                                                             






























                                                                                     

                                                                   


                                             





                                                                
                                                                     



                                                                                                                            



                                                     































                                                                                                























                                                                                                           
                                                                                             







































                                                                                                          







                                                                                                    




                           

                      



                                                                                 




                                                                                        
 















                                                                            
//Copyright Chris Xiong 2018
//License: Expat (MIT)
module.exports={
	render:function(inf,outf,np,pp){return _render(inf,outf,np,pp);},
	set_template:function(tmplf){return _set_template(tmplf);}
};
const fs=require('fs');
const jsdom=require('jsdom');
const path=require('path');
const aes=require('aes-js');
const scrypt=require('scrypt-js');
const btoa=require('btoa');
const sha256=require('./sha256').sha256;
const spawn=require('child_process').spawn;
const util=require('./util');

const THUMB_IMAGE_URL='//filestorage.chrisoft.org/blog/img/';
const THUMB_LOCAL_DIR='../content/img/';

let template_file='';
let tmplcont='';
let tocid;
let headerlist;

function _dfs(doc,el,le,p)
{
	var e=doc.createElement('li');
	e.innerHTML=`<a class="toctarg" href="#tocanch${tocid}">${el.innerHTML}</a>`;
	le.appendChild(e);
	el.id='tocanch'+(tocid++);
	el.classList.add('tvis');
	var che=null,i;
	for(i=p+1;i<headerlist.length;)
	{
		if(headerlist[i].tagName<=el.tagName)break;
		if(headerlist[i].classList.contains('notoc'))continue;
		if(che===null)
		{
			var te=doc.createElement('li');
			che=doc.createElement('ul');
			che.classList.add('tocnode');
			te.appendChild(che);
			le.appendChild(te);
		}
		i=_dfs(doc,headerlist[i],che,i);
	}
	return i;
}
function footnoter(doc)
{
	var footnotes=doc.body.getElementsByTagName('footnote');
	let starting=doc.getElementById('notediv').children.length;
	for(var i=0;i<footnotes.length;++i)
	{
		var s=footnotes[i].innerHTML;
		footnotes[i].innerHTML='';
		var a=doc.createElement('a');
		a.setAttribute('id',`n${(starting+i+1)}`);
		a.setAttribute('href',`#note${(starting+i+1)}`);
		a.setAttribute('class','note');
		a.innerHTML=`[${(starting+i+1)}]`;
		footnotes[i].parentNode.insertBefore(a,footnotes[i]);
		var span=doc.createElement('span');
		span.setAttribute('class','TText');
		span.innerHTML=`<a id="note${(starting+i+1)}" href="#n${(starting+i+1)}">[${(starting+i+1)}]</a>: ${s}<br>`;
		doc.getElementById('notediv').appendChild(span);
	}
	starting+=footnotes.length;
	while(footnotes.length)footnotes[0].remove();
}
function gen_thumb(doc,el)
{
	const a=doc.createElement('a');
	const im=doc.createElement('img');
	const srcfn=el.innerHTML;
	let w=el.hasAttribute('width')?Number(el.getAttribute('width')):-1;
	let h=el.hasAttribute('height')?Number(el.getAttribute('height')):-1;
	let p=el.hasAttribute('scale')?Number(el.getAttribute('scale')):-1;
	if(el.hasAttribute('iw'))
	im.style.width=el.getAttribute('iw');
	if(el.hasAttribute('ih'))
	im.style.height=el.getAttribute('ih');
	if(p>0)w=h=-1;
	if(w>0||h>0)
	{
		if(w<=0)w=h;
		else if(h<=0)h=w;
	}
	const dstfn=`ssbsthumb_${p>0?'s'+p.toFixed(2):w+'x'+h}_${srcfn}`
	const srcf=path.join(THUMB_LOCAL_DIR,srcfn);
	const dstf=path.join(THUMB_LOCAL_DIR,dstfn);
	if(!util.mtime_cmp(srcf,dstf))
	{
		const m=spawn('magick',[srcf,'-resize',p>0?(p*100).toFixed()+'%':w+'x'+h,dstf]);
		m.stdout.on('data',(d)=>{console.log(`IMout:${d}`);});
		m.stderr.on('data',(d)=>{console.log(`IMerr:${d}`);});
	}
	a.setAttribute('href',THUMB_IMAGE_URL+srcfn);
	im.src=THUMB_IMAGE_URL+dstfn;
	a.appendChild(im);
	el.parentNode.replaceChild(a,el);
}
async function encrypt(doc)
{
	let enid=0;
	for(let i of doc.querySelectorAll('encrypted'))
	{
		const key=i.getAttribute('key');
		i.removeAttribute('key');
		const contu8=aes.utils.utf8.toBytes(i.innerHTML);
		const conths=sha256.hash(aes.utils.hex.fromBytes(contu8),{msgFormat:'hex-bytes'});
		const keyu8=aes.utils.utf8.toBytes(key);
		const saltu8=aes.utils.utf8.toBytes('hellwhymustiaddsalttothiscrap');
		const enckey=await new Promise(
			(resolv,rej)=>
			{scrypt(keyu8,saltu8,1024,16,2,32,(e,p,k)=>{e?rej(e):k?resolv(k):undefined;});}
		);
		const ctr=new aes.ModeOfOperation.ctr(enckey);
		const enccont=ctr.encrypt(contu8);
		let encconts='';for(let i=0;i<enccont.length;++i)encconts+=String.fromCharCode(enccont[i]);
		encconts=btoa(encconts);
		i.setAttribute('hash',conths);
		i.setAttribute('encont',encconts);
		i.setAttribute('enid',enid);
		i.innerHTML=
`This section is encrypted. Click <a href="javascript:void(0)" onclick="decryptui(${enid})">here</a>
to decrypt. <noscript>Note that you need have JavaScript enabled for decryption.</noscript>`;
		++enid;
	}
}
function _set_template(tmplf)
{
	template_file=tmplf;
	tmplcont=fs.readFileSync(template_file,'utf8');
}
async function _render(inf,outf,np,pp)
{
	const postcont=fs.readFileSync(inf,'utf8');
	const tr=new jsdom.JSDOM(tmplcont);
	const trd=tr.window.document;
	const cont=fs.readFileSync(inf,'utf8');
	const contsplit=cont.split('\n');
	const meta=contsplit.splice(0,4);
	const preproc=meta[3].trim();
	trd.getElementById('title').innerHTML=
	trd.getElementById('titleh').innerHTML=meta[0].trim();
	trd.getElementById('datetags').innerHTML=`${meta[1].trim()}<br>#${meta[2].split(',').join(' #')}`;
	if(preproc.length)
	{
		const ppargs=preproc.split(' ');
		const ppcmd=ppargs.splice(0,1);
		const pp=spawn(ppcmd[0],ppargs,{maxBuffer:1048576});
		let out='';
		pp.stdout.setEncoding('utf8');
		pp.stdout.on('data',(d)=>{out+=d;});
		pp.stdin.write(contsplit.join('\n'),'utf8');
		const ppp=new Promise(
			(resolv,rej)=>
			{
				pp.on('close',(r)=>{if(r)rej(r);resolv(out);});
			}
		);
		pp.stdin.end();
		trd.getElementById('article').innerHTML=await ppp;
	}
	else
	trd.getElementById('article').innerHTML=contsplit.join('\n');
	
	//async images
	trd.getElementById('article').querySelectorAll('img').forEach(
		(i)=>{if(!i.getAttribute('decoding'))i.setAttribute('decoding','async');}
	);

	//thumbnails generation
	trd.getElementById('article').querySelectorAll('thumb').forEach((el)=>{gen_thumb(trd,el);});

	//Encryption
	await encrypt(trd);

	//TOC
	tocid=0;
	headerlist=[];
	trd.getElementById('article').querySelectorAll('h2,h3,h4,h5,h6').forEach(
		(i)=>{if(!i.classList.contains('notoc'))headerlist.push(i);}
	);
	const tocroot=trd.getElementById('tocroot');
	for(let i=0;i<headerlist.length;)i=_dfs(trd,headerlist[i],tocroot,i);
	if(!tocroot.children.length)trd.getElementById('tocouter').style.display='none';
	
	//Footnotes
	footnoter(trd);

	//Tag list
	const tgs=meta[2].split(',');
	for(let i=0;i<tgs.length;++i)
	{
		let l=trd.createElement('li');
		l.innerHTML=`<a href="/blog/list/${tgs[i]}/">${tgs[i]}</a>`;
		trd.getElementById('tagslist').appendChild(l);
	}
	
	//neighboring posts
	if(pp)trd.getElementById('prevp').href=`${pp}.html`;
	if(np)trd.getElementById('nextp').href=`${np}.html`;
		
	fs.writeFileSync(outf,trd.documentElement.outerHTML,'utf8');
	return outf;
}