diff --git a/src/core/filesystem.js b/src/core/filesystem.js index a974e20..1dcde4e 100644 --- a/src/core/filesystem.js +++ b/src/core/filesystem.js @@ -1,7 +1,6 @@ const fs = require('fs-extra') const syspath = require('path') const { ncp } = require('ncp') -const toc = require('markdown-toc') const { parseFrontmatter } = require('../utils/frontmatter') const INDEX_FILES = ['index', 'readme'] @@ -156,10 +155,6 @@ async function getContent (path) { return content } -function getTableOfContents (content) { - return toc(content).json -} - /** * because of https://github.com/zeit/pkg/issues/420 */ @@ -177,7 +172,6 @@ module.exports = { checkForConflicts, dirTree, getContent, - getTableOfContents, copyDir, } diff --git a/src/core/hydrate.js b/src/core/hydrate.js index 921bc2f..3d01068 100644 --- a/src/core/hydrate.js +++ b/src/core/hydrate.js @@ -1,8 +1,10 @@ const syspath = require('path') // const chokidar = require('chokidar') +const markdownToc = require('markdown-toc') const ourpath = require('../utils/path') const { getFrontmatterOnly } = require('../utils/frontmatter') const { mergeLeftByKey } = require('../utils/merge') +const { getContent } = require('./filesystem') const { walkSource } = require('./source') const Sitemap = require('./sitemap') @@ -48,6 +50,34 @@ function normalizeItems (data) { } } +async function tableOfContents ({ toc, input, items }) { + // only add items that have a file associated with it + if (input) { + if (toc.page) { + const content = await getContent(input) + toc.page = markdownToc(content).json + } + + if (toc.folder) { + toc.folder = items + // only want children items that have an input + .filter(item => item.input) + // reduced data, since we don't need everything + .map(item => ({ + title: item.title, + description: item.description, + url: item.url, + })) + } + } + + // dont keep empty arrays + if (!toc.page || !toc.page.length) delete toc.page + if (!toc.folder || !toc.folder.length) delete toc.folder + + return toc +} + async function hydrateTree (tree, config, onRegenerate) { const urls = {} const sitemap = new Sitemap() @@ -80,6 +110,8 @@ async function hydrateTree (tree, config, onRegenerate) { const hydratedItem = { path: path_relative, draft: metaData.draft || false, + description: metaData.description || '', + toc: Object.assign({}, config.table_of_contents, metaData.table_of_contents), title: metaData.title || (itemParent.path !== undefined // convert the file path into the title ? ourpath.titlify(hoistedItem.path) @@ -157,6 +189,14 @@ async function hydrateTree (tree, config, onRegenerate) { ...hydratedItem.items || [], ] + // don't keep an empty items array + if (!hydratedItem.items.length) { + delete hydratedItem.items.length + } + + // add table of contents, if applicable + hydratedItem.toc = await tableOfContents(hydratedItem) + return hydratedItem } @@ -195,6 +235,7 @@ async function hydrateTree (tree, config, onRegenerate) { module.exports = { getMetaData, normalizeItems, + tableOfContents, hydrateTree, // hydrateContent, } diff --git a/src/core/output.js b/src/core/output.js index a2e33f5..f25152d 100644 --- a/src/core/output.js +++ b/src/core/output.js @@ -3,7 +3,7 @@ const syspath = require('path') const { warn } = require('../utils/emit') const { generateDatabase } = require('./database') const { templateForProduction } = require('./template') -const { getContent, getTableOfContents } = require('./filesystem') +const { getContent } = require('./filesystem') module.exports = async (entrypoints, props) => { const outputDB = syspath.join(props.config.output, 'db.json') @@ -28,7 +28,6 @@ module.exports = async (entrypoints, props) => { await fs.outputFile(outputHtml, template) await fs.outputJson(outputJson, { content: item.content, - toc: getTableOfContents(item.content), }) } diff --git a/src/core/socket.js b/src/core/socket.js index 1260481..e8e2ada 100644 --- a/src/core/socket.js +++ b/src/core/socket.js @@ -1,6 +1,6 @@ const fs = require('fs') const WebSocket = require('ws') -const { getContent, getTableOfContents } = require('./filesystem') +const { getContent } = require('./filesystem') module.exports = (server) => { const socket = new WebSocket.Server({ @@ -15,7 +15,6 @@ module.exports = (server) => { const content = await getContent(file) client.send(JSON.stringify({ content, - toc: getTableOfContents(content), })) } diff --git a/src/utils/config.js b/src/utils/config.js index 9516f7f..a7517f6 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -29,7 +29,10 @@ const DEFAULT_CONFIG = { header_links: [], theme: 'default', prefix_titles: true, - table_of_contents: true, + table_of_contents: { + page: true, + folder: true, + }, syntax: { theme: 'atom-one-light', renderer: 'hljs', diff --git a/tests/manifest.test.js b/tests/manifest.test.js index 1b11fcf..fa3e2ec 100644 --- a/tests/manifest.test.js +++ b/tests/manifest.test.js @@ -14,13 +14,23 @@ describe('integration: manifest', () => { expect(res.url).to.equal('/') expect(res.input).to.equal(syspath.resolve(__dirname, 'mock/readme.md')) expect(res.outputDir).to.equal('.gitdocs_build/') + expect(res.toc.page).to.have.length(1) + expect(res.toc.folder).to.have.length(5) + expect(res.toc.folder[0].title).to.equal('The Foo') + expect(res.toc.folder[0].description).to.equal('This is a test') + expect(res.toc.folder[0].url).to.equal('/foo/') + expect(res.items).to.have.length(7) expect(res.items[0].path).to.equal('foo') expect(res.items[0].draft).to.be.false() expect(res.items[0].title).to.equal('The Foo') + expect(res.items[0].description).to.equal('This is a test') expect(res.items[0].url).to.equal('/foo/') expect(res.items[0].input).to.equal(syspath.resolve(__dirname, 'mock/foo/index.md')) expect(res.items[0].outputDir).to.equal('.gitdocs_build/foo/') expect(res.items[0].items).to.have.length(3) + expect(res.items[1].title).to.equal('Garply') + expect(res.items[2].title).to.equal('XYZZY') + expect(res.items[3].title).to.equal('Thud') expect(res.items[4].path).to.equal('external.md') expect(res.items[4].title).to.equal('GitDocs') expect(res.items[4].url).to.equal('/gitdocs/') @@ -30,5 +40,7 @@ describe('integration: manifest', () => { expect(res.items[4].items[0].path).to.equal('externals.md') expect(res.items[4].items[0].title).to.equal('Externals') expect(res.items[4].items[0].url).to.equal('/gitdocs/externals/') + expect(res.items[5].component).to.equal('Divider') + expect(res.items[6].title).to.equal('The Quux') }) }) diff --git a/tests/mock/foo/index.md b/tests/mock/foo/index.md index 2b2202e..cb65632 100644 --- a/tests/mock/foo/index.md +++ b/tests/mock/foo/index.md @@ -1,5 +1,6 @@ --- title: The Foo +description: This is a test items_append: - component: Divider - path: bar.md diff --git a/tests/mock/readme.md b/tests/mock/readme.md index 994163d..bb210b5 100644 --- a/tests/mock/readme.md +++ b/tests/mock/readme.md @@ -5,6 +5,7 @@ items: - xyzzy - thud.md - external.md + - component: Divider - path: qux title: The Quux items_prepend: diff --git a/themes/default/page/index.js b/themes/default/page/index.js index 5447767..97ea3f3 100644 --- a/themes/default/page/index.js +++ b/themes/default/page/index.js @@ -3,35 +3,10 @@ import Helmet from 'react-helmet' import axios from 'axios' import Markdown from '../markdown' import Loading from '../loading' +import TocPage from '../toc/page' +import TocFolder from '../toc/folder' import { ConfigContext } from '../context' -import { Wrapper, ContentWrapper, TOC } from './styles' - -const TableOfContents = ({ toc, sticky }) => { - // Don't show this if there aren't enough headers - if (!toc) return null - if (toc.length < 2) return null - - // Create TOC hierarchy and link to headers - const items = toc.map(t => ( -