UNPKG

skypager-project

Version:
441 lines (351 loc) 11.7 kB
import { camelCase, snakeCase, kebabCase } from 'skypager-util/lib/string' import cloneDeep from 'lodash/cloneDeep' export default (doc) => { const ast = doc.ast if (!doc.ast) { console.log('Could not find', doc) } doc.ast = Object.assign(ast, { children: ast.children.map(node => decorateNode(doc, node)) }) if (!doc.chain) { Object.defineProperty(doc, 'chain', { enumerable: false, get: function() { return __non_webpack_require__('lodash').chain(doc) } }) } return Object.assign(doc, { decorateNode(node) { return decorateNode(doc, node) }, createTree() { const headings = doc.createHeadingsMap('headingPath') const tree = {} Object.keys(headings).forEach(headingPath => { const treePath = headingPath.split('/').map(v => camelCase(kebabCase(v))) lodash.set(tree, treePath, headings[headingPath]) }) return tree }, createHeadingsMap(keyBy = 'accessor', stayChained = false) { const obj = doc.chain .invoke('selectHeadingNodes') .map(heading => { heading.accessorMethod = camelCase(snakeCase(heading.textContent())) heading.headingPath = heading.getHeadingPath() heading.accessors = heading.headingPath.split('/').map(i => camelCase(snakeCase(i))) heading.accessor = camelCase(snakeCase(heading.headingPath.replace(/\//g,'_'))) return heading }) .keyBy(keyBy) return stayChained ? obj : obj.value() }, stringifyNode(node) { return require('mdast-util-to-string')(node) }, getSourceForNode(node) { return require('unist-util-source')(node) }, headingRange(test) { const ast = doc.ast return new Promise((resolve,reject) => { try { require('mdast-util-heading-range')(ast, test, (startNode, nodes, endNode) => { resolve({startNode, nodes, endNode}) }) } catch(error) { reject(error) } }) }, documentTitle() { const titleNode = doc.titleNode() if (!titleNode) { return doc.get('frontmatter.title', doc.variations.humanized) } else { return titleNode.textContent() } }, introParagraphs() { const until = doc.selectHeadingNodes()[1] || doc.finalNode() if (!until) { return doc.selectNodes('paragraph') } else { const children = until.getNodesBefore((node) => ( ['paragraph'].indexOf(node.type) >= 0 )).reverse() return require('mdast-util-to-string')({type:'root', children}) } }, withoutNodeIndexes(indexList = []) { const ast = this.ast return { type: 'root', children: children.filter(node => indexList.indexOf(node.index) === -1), data: ast.data, } }, createAST(options = {}) { const newAST = { type: options.type || 'root', children: options.children || [], data: options.data || {}, } return newAST }, extractSectionNodes(node) { return node.headingRange().then(({nodes}) => nodes) }, inspectOutline() { const headings = this.selectHeadingNodes() return headings.map(headingNode => { const content = `- ${headingNode.textContent()}` return lodash.padStart(content, content.length + (headingNode.depth - 1) * 2) }).join("\n") }, headingIndexes(depth) { return this.chain .invoke('selectHeadingNodes', depth && `[depth=${depth}]`) .sortBy(node => node.position ? node.position.start.line : node.index) .map(node => node.index) .value() }, references() { return Object.assign({}, this.referenceImages(), this.referenceLinks()) }, referenceImages() { return this.chain .invoke('selectNodes', 'imageReference') .keyBy('identifier') .mapValues((refLink, identifier) => { const definitionNodes = doc.definitionNodes() return Object.assign(refLink, { definitionNode() { return definitionNodes[identifier] }, get definition() { return lodash.pick(definitionNodes[identifier], 'title', 'url', 'identifier') } }) }) .value() }, imageNodes() { return this.selectNodes('image') }, referenceLinks() { return this.chain .invoke('selectNodes', 'linkReference') .keyBy('identifier') .mapValues((refLink, identifier) => { const definitionNodes = doc.definitionNodes() return Object.assign(refLink, { definitionNode() { return definitionNodes[identifier] }, get definition() { return lodash.pick(definitionNodes[identifier], 'title', 'url', 'identifier') } }) }) .value() }, definitionNodes() { return this.chain .invoke('selectNodes', 'definition') .keyBy('identifier') .value() }, selectHeadingsByDepth(depth = 1) { return this.selectHeadingNodes(`[depth=${depth}]`) }, selectOneNode(selector) { return require('unist-util-select')(this.ast)(selector)[0] }, selectNodes(selector = '*', testFn) { const results = require('unist-util-select')(this.ast)(selector) return typeof testFn === 'function' ? results.filter(r => testFn.call(doc, r)) : results }, selectHeadingNodes(enhance = '') { return doc.selectNodes(`heading${enhance}`.trim()) }, selectCodeBlocks(lang = 'javascript', enhance='') { return doc.selectNodes(`code[lang=${lang}]${enhance}`.trim()) }, findNodeAfter(node, ...args) { return require('unist-util-find-after')(this.ast, node, ...args) }, findAllNodesAfter(node, ...args) { return require('unist-util-find-all-after')(this.ast, node, ...args) }, findAllNodesBefore(node, ...args) { return require('unist-util-find-all-before')(this.ast, node, ...args) }, nodesIndexedByPosition() { return doc.chain .get('ast.children') .keyBy(node => parseInt(node.position.start.line)) .value() }, introNodes() { const f = this.selectNodes('heading') const m = f.find(n => n.depth > 1) const nodes = m ? m.getNodesBefore().reverse() : this.ast.children return nodes.filter(n => n.type !== 'yaml') }, finalHeading() { return this.selectNodes('heading:last-of-type') }, titleNode() { return this.selectOneNode('heading[depth=1]') || this.selectOneNode('heading:first-of-type') }, firstSectionHeadingNode() { return this.selectOneNode('heading[depth=2]') }, sectionHeadingNodes() { return this.selectNodes('heading[depth=2]') }, articleHeadingNodes() { return this.selectNodes('heading[depth=3]') }, subarticleHeadingNodes() { return this.selectNodes('heading').filter(n => n.depth >= 4) }, getNodesBetweenPositions(alpha, bravo) { const start = isNode(alpha) ? alpha.position.start.line : parseInt(alpha) const end = isNode(bravo) ? bravo.position.start.line : parseInt(bravo) return doc.chain.get('ast.children').filter(node => { const { position: { start: { line } }} = node return line > start && line < end }) }, firstNode() { return this.chain.get('ast.children', []).first().value() }, finalNode() { return this.chain.get('ast.children', []).last().value() }, }) } export function decorateNode(doc, node) { const original = cloneDeep(node) return Object.assign(node, { original() { return original }, slug() { return kebabCase(this.textContent()) }, textContent() { return require('mdast-util-to-string')(node) || '' }, getHeadingPath() { if (node.type !== 'heading') { const prev = node.previousHeading() return prev && prev.getHeadingPath() } const textContent = kebabCase(node.textContent().toLowerCase()) const prevSuperior = node.getPreviousSuperior() const previousPath = typeof prevSuperior === 'undefined' ? `` : (prevSuperior.depth >= 2 && prevSuperior.getHeadingPath()) || '' return [previousPath, textContent].filter(i => i && i.length > 0).join('/') }, headingRangeSync() { const alpha = node const bravo = node.getFinalHeading() || doc.finalNode() return doc.getNodesBetweenPositions(alpha, bravo || 50000).value() }, headingRange(test = node.textContent()) { const ast = doc.ast return new Promise((resolve,reject) => { try { require('mdast-util-heading-range')(ast, test, (startNode, nodes, endNode) => { resolve({startNode, nodes, endNode}) }) } catch(error) { reject(error) } }) }, getFinalHeading() { return doc.findAllNodesAfter(node, (n) => n.type === 'heading' && n.depth <= node.depth )[0] }, getPreviousSuperior() { return node && node.getNodesBefore && node.getNodesBefore(n => n.type === 'heading' && (n.depth < node.depth))[0] }, previousHeading() { return node.getNodesBefore(n => n.type === 'heading').shift() }, nextHeading() { return node.type === 'heading' ? node.getNextNode((n) => n.type === 'heading' && n.depth >= node.depth) : node.getNextNode(n => n.type === 'heading') }, getNextNode(...args) { return doc.findNodeAfter(node, ...args) }, getNodesAfter(...args) { return doc.findAllNodesAfter(node, ...args) }, getNodesBefore(...args) { return doc.findAllNodesBefore(node, ...args) }, selectChildren(selector, testFn) { if(node.type === 'heading') { return node.selectHeadingChildren(selector, testFn) } const results = require('unist-util-select')(node)(selector) return typeof testFn === 'function' ? results.filter(r => testFn.call(doc, r)) : results }, virtualAST() { return { type: 'root', children: node.headingRangeSync().map(n => n.original()).map((n,i) => { n.index = i return n }) } }, selectHeadingChildrenSync(selector = '*', testFn) { let results = require('unist-util-select')(node.virtualAST())(selector) results = typeof testFn === 'function' ? results.filter(r => testFn.call(doc, r)) : results return results }, selectHeadingChildren(selector = '*', testFn) { return node.headingRange() .then(({nodes}) => { const base = { type: 'root', children: nodes } return require('unist-util-select')(base)(selector) }) .then((results) => { return typeof testFn === 'function' ? results.filter(r => testFn.call(doc, r)) : results }) } }) } export function isNode(obj) { return typeof obj === 'object' && typeof obj.type === 'string' && obj.position } export function createBlock(contents, lang='') { return ["```" + lang, contents, "```"].join("\n") }