Added support for loading posts

This commit is contained in:
Jocelyn Badgley (Twipped) 2020-02-29 16:27:55 -08:00
parent 56d5f33453
commit 27621e0edd
10 changed files with 216 additions and 88 deletions

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ node_modules
/bs-manifest.json /bs-manifest.json
/bs-cache /bs-cache
/pages.json /pages.json
/posts.json
/if-* /if-*
/twitter-cache.json /twitter-cache.json
/twitter-config.json /twitter-config.json

View File

@ -28,19 +28,19 @@ module.exports = exports = class File {
file.basename = basename = basename.slice(1); file.basename = basename = basename.slice(1);
} }
// remove the public root and any _images segment from the dir
const dir = this._dir(file.dir);
this.kind = kind(filepath); this.kind = kind(filepath);
this.type = type(filepath); this.type = type(filepath);
this.input = filepath; // public/file.ext
this.cwd = file.dir; this.cwd = file.dir;
this.ext = this.preprocessed ? file.ext : normalizedExt(file.ext); this.ext = this.preprocessed ? file.ext : normalizedExt(file.ext);
this.input = filepath; // public/file.ext
this.base = path.join(...dir); // '', 'folder', 'folder/subfolder'
this.dir = path.join('/', ...dir); // /, /folder, /folder/subfolder
this.name = name; // index, fileA, fileB this.name = name; // index, fileA, fileB
this.basename = basename; // index.ext, fileA.ext, fileB.ext this.basename = basename; // index.ext, fileA.ext, fileB.ext
this.ext = file.ext;
const dir = this._dir(file.dir);
if (dir) {
this.base = path.join(...dir); // '', 'folder', 'folder/subfolder'
this.dir = path.join('/', ...dir); // /, /folder, /folder/subfolder
}
this._out(); this._out();

71
build/files.js Normal file
View File

@ -0,0 +1,71 @@
const path = require('path');
const { groupBy, keyBy, filter, find, get, memoize } = require('lodash');
const { kind, KIND } = require('./resolve');
const File = require('./file');
const Asset = require('./asset');
const Page = require('./page');
module.exports = exports = class Files {
constructor (paths, base = '') {
this.KIND_MAP = this._kindMap();
this.base = base;
this.files = paths.map(this._parsePath.bind(this)).filter(Boolean);
const {
[KIND.PAGE]: pages,
[KIND.ASSET]: assets,
} = groupBy(this.files, 'kind');
this.pages = pages || [];
this.assets = assets || [];
this._getTitlecard = memoize(() =>
get(find(this.files, { name: 'titlecard', dir: this.base }), [ 0, 'url' ]),
);
this._getWebReady = memoize(() => assets && keyBy(assets.map((a) => a.webready()), 'name'));
this.for = memoize(this.for);
}
get all () {
return this.files;
}
get titlecard () {
return this._getTitlecard();
}
get webready () {
return this._getWebReady();
}
get tasks () {
return this.files.map((a) => a.tasks()).flat(1);
}
for (dir) {
dir = path.join(this.base, dir);
const subset = filter(this.files, { dir });
return new this.constructor(subset, dir);
}
_kindMap () {
return {
[KIND.PAGE]: Page,
[KIND.ASSET]: Asset,
[KIND.OTHER]: File,
};
}
_parsePath (filepath) {
if (typeof filepath === 'object') return filepath;
const k = kind(filepath);
const F = this.KIND_MAP[k];
const f = new F(filepath);
if (f.kind === KIND.PAGE && f.preprocessed) return false;
return f;
}
};

View File

@ -1,5 +1,8 @@
process.env.BLUEBIRD_DEBUG = true;
const loadPublicFiles = require('./public'); const loadPublicFiles = require('./public');
const loadPostFiles = require('./posts');
const Cache = require('./cache'); const Cache = require('./cache');
const Promise = require('bluebird'); const Promise = require('bluebird');
const fs = require('fs-extra'); const fs = require('fs-extra');
@ -16,21 +19,31 @@ const scripts = require('./scripts');
exports.everything = function (prod = false) { exports.everything = function (prod = false) {
const fn = async () => { async function fn () {
// load a directory scan of the public folder // load a directory scan of the public and post folders
const PublicFiles = await loadPublicFiles(); const [ PublicFiles, PostFiles ] = await Promise.all([
loadPublicFiles(),
loadPostFiles(),
]);
// load data for all the files in that folder // load data for all the files in that folder
await Promise.map(PublicFiles.assets, (p) => p.load()); await Promise.map(PublicFiles.assets, (p) => p.load());
await Promise.map(PublicFiles.pages, (p) => p.load(PublicFiles)); await Promise.map(PublicFiles.pages, (p) => p.load(PublicFiles));
await Promise.map(PostFiles.assets, (p) => p.load());
await Promise.map(PostFiles.pages, (p) => p.load(PostFiles));
// prime tweet data for all pages // prime tweet data for all pages
const pages = await primeTweets(PublicFiles.pages); const pages = await primeTweets(PublicFiles.pages);
const posts = await primeTweets(PostFiles.pages);
// compile all tasks to be completed // compile all tasks to be completed
const tasks = await Promise.all([ const tasks = await Promise.all([
PublicFiles.tasks, PublicFiles.tasks,
PostFiles.tasks,
scss(prod), scss(prod),
scripts(prod), scripts(prod),
svg(prod), svg(prod),
@ -38,6 +51,7 @@ exports.everything = function (prod = false) {
]); ]);
await fs.writeFile(resolve('pages.json'), JSON.stringify(pages.map((p) => p.toJson()), null, 2)); await fs.writeFile(resolve('pages.json'), JSON.stringify(pages.map((p) => p.toJson()), null, 2));
await fs.writeFile(resolve('posts.json'), JSON.stringify(posts.map((p) => p.toJson()), null, 2));
await fs.ensureDir(resolve('dist')); await fs.ensureDir(resolve('dist'));
const cache = new Cache({ prod }); const cache = new Cache({ prod });
@ -45,10 +59,9 @@ exports.everything = function (prod = false) {
await evaluate(tasks.flat(), cache); await evaluate(tasks.flat(), cache);
await cache.save(); await cache.save();
await pageWriter(pages, prod); await pageWriter([ ...pages, ...posts ], prod);
}; }
const ret = () => fn().catch((err) => { console.log(err.trace || err); throw err; }); fn.displayName = prod ? 'buildForProd' : 'build';
ret.displayName = prod ? 'generateEverythingForProd' : 'generateEverything'; return fn;
return ret;
}; };

View File

@ -44,7 +44,7 @@ module.exports = exports = async function writePageContent (pages, prod) {
preview: page.engine === 'md' && String(engines.preview(data.source, data)), preview: page.engine === 'md' && String(engines.preview(data.source, data)),
}; };
const output = resolve('dist', page.output); const output = resolve('dist', page.out);
await fs.ensureDir(path.dirname(output)); await fs.ensureDir(path.dirname(output));
await Promise.all([ await Promise.all([
fs.writeFile(output, Buffer.from(html)), fs.writeFile(output, Buffer.from(html)),

View File

@ -31,23 +31,27 @@ module.exports = exports = class Page extends File {
'flags', 'flags',
); );
this.engine = ENGINE[this.type] || ENGINE.COPY;
}
_out () {
var isIndexPage = (this.name === 'index'); var isIndexPage = (this.name === 'index');
var isClean = isCleanUrl(this.ext); var isClean = isCleanUrl(this.ext);
if (isClean && isIndexPage) { if (isClean && isIndexPage) {
this.output = path.join(this.base, 'index.html'); this.out = path.join(this.base, 'index.html');
this.json = path.join(this.base, 'index.json'); this.json = path.join(this.base, 'index.json');
this.url = this.dir; this.url = this.dir;
} else if (isClean) { } else if (isClean) {
this.output = path.join(this.base, this.name, 'index.html'); this.out = path.join(this.base, this.name, 'index.html');
this.json = path.join(this.base, this.name + '.json'); this.json = path.join(this.base, this.name + '.json');
this.url = path.join(this.dir, this.name); this.url = path.join(this.dir, this.name);
} else if (isIndexPage) { } else if (isIndexPage) {
this.output = path.join(this.base, 'index.html'); this.out = path.join(this.base, 'index.html');
this.json = path.join(this.base, this.name + '.json'); this.json = path.join(this.base, this.name + '.json');
this.url = this.dir; this.url = this.dir;
} else { } else {
this.output = path.join(this.base, this.basename); this.out = path.join(this.base, this.basename);
this.json = path.join(this.base, this.basename + '.json'); this.json = path.join(this.base, this.basename + '.json');
this.url = path.join(this.dir, this.basename); this.url = path.join(this.dir, this.basename);
} }
@ -55,8 +59,6 @@ module.exports = exports = class Page extends File {
const url = new URL(pkg.siteInfo.siteUrl); const url = new URL(pkg.siteInfo.siteUrl);
url.pathname = this.url; url.pathname = this.url;
this.fullurl = url.href; this.fullurl = url.href;
this.engine = ENGINE[this.type] || ENGINE.COPY;
} }
async load (PublicFiles) { async load (PublicFiles) {
@ -65,8 +67,6 @@ module.exports = exports = class Page extends File {
fs.stat(this.input).catch(() => ({})), fs.stat(this.input).catch(() => ({})),
]); ]);
const { titlecard, assets } = PublicFiles.for(this.dir);
// empty file // empty file
if (!raw || !ctime) { if (!raw || !ctime) {
log.error('Could not load page: ' + this.filepath); log.error('Could not load page: ' + this.filepath);
@ -82,20 +82,28 @@ module.exports = exports = class Page extends File {
this.source = body; this.source = body;
this.meta = meta; this.meta = meta;
this.images = assets;
this.titlecard = titlecard;
this.tweets = (meta.tweets || []).map(parseTweetId);
this.dateCreated = meta.date && new Date(meta.date) || ctime; this.dateCreated = meta.date && new Date(meta.date) || ctime;
this.dateModified = mtime; this.dateModified = mtime;
this.classes = Array.from(new Set(meta.classes || [])); this._parse(PublicFiles);
return this;
}
_parse (PublicFiles) {
const { titlecard, webready } = PublicFiles.for(this.dir);
this.images = webready;
this.titlecard = titlecard;
this.tweets = (this.meta.tweets || []).map(parseTweetId);
this.classes = Array.from(new Set(this.meta.classes || []));
this.flags = this.classes.reduce((res, item) => { this.flags = this.classes.reduce((res, item) => {
var camelCased = item.replace(/-([a-z])/g, (g) => g[1].toUpperCase()); var camelCased = item.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
res[camelCased] = true; res[camelCased] = true;
return res; return res;
}, {}); }, {});
return this;
} }
tasks () { tasks () {

16
build/post-asset.js Normal file
View File

@ -0,0 +1,16 @@
const { without } = require('lodash');
const Asset = require('./asset');
const postmatch = /(\d{4}-\d\d-\d\d)\.\d{4}\.(\w+)/;
module.exports = exports = class PostAsset extends Asset {
_dir (dir) {
dir = dir.replace(postmatch, '$2').split('/');
dir = without(dir, 'posts', '_images');
dir.unshift('p');
return dir;
}
};

54
build/post.js Normal file
View File

@ -0,0 +1,54 @@
const path = require('path');
const { without } = require('lodash');
const { resolve, isCleanUrl } = require('./resolve');
const Page = require('./page');
const pkg = require(resolve('package.json'));
const postmatch = /(\d{4}-\d\d-\d\d)\.\d{4}\.(\w+)/;
module.exports = exports = class Post extends Page {
_dir (dir) {
// if the file name matches the postmatch pattern, then this needs to be /p/ file
const match = this.name.match(postmatch);
if (match) {
return [ 'p', match[2] ];
}
dir = dir.replace(postmatch, '$2').split('/');
dir = without(dir, 'posts', '_images');
dir.unshift('p');
return dir;
}
_out () {
var isIndexPage = (this.name === 'index' || this.name.match(postmatch));
var isClean = isCleanUrl(this.ext);
if (isClean && isIndexPage) {
this.out = path.join(this.base, 'index.html');
this.json = path.join(this.base, 'index.json');
this.url = this.dir;
} else if (isClean) {
this.out = path.join(this.base, this.name, 'index.html');
this.json = path.join(this.base, this.name + '.json');
this.url = path.join(this.dir, this.name);
} else if (isIndexPage) {
this.out = path.join(this.base, 'index.html');
this.json = path.join(this.base, this.name + '.json');
this.url = this.dir;
} else {
this.out = path.join(this.base, this.basename);
this.json = path.join(this.base, this.basename + '.json');
this.url = path.join(this.dir, this.basename);
}
const url = new URL(pkg.siteInfo.siteUrl);
url.pathname = this.url;
this.fullurl = url.href;
}
};

21
build/posts.js Normal file
View File

@ -0,0 +1,21 @@
const glob = require('./lib/glob');
const { ROOT, KIND } = require('./resolve');
const File = require('./file');
const Asset = require('./post-asset');
const Post = require('./post');
const Files = require('./files');
class PostFiles extends Files {
_kindMap () {
return {
[KIND.PAGE]: Post,
[KIND.ASSET]: Asset,
[KIND.OTHER]: File,
};
}
}
module.exports = exports = async function loadPublicFiles () {
return new PostFiles(await glob('posts/**/*', { cwd: ROOT, nodir: true }));
};

View File

@ -1,64 +1,8 @@
const glob = require('./lib/glob'); const glob = require('./lib/glob');
const { groupBy, keyBy, filter, find, get, memoize } = require('lodash'); const { ROOT } = require('./resolve');
const { ROOT, kind, KIND } = require('./resolve');
const File = require('./file');
const Asset = require('./asset');
const Page = require('./page');
const Promise = require('bluebird');
const KIND_MAP = { const Files = require('./files');
[KIND.PAGE]: Page,
[KIND.ASSET]: Asset,
[KIND.OTHER]: File,
};
module.exports = exports = async function loadPublicFiles () { module.exports = exports = async function loadPublicFiles () {
const files = await Promise.map(glob('public/**/*', { cwd: ROOT, nodir: true }), (filepath) => { return new Files(await glob('public/**/*', { cwd: ROOT, nodir: true }));
const k = kind(filepath);
const F = KIND_MAP[k];
const f = new F(filepath);
if (f.kind === KIND.PAGE && f.preprocessed) return false;
return f;
}).filter(Boolean);
const {
[KIND.PAGE]: pages,
[KIND.ASSET]: assets,
} = groupBy(files, 'kind');
function within (dir) {
const subset = filter(files, { dir });
const getTitlecard = memoize(() =>
get(find(files, { name: 'titlecard' }), [ 0, 'url' ]),
);
const {
[KIND.PAGE]: subpages,
[KIND.ASSET]: subassets,
} = groupBy(subset, 'kind');
const webready = subassets && keyBy(subassets.map((a) => a.webready()), 'name');
return {
all: subset,
get titlecard () { return getTitlecard; },
get pages () {
return subpages;
},
get assets () {
return webready;
},
};
}
return {
all: files,
pages,
assets,
for: memoize(within),
get tasks () {
return files.map((a) => a.tasks()).flat(1);
},
};
}; };