diff options
-rw-r--r-- | generator/listrenderer.js | 8 | ||||
-rwxr-xr-x | generator/main.js | 3 | ||||
-rwxr-xr-x | generator/make4htwrapper.js | 105 | ||||
-rw-r--r-- | generator/package.json | 12 | ||||
-rw-r--r-- | generator/postrenderer.js | 76 | ||||
-rw-r--r-- | generator/scanner.js | 5 |
6 files changed, 183 insertions, 26 deletions
diff --git a/generator/listrenderer.js b/generator/listrenderer.js index ce360af..e1fd797 100644 --- a/generator/listrenderer.js +++ b/generator/listrenderer.js @@ -6,7 +6,7 @@ module.exports={ }; const fs=require('fs'); const jsdom=require('jsdom'); -const path=require('path'); +const path=require('path'); let template_file=''; let tmplcont=''; @@ -20,7 +20,7 @@ async function _render(outf,pginfo,posts) { const tr=new jsdom.JSDOM(tmplcont); const trd=tr.window.document; - + const tags_list=trd.getElementById('tagslist'); const posts_list=trd.getElementById('postslist'); const atag_template=trd.getElementById('active_taglist_item_template'); @@ -64,7 +64,7 @@ async function _render(outf,pginfo,posts) atag_template.parentNode.removeChild(atag_template); tag_template.parentNode.removeChild(tag_template); pitm_template.parentNode.removeChild(pitm_template); - - fs.writeFileSync(outf,trd.documentElement.outerHTML,'utf8'); + + fs.writeFileSync(outf,"<!DOCTYPE html>"+trd.documentElement.outerHTML,'utf8'); return outf; } diff --git a/generator/main.js b/generator/main.js index e25d5f2..ce6c8ff 100755 --- a/generator/main.js +++ b/generator/main.js @@ -10,7 +10,7 @@ const fs=require('fs'); const path=require('path'); const scanner=require('./scanner'); -const list=scanner.scan(content_dir,dest_dir); +let list=scanner.scan(content_dir,dest_dir); const tags=scanner.build_list_index(); const taglist=Object.keys(tags).sort(); const force=process.argv.indexOf('--force')!=-1; @@ -49,6 +49,7 @@ if(i.needsupdate||force) const listrenderer=require('./listrenderer'); listrenderer.set_template(path.join(template_dir,'list_template')); +list = list.filter((t) => (t.date.indexOf('UNLISTED') == -1)); const list_dir=path.join(dest_dir,'list'); const ppp=posts_per_listpage; ensure_dir(list_dir); diff --git a/generator/make4htwrapper.js b/generator/make4htwrapper.js new file mode 100755 index 0000000..b8e0c3a --- /dev/null +++ b/generator/make4htwrapper.js @@ -0,0 +1,105 @@ +#!/usr/bin/node +//Copyright Chris Xiong 2024 +//License: Expat (MIT) + +const content_dir='../content'; +const image_dir='../content/img'; + +const process = require('node:process'); +const path = require('node:path'); +const util = require('node:util'); +const fs = require('node:fs'); +const jsdom = require('jsdom'); +const spawn = require('child_process').spawn; +const image_size = require('image-size'); + +function promote_header(doc) +{ + for (let i = 2; i <= 6; ++i) + { + const list = doc.querySelectorAll(`h${i}`); + for (let j = list.length - 1; j >= 0; --j) + { + const e = list[j]; + const newe = doc.createElement(`h${i-1}`); + newe.innerHTML = e.innerHTML; + e.parentNode.replaceChild(newe, e); + } + } +} + +function image_unsquash(doc, wd) +{ + for (let im of doc.querySelectorAll('img')) + { + const d = image_size(path.join(wd, im.src)); + if (d.width > d.height) + im.removeAttribute('width'); + else if (d.width < d.height) + im.removeAttribute('height'); + } +} + +function image_relocate(doc, wd, imd) +{ + for (let im of doc.querySelectorAll('img')) + { + fs.copyFileSync(path.join(wd, im.src), path.join(imd, im.src)); + im.src = `//filestorage.chrisoft.org/blog/img/${im.src}`; + } +} + +function css_embed(doc, cssc) +{ + const style = doc.createElement('style'); + style.textContent = cssc.replaceAll('font-family', 'not-font-family'); + const body = doc.querySelector('body'); + body.insertBefore(style, body.childNodes[0]); +} + +async function main() +{ + process.stdin.setEncoding('utf8'); + + const input = await new Promise((resolv, rej) => { + let d = ''; + process.stdin.on('readable', () => { const r = process.stdin.read(); if (r) d += r; }); + process.stdin.on('end',() => { resolv(d.trim()); }); + }); + + const wd = path.resolve(path.join(content_dir, path.dirname(input))); + const fn = path.basename(input); + const fnb = path.basename(input, '.tex'); + + const make4htp = spawn('make4ht', ['-f', 'html5', fn, '"fn-in"'], { + cwd: wd, + shell: true, + stdio: ['ignore', 'pipe', 'pipe'] + }); + make4htp.stdout.setEncoding('utf8'); + make4htp.stderr.setEncoding('utf8'); + make4htp.stdout.on('data', (d) => { console.error(d); }); + make4htp.stderr.on('data', (d) => { console.error(d); }); + try { + await new Promise((resolv, rej) => { + make4htp.on('close', (r) => r ? rej(r) : resolv()) + }); + } catch (e) { + console.error(`make4ht failed with exitcode ${e}`); + return; + } + + const htmlc = fs.readFileSync(path.join(wd, `${fnb}.html`), 'utf8'); + const cssc = fs.readFileSync(path.join(wd, `${fnb}.css`), 'utf8'); + const domrt = new jsdom.JSDOM(htmlc); + + promote_header(domrt.window.document); + image_unsquash(domrt.window.document, wd); + image_relocate(domrt.window.document, wd, path.resolve(image_dir)); + css_embed(domrt.window.document, cssc); + + process.stdout.setEncoding('utf8'); + process.stdout.write(domrt.window.document.querySelector('body').innerHTML); +} + +main() diff --git a/generator/package.json b/generator/package.json new file mode 100644 index 0000000..84473e2 --- /dev/null +++ b/generator/package.json @@ -0,0 +1,12 @@ +{ + "dependencies": { + "aes-js": "^3.1.2", + "btoa": "^1.2.1", + "child_process": "^1.0.2", + "fs": "^0.0.1-security", + "image-size": "^1.1.1", + "jsdom": "^24.0.0", + "path": "^0.12.7", + "scrypt-js": "^3.0.1" + } +} diff --git a/generator/postrenderer.js b/generator/postrenderer.js index f3cf46b..80991a0 100644 --- a/generator/postrenderer.js +++ b/generator/postrenderer.js @@ -22,10 +22,20 @@ let tmplcont=''; let tocid; let headerlist; +function texts(n) +{ + if (typeof n.wholeText == 'string') + return n.wholeText; + let ret = ''; + for (let c of n.childNodes) + ret += texts(c); + return ret; +} + function _dfs(doc,el,le,p) { var e=doc.createElement('li'); - e.innerHTML=`<a class="toctarg" href="#tocanch${tocid}">${el.innerHTML}</a>`; + e.innerHTML=`<a class="toctarg" href="#tocanch${tocid}">${texts(el)}</a>`; le.appendChild(e); el.id='tocanch'+(tocid++); el.classList.add('tvis'); @@ -86,10 +96,10 @@ function gen_thumb(doc,el) 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 dstfn=(p>0||w>0||h>0)?`ssbsthumb_${p>0?'s'+p.toFixed(2):w+'x'+h}_${srcfn}`:srcfn; const srcf=path.join(THUMB_LOCAL_DIR,srcfn); const dstf=path.join(THUMB_LOCAL_DIR,dstfn); - if(!util.mtime_cmp(srcf,dstf)) + if(dstfn!=srcfn&&!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}`);}); @@ -110,11 +120,8 @@ async function encrypt(doc) 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 saltu8=aes.utils.utf8.toBytes('mmmdeliciousthermalpasteletmeconsumeallofit'); + const enckey=scrypt.syncScrypt(keyu8, saltu8, 1024, 16, 2, 32); 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]); @@ -133,6 +140,14 @@ function _set_template(tmplf) template_file=tmplf; tmplcont=fs.readFileSync(template_file,'utf8'); } +function _style_to_header(doc) +{ + const head = doc.querySelector('head'); + const body = doc.querySelector('body'); + const styles = body.querySelectorAll('style'); + for (let s of styles) + head.appendChild(s); +} async function _render(inf,outf,np,pp) { const postcont=fs.readFileSync(inf,'utf8'); @@ -142,6 +157,12 @@ async function _render(inf,outf,np,pp) const contsplit=cont.split('\n'); const meta=contsplit.splice(0,4); const preproc=meta[3].trim(); + let unlisted = false; + if (meta[1].indexOf('UNLISTED') != -1) + { + unlisted = true; + meta[1] = meta[1].substr(0, meta[1].indexOf('UNLISTED')); + } trd.getElementById('title').innerHTML= trd.getElementById('titleh').innerHTML=meta[0].trim(); trd.getElementById('datetags').innerHTML=`${meta[1].trim()}<br>#${meta[2].split(',').join(' #')}`; @@ -149,23 +170,28 @@ async function _render(inf,outf,np,pp) { const ppargs=preproc.split(' '); const ppcmd=ppargs.splice(0,1); - const pp=spawn(ppcmd[0],ppargs,{maxBuffer:1048576}); + const pp=spawn(ppcmd[0],ppargs,{maxBuffer:1048576, stdio:['pipe', 'pipe', 'inherit']}); 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)=> + const ppp = new Promise( + (resolv, rej)=> { - pp.on('close',(r)=>{if(r)rej(r);resolv(out);}); + pp.on('error', (e) => rej(e)); + pp.on('close',(r) => { if(r) rej(r); resolv(out); }); } ); pp.stdin.end(); - trd.getElementById('article').innerHTML=await ppp; + try { + trd.getElementById('article').innerHTML = await ppp; + } catch (e) { + console.log(`failed to render ${inf} : ${e}`); + } } 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');} @@ -186,7 +212,7 @@ async function _render(inf,outf,np,pp) 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); @@ -198,11 +224,21 @@ async function _render(inf,outf,np,pp) l.innerHTML=`<a href="/blog/list/${tgs[i]}/">${tgs[i]}</a>`; trd.getElementById('tagslist').appendChild(l); } - + + _style_to_header(trd); + //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'); + if (unlisted) + { + trd.getElementById('prevp').parentNode.remove(); + trd.getElementById('nextp').parentNode.remove(); + } + else + { + if(pp)trd.getElementById('prevp').href=`${pp}.html`; + if(np)trd.getElementById('nextp').href=`${np}.html`; + } + + fs.writeFileSync(outf,"<!DOCTYPE html>"+trd.documentElement.outerHTML,'utf8'); return outf; } diff --git a/generator/scanner.js b/generator/scanner.js index 52744a4..c8c1069 100644 --- a/generator/scanner.js +++ b/generator/scanner.js @@ -34,18 +34,21 @@ function _scan(s,dst) mdate:fs.statSync(path.join(s,i)).mtimeMs, needsupdate:!util.mtime_cmp(path.join(s,i),path.join(pdst,i.substring(0,i.length-4)+'.html')) }); - + } return list; } function _build_list_index() { for(let i of list) + { + if (i.date.indexOf('UNLISTED') != -1) continue; for(let tg of i.tags.split(',')) { if(!tags[tg])tags[tg]={posts:[],needsupdate:false}; tags[tg].posts.push(i); tags[tg].needsupdate|=i.needsupdate; } + } return tags; } |