From abb10f112aa1248700e98caa0273f6afc5154a47 Mon Sep 17 00:00:00 2001 From: Chris Xiong Date: Sat, 22 Jun 2024 20:07:43 -0400 Subject: Yes we are 2.6 now. --- README.rst | 5 +++-- generator/atomgen.js | 46 ++++++++++++++++++++++++++++++++++++++++++++++ generator/main.js | 35 ++++++++++++++++++++++++----------- generator/package.json | 2 +- generator/postrenderer.js | 9 +-------- generator/scanner.js | 10 ++++++++++ generator/util.js | 11 ++++++++++- templates/list_template | 2 +- templates/post_template | 2 +- 9 files changed, 97 insertions(+), 25 deletions(-) create mode 100644 generator/atomgen.js diff --git a/README.rst b/README.rst index feee8fc..670a191 100644 --- a/README.rst +++ b/README.rst @@ -10,10 +10,11 @@ List of features of the SBS: - Automatic table of contents and footnotes generation - Generates static webpages, ready to be served with httpd - Flexible templates - - Automatic image compression (TODO, using imagemagick) + - Automatic image compression + - Atom feed generation Dependencies: - - node.js >8.x + - node.js >20.x - jsdom - aes-js - scrypt-js diff --git a/generator/atomgen.js b/generator/atomgen.js new file mode 100644 index 0000000..ede9771 --- /dev/null +++ b/generator/atomgen.js @@ -0,0 +1,46 @@ +//Copyright Chris Xiong 2024 +//License: Expat (MIT) +module.exports = { + gen_atom:function(list, config) { _gen_atom(list, config); } +}; +const jsdom=require('jsdom'); +const path=require('path'); +const fs=require('fs'); +const htmlescape=require('./util').htmlescape; + +function _gen_atom(list, config) +{ + const sorted_list = list.toSorted((a, b) => (new Date(b.date)).getTime() - (new Date(a.date)).getTime()); + let xmlbuf = ``; + xmlbuf += ``; + xmlbuf += `SSBS`; + xmlbuf += `${new Date().toISOString()}`; + xmlbuf += ``; + xmlbuf += ``; + xmlbuf += `${config.atom_url}`; + xmlbuf += `${config.atom_title}`; + xmlbuf += `${config.atom_subtitle}`; + xmlbuf += `${config.atom_icon}`; + xmlbuf += `${config.atom_icon}`; + for (let i = 0; i < sorted_list.length && i < config.atom_feed_nposts; ++i) + { + const p = sorted_list[i]; + const postpath = path.join(path.join(config.dest_dir,'post'), `${p.file}.html`); + const contfull = fs.readFileSync(postpath, 'utf8'); + const doc = new jsdom.JSDOM(contfull).window.document; + const content = doc.querySelector('article').innerHTML; + const footnotes = doc.getElementById('notediv').outerHTML; + const link = `https://chrisoft.org/blog/post/${p.file}.html`; + xmlbuf += ``; + xmlbuf += `${htmlescape(p.title)}`; + xmlbuf += ``; + xmlbuf += `${new Date(p.date).toISOString()}`; + xmlbuf += `${new Date(p.mdate).toISOString()}`; + xmlbuf += `${link}`; + xmlbuf += `${config.atom_author}` + xmlbuf += `
${footnotes}]]>
`; + xmlbuf += `
`; + } + xmlbuf += `
`; + fs.writeFileSync(path.join(config.dest_dir, "atom.xml"), xmlbuf ,'utf8'); +} diff --git a/generator/main.js b/generator/main.js index ce6c8ff..3697dbd 100755 --- a/generator/main.js +++ b/generator/main.js @@ -1,22 +1,33 @@ #!/usr/bin/node //Copyright Chris Xiong 2018 //License: Expat (MIT) -const content_dir='../content'; -const template_dir='../templates'; -const dest_dir='../generated'; -const posts_per_listpage=5; +const config = { + content_dir: '../content', + template_dir: '../templates', + dest_dir: '../generated', + posts_per_listpage: 5, + // These are only used by the Atom feed generator + atom_feed_nposts: 10, + version_string: '2.6', + atom_url: 'https://chrisoft.org/blog/atom.xml', + published_url: 'https://chrisoft.org/blog/', + atom_title: 'Specluncam Ursae', + atom_subtitle: 'Chris Xiong\'s blog posts', + atom_author: 'Chris Xiong', + atom_icon: 'https://chrisoft.org/cx.png' +}; const fs=require('fs'); const path=require('path'); const scanner=require('./scanner'); -let list=scanner.scan(content_dir,dest_dir); +let list=scanner.scan(config.content_dir,config.dest_dir); const tags=scanner.build_list_index(); const taglist=Object.keys(tags).sort(); const force=process.argv.indexOf('--force')!=-1; const postrenderer=require('./postrenderer'); -postrenderer.set_template(path.join(template_dir,'post_template')); +postrenderer.set_template(path.join(config.template_dir,'post_template')); function ensure_dir(p) { @@ -32,13 +43,13 @@ function ensure_dir(p) if(!fs.statSync(p).isDirectory())throw 'shit'; } -const post_dir=path.join(dest_dir,'post'); +const post_dir=path.join(config.dest_dir,'post'); ensure_dir(post_dir); for(let j=0;j (t.date.indexOf('UNLISTED') == -1)); -const list_dir=path.join(dest_dir,'list'); -const ppp=posts_per_listpage; +const gen_atom = require('./atomgen').gen_atom; +gen_atom(list, config); +const list_dir=path.join(config.dest_dir,'list'); +const ppp=config.posts_per_listpage; ensure_dir(list_dir); let pc=Math.floor(list.length/ppp)+(list.length%ppp!=0); for(let i=0;i/g, ">") - .replace(/&/g, "&") - .replace(/"/g, """) - .replace(/'/g, "'"); -} async function _render(inf,outf,np,pp) { const postcont=fs.readFileSync(inf,'utf8'); diff --git a/generator/scanner.js b/generator/scanner.js index c8c1069..a1630d1 100644 --- a/generator/scanner.js +++ b/generator/scanner.js @@ -1,5 +1,15 @@ //Copyright Chris Xiong 2018 //License: Expat (MIT) +/* Returns all scanned posts in an array in the following format: + * { + * file: String = file name of post (excluding the .txt extension) + * title: String = title extracted from the post + * date: String = post date extracted from the post (expected to be an ISO8601 full-date string, with optional post-status suffix) + * tags: String = comma-separated list of tags + * mdate: Number = source last modification time in msec since epoch + * needsupdate: boolean = true if post needs re-rendering + * } + */ module.exports={ scan:function(s,d){return _scan(s,d);}, build_list_index:function(){return _build_list_index();} diff --git a/generator/util.js b/generator/util.js index e9b5c78..85e7ab3 100644 --- a/generator/util.js +++ b/generator/util.js @@ -1,7 +1,8 @@ //Copyright Chris Xiong 2019 //License: Expat (MIT) module.exports={ - mtime_cmp:function(a,b){return _mtime_cmp(a,b);} + mtime_cmp: function(a, b){ return _mtime_cmp(a, b); }, + htmlescape: function(s){ return _htmlescape(s); } }; const fs=require('fs'); @@ -17,3 +18,11 @@ function _mtime_cmp(a,b) }catch(e){return false}; return mtimeb>=mtimea; } +function _htmlescape(s) +{ + return s.replace(//g, ">") + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(/'/g, "'"); +} diff --git a/templates/list_template b/templates/list_template index 453a951..b589da0 100644 --- a/templates/list_template +++ b/templates/list_template @@ -62,7 +62,7 @@ function ol() >>
- Proudly powered by SSBS (the static stupid blogging system) 2.5 + Proudly powered by SSBS (the static stupid blogging system) 2.6
Content licensed under CC BY-SA 4.0.
diff --git a/templates/post_template b/templates/post_template index caa87b5..ad20598 100644 --- a/templates/post_template +++ b/templates/post_template @@ -68,7 +68,7 @@ function ol()