diff --git a/docs/externals.md b/docs/externals.md new file mode 100644 index 0000000..9de4e59 --- /dev/null +++ b/docs/externals.md @@ -0,0 +1,2 @@ +{ "name": "Node", "url": "https://github.com/timberio/timber-node.git", "branch": "docs/gitdocs", "root": "docs/" }, +{ "name": "Elixir", "url": "https://github.com/timberio/timber-elixir.git", "branch": "master", "root": "docs/" } \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 85c3155..6159ff3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1 +1 @@ -# Our documentation is coming soon! +# Our documentation is coming soon! \ No newline at end of file diff --git a/src/cmds/serve.js b/src/cmds/serve.js index 8d4d894..c368179 100644 --- a/src/cmds/serve.js +++ b/src/cmds/serve.js @@ -14,7 +14,7 @@ module.exports = async (args, config) => { const { props, compiler } = await runCore(env, config, bar) const server = await startServer(env, compiler, props) - log(`We are live at ${styles.url(server.url)}`) + log(`[\u2713] Docs are live at ${styles.url(server.url)}`) } module.exports.menu = ` diff --git a/src/core/externals.js b/src/core/externals.js index 2d71fde..3354e47 100644 --- a/src/core/externals.js +++ b/src/core/externals.js @@ -2,6 +2,7 @@ const fs = require('fs-extra') const path = require('path') const git = require('simple-git/promise') const { namespaces } = require('../utils/temp') +const { getExternalConfig } = require('../utils/config') const { log, warn, error } = require('../utils/emit') // Warnings for missing source information @@ -73,16 +74,24 @@ function extractDocs (reposDir, externalsDir, sources) { return warn(`No docs root found for ${s.name || 'source'}, skipping...`) } + // Get external docs config, will be merged with cwd config + const config = getExternalConfig(externalsDir, s.name) + // Add to the list of valid external doc sources externals.push({ name: s.name, path: outputPath, + config, }) // Move the external docs repo to our tmp folder fs.copySync(docsRoot, outputPath) }) + if (externals.length) { + log('[\u2713] External docs loaded') + } + return externals } diff --git a/src/core/index.js b/src/core/index.js index 4a232b4..a98bace 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -3,12 +3,22 @@ const getExternals = require('./externals') const getManifest = require('./manifest') const staticAssets = require('./static') const getCompiler = require('./compiler') +const mergeConfig = require('./merge') -module.exports = async (env, config, bar) => { - await loadSyntax(config) - await getExternals(config) - await staticAssets(config, env === 'development') +module.exports = async (env, localConfig, bar) => { + // Load only supported syntaxes to reduce bundle size + await loadSyntax(localConfig) + // Fetch any external docs sources + const externals = await getExternals(localConfig) + + // Merge current and extenal configs + const config = mergeConfig(localConfig, externals) + + // Load static assets like images, scripts, css, etc. + await staticAssets(localConfig, env === 'development') + + // Build the documentation manifest const manifest = await getManifest(env, config) // this gets passed to the theme app diff --git a/src/core/manifest.js b/src/core/manifest.js index 7993639..a212b30 100644 --- a/src/core/manifest.js +++ b/src/core/manifest.js @@ -35,11 +35,10 @@ async function warnForIndexConflict (file) { } } -async function hydrate (file, baseDir, outputDir, shouldGetContent) { - const data = await getFrontmatter(file) - +async function hydrate (data, file, baseDir, outputDir, shouldGetContent, config) { data.url = ourpath.routify(data.url || file, baseDir) data.title = data.title || ourpath.titlify(data.url) + data.order = data.order || config.order[data.url.replace(/\/$/, '')] || null data.file = file data.fileOutput = ourpath.outputify(`${outputDir}${data.url}`, { @@ -53,7 +52,13 @@ async function hydrate (file, baseDir, outputDir, shouldGetContent) { return data } -async function buildManifest (env, opts = {}) { +function sortByOrder (a, b) { + if (a.order === null && b.order !== null) return 1 + if (b.order === null && a.order !== null) return -1 + return a.order - b.order +} + +async function buildManifest (env, config, opts = {}) { const files = [] const filemap = {} const urlmap = {} @@ -69,34 +74,44 @@ async function buildManifest (env, opts = {}) { } if (stats.isFile()) { + // Only watch for certain file extensions (defaults to *.md) if (opts.extensions && opts.extensions.indexOf(ext) === -1) { return null } + // Warn if there are any route index conflicts await warnForIndexConflict(path) await checkForIndexConflict(path) + // @TODO: Check fro Readme.md or [folder].md const isIndex = /\/index\.[\w]+$/.test(path) const shouldGetContent = env === 'production' - const hydrated = await hydrate(path, dir, opts.outputDir, shouldGetContent) + const frontMatter = await getFrontmatter(path) + const hydrated = await hydrate( + frontMatter, path, dir, opts.outputDir, shouldGetContent, config + ) + // Ignore files with a `draft` status + if (frontMatter.draft) return + + // Detect duplicate URLs if (urlmap[hydrated.url]) { const duplicated = files[urlmap[hydrated.url]].file - throw new Error(`Can't use a URL more than once: ${hydrated.url}\n\t- ${duplicated}\n\t- ${hydrated.file}`) + throw new Error( + `Can't use a URL more than once: ${hydrated.url}\n\t- ${duplicated}\n\t- ${hydrated.file}` + ) } filemap[path] = files.push(hydrated) - 1 urlmap[hydrated.url] = filemap[path] - if (hydrated.draft) { - return - } - return isIndex ? { indexLink: hydrated.url, + order: hydrated.order, } : { text: hydrated.title, link: hydrated.url, + order: hydrated.order, } } @@ -112,11 +127,14 @@ async function buildManifest (env, opts = {}) { return { text: ourpath.titlify(path), link: indexItem ? indexItem.indexLink : undefined, + order: indexItem ? indexItem.order : null, children: children .filter(Boolean) .filter(({ indexLink }) => !indexLink) - // sort children alphabetically - .sort((a, b) => a.text > b.text), + // Sort alphabetically + .sort((a, b) => a.text > b.text) + // Sort by order in config or frontmatter + .sort(sortByOrder) } } } @@ -133,14 +151,15 @@ async function buildManifest (env, opts = {}) { children: navtreeExternal, } = await _walk(opts.reposDir, ignoredUnderscores) + // Combine and sort local and external nav trees + const navtree = [...navtreeLocal, ...navtreeExternal] + .sort(sortByOrder) + return { files, filemap, urlmap, - navtree: [ - ...navtreeLocal, - ...navtreeExternal, - ], + navtree, } } @@ -150,10 +169,12 @@ module.exports = async (env, config) => { } if (/^\//.test(config.root)) { - throw new Error(`Root is set to an absolute path! Did you mean ".${config.root}" instead of "${config.root}"?`) + throw new Error( + `Root is set to an absolute path! Did you mean ".${config.root}" instead of "${config.root}"?` + ) } - const manifest = await buildManifest(env, { + const manifest = await buildManifest(env, config, { dir: syspath.resolve(config.root), reposDir: syspath.resolve(config.temp, namespaces.repos), outputDir: syspath.resolve(config.output), diff --git a/src/core/merge.js b/src/core/merge.js new file mode 100644 index 0000000..8dd6ca6 --- /dev/null +++ b/src/core/merge.js @@ -0,0 +1,16 @@ +const { routify } = require('../utils/path') + +module.exports = (config, externals) => { + externals.forEach(e => { + console.log(routify(e.name)) + const order = Object + .keys(e.config.order || {}) + .map(k => ({ + [`/${routify(e.name)}${k}`]: e.config.order[k] + })) + .reduce((acc, cur) => ({ ...acc, ...cur }), {}) + config.order = { ...order, ...config.order } + }) + + return config +} diff --git a/src/core/static.js b/src/core/static.js index 43d1685..da522bb 100644 --- a/src/core/static.js +++ b/src/core/static.js @@ -2,6 +2,7 @@ const fs = require('fs-extra') const syspath = require('path') const chokidar = require('chokidar') const { namespaces } = require('../utils/temp') +const { log } = require('../utils/emit') function _watch (cwd, tempDir) { const watcher = chokidar.watch('**/*', { @@ -41,5 +42,7 @@ module.exports = async (config, useTempDir) => { await fs.copy(config.static, config.output) } + log('[\u2713] Static assets loaded') + return dir } diff --git a/src/core/syntax.js b/src/core/syntax.js index c9cc61a..cdc22c7 100644 --- a/src/core/syntax.js +++ b/src/core/syntax.js @@ -1,5 +1,6 @@ const fs = require('fs-extra') const syspath = require('path') +const { log } = require('../utils/emit') const { namespaces } = require('../utils/temp') const NODE_MODS = syspath.resolve(__dirname, '../../node_modules') @@ -23,6 +24,6 @@ module.exports = async (config) => { const path = `${temp}/${namespaces.codegen}/loadSyntax.js` await fs.outputFile(path, content) - + log('[\u2713] Syntax loaded') return path } diff --git a/src/index.js b/src/index.js index f7ae3f2..03c73aa 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,5 @@ const getArguments = require('./utils/arguments') -const getConfig = require('./utils/config') +const { getConfig } = require('./utils/config') const { error } = require('./utils/emit') module.exports = async () => { diff --git a/src/utils/config.js b/src/utils/config.js index 1bbf20a..266a755 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -32,13 +32,14 @@ const DEFAULT_CONFIG = { sidebar_links: [], header_links: [], sources: [], + order: {}, } -function _getConfigFilename () { +function getConfigFilename () { return FILENAMES.find(fs.pathExistsSync) } -function _readConfigFile (file) { +function readConfigFile (file) { const ext = syspath.extname(file) return ext === '.js' @@ -46,7 +47,18 @@ function _readConfigFile (file) { : fs.readJson(file) } -module.exports = async (customFile) => { +function getExternalConfigFilename (dir, name) { + return FILENAMES + .map(f => `${dir}/${name}/${f}`) + .find(fs.pathExistsSync) +} + +function getExternalConfig (dir, name) { + const file = getExternalConfigFilename(dir, name) + return file ? readConfigFile(file) : {} +} + +async function getConfig (customFile) { // prioritize custom config file if passed, // but still fallback to default files if (customFile) { @@ -57,8 +69,8 @@ module.exports = async (customFile) => { } } - const configFile = _getConfigFilename() - const userConfig = configFile ? await _readConfigFile(configFile) : {} + const configFile = getConfigFilename() + const userConfig = configFile ? await readConfigFile(configFile) : {} // return deepmerge(DEFAULT_CONFIG, userConfig) const masterConfig = { @@ -78,8 +90,13 @@ module.exports = async (customFile) => { return masterConfig } +module.exports = { + getConfig, + getExternalConfig +} + module.exports.createConfig = async (name, root) => { - if (_getConfigFilename()) { + if (getConfigFilename()) { throw new Error('GitDocs is already initialized in this folder!') } diff --git a/src/utils/path.js b/src/utils/path.js index a6e1da9..1af9612 100644 --- a/src/utils/path.js +++ b/src/utils/path.js @@ -46,8 +46,13 @@ function titlify (str) { .join(' ') } +function slugify (str) { + return str.toLowerCase().split(' ').join('-') +} + module.exports = { routify, outputify, titlify, + slugify, } diff --git a/themes/default/search/styles.js b/themes/default/search/styles.js index bdf370c..5bfcb7f 100644 --- a/themes/default/search/styles.js +++ b/themes/default/search/styles.js @@ -22,9 +22,10 @@ export const Input = styled('input')` export const Results = styled('div')` position: absolute; width: 100%; - top: 75; + top: 75px; height: calc(100vh - 75px); background: #FFF; + z-index: 99; ` export const Result = styled('div')` diff --git a/themes/default/sidebar/styles.js b/themes/default/sidebar/styles.js index 67fbb8f..8c3c496 100644 --- a/themes/default/sidebar/styles.js +++ b/themes/default/sidebar/styles.js @@ -107,8 +107,9 @@ export const NavItem = styled(Accordion)` font-weight: 700; text-decoration: none; cursor: pointer; - line-height: 30px; + line-height: 24px; border-left: 3px solid transparent; + padding: .1rem 1rem .1rem 0; :hover { opacity: 0.7; }