UNPKG

empress-blog

Version:

Fully-functional, SEO friendly static site implementation of a blog system built on Ember.

278 lines (233 loc) 8.15 kB
/* eslint-disable prettier/prettier */ 'use strict'; const _ = require('lodash'); const MergeTrees = require('broccoli-merge-trees'); const StaticSiteJson = require('broccoli-static-site-json'); const StaticSiteJsonXml = require('broccoli-static-site-json-xml'); const Funnel = require('broccoli-funnel'); const walkSync = require('walk-sync'); const yamlFront = require('yaml-front-matter'); const { readFileSync, existsSync } = require('fs'); const { join } = require('path'); const AuthorsArray = require('./lib/authors-array'); const TagGenerator = require('./lib/tag-generator'); const ItemIncludePosts = require('./lib/item-include-posts'); const AutomaticNewTag = require('./lib/automatic-new-tag'); module.exports = { name: require('./package').name, includedCommands: function() { return { 'empress-blog:import': require('./lib/import.js'), } }, config() { return { blog: {}, fastboot: { hostWhitelist: [/localhost:\d+/] }, } }, // isDevelopingAddon() { // return true; // }, treeForPublic() { const config = this.project.config(process.env.EMBER_ENV || 'development'); const blogConfig = config.blog || {}; let appPrefix = join(this.project.configPath(), '../..'); // TODO: once this is stable and added to documentation then we should enable this warning // if(_.isNil(blogConfig.paginate)) { // this.ui.writeWarnLine(`You have not set paginate to 'true' or 'false' in your blog config. In the next major version of empress-blog this will default to true // // Please make sure that the current version of your template supports pagination before turning it on // `); // } let contentFolder = join(appPrefix, 'content'); // apply backwards-compatability shim for single author attribute contentFolder = new AuthorsArray(contentFolder); // automatically add new tag to latest content contentFolder = new AutomaticNewTag(contentFolder); const contentTree = new StaticSiteJson(contentFolder, { type: 'content', attributes: [ 'canonical', 'date', 'excerpt', 'featured', 'image', 'imageMeta', 'language', 'meta', 'meta_description', 'meta_title', 'page', 'status', 'title', 'uuid', ], references: ['authors', 'tags'], contentFolder: 'content', collate: true, collationFileName: 'content.json', paginate: blogConfig.paginate, paginateSortFunction(a, b) { return new Date(b.data.attributes.date) - new Date(a.data.attributes.date); } }); const pageTree = new StaticSiteJson(new AuthorsArray(join(appPrefix, 'page')), { type: 'page', attributes: [ 'canonical', 'date', 'featured', 'image', 'imageMeta', 'language', 'meta', 'meta_description', 'meta_title', 'page', 'status', 'title', 'uuid', ], references: ['authors', 'tags'], contentFolder: 'page', collate: true, collationFileName: 'page.json', }); let authorFolder = join(appPrefix, 'author'); // include the post IDs into authors authorFolder = new ItemIncludePosts( new MergeTrees([ new Funnel(authorFolder, { destDir: 'author' }), new Funnel(contentFolder, { destDir: 'content' }) ]), { itemType: 'author', } ); const authorTree = new StaticSiteJson(authorFolder, { type: 'author', contentFolder: 'author', attributes: [ 'name', 'image', 'coverImage', 'coverMeta', 'bio', 'meta', 'website', 'twitter', 'facebook', 'location', ], collate: true, collationFileName: 'author.json', references: [{ name: 'posts', type: 'contents' }], }); let tagFolder = join(appPrefix, 'tag'); if(!existsSync(tagFolder)) { this.ui.writeWarnLine(`As of empress-blog@1.7 you must define your tags in the same way as you define your authors. We will auto generate tag files for you but this behaviour will be removed in empress-blog@2.0. Please generate tags using 'ember generate tag your-tag-name'`); tagFolder = new TagGenerator(join(appPrefix, 'content')); } else { // make sure if you have defined a tag in a post that it exists let postTags = []; const markdownFiles = walkSync(join(appPrefix, 'content')) .filter(path => path.endsWith('.md')); markdownFiles.forEach((file) => { const fileContents = readFileSync(join(appPrefix, 'content', file)) const frontMatter = yamlFront.loadFront(fileContents); if (frontMatter.tags) { postTags.push(frontMatter.tags); } }); postTags = _.chain(postTags) .flatten() .uniq() .value(); const tags = walkSync(join(appPrefix, 'tag')) .filter(path => path.endsWith('.md')) .map(fileName => fileName.replace(/\.md$/, '')); if(!_.includes(tags, 'new')) { throw new Error(`We now automatically add the "new" tag to recent posts but you don't have a tag with that id. To create this tag run 'npx ember g tag new'`); } postTags.forEach((tag) => { if(!_.includes(tags, tag)) { throw new Error(`You have defined a post with tag "${tag}" but there is no tag with that id. To create this tag run 'npx ember g tag ${tag}'`); } }) } // include the post IDs into tags tagFolder = new ItemIncludePosts( new MergeTrees([ new Funnel(tagFolder, { destDir: 'tag' }), new Funnel(contentFolder, { destDir: 'content' }) ]), { itemType: 'tag', } ); const tagTree = new StaticSiteJson(tagFolder, { type: 'tag', contentFolder: 'tag', attributes: [ 'name', 'description', 'image', 'imageMeta', 'meta', ], references: [{ name: 'posts', type: 'contents' }], collate: true, collationFileName: 'tag.json', }); const trees = [contentTree, pageTree, authorTree, tagTree]; if (blogConfig.host) { trees.push(new StaticSiteJsonXml(contentTree, { title: blogConfig.title, host: blogConfig.host, icon: blogConfig.rssLogo || blogConfig.logo, })); } else { if(this.ui) { this.ui.writeWarnLine(`Host is not configured so no RSS feed will be generated If you want know how to configure the host and other parameters check out our documentation: https://github.com/empress/empress-blog#configuring-your-host--enabling-rss`); } } return MergeTrees(trees); }, contentFor(type, config) { if (type === 'head' && config.blog && config.blog.host) { return `<link rel="alternate" type="application/rss+xml" title="${config.blog.title}" href="${config.blog.host}/rss.xml" />` } }, urlsForPrember() { let appPrefix = join(this.project.configPath(), '../..'); const content = walkSync(join(appPrefix, 'content'), { globs: ['*.md'], }); const contentYamls = _.chain(content) .map(path => ({ path, yaml: yamlFront.loadFront(readFileSync(join(appPrefix, 'content', path))) })) .value(); const staticUrls = ['/']; const tagUrls = _.chain(contentYamls) .map(file => file.yaml.tags) .flatten() .compact() .uniq() .map(tag => `/tag/${tag}`) .value(); const contentUrls = content.map(file => file.replace(/\.md$/, '')).map(file => `/${file}`); const pageUrls = walkSync(join(appPrefix, 'page'), { globs: ['*.md'], }).map(file => file.replace(/\.md$/, '')).map(file => `/page/${file}`); const authorUrls = walkSync(join(appPrefix, 'author'), { globs: ['*.md'], }).map(file => file.replace(/\.md$/, '')).map(file => `/author/${file}`); return [...staticUrls, ...contentUrls, ...authorUrls, ...pageUrls, ...tagUrls]; }, };