import { join } from 'path' ; import { readFileSync , statSync } from 'fs' ; import matter from 'gray-matter' ; import dateFns from 'date-fns' ; import _ from 'lodash' ; import pathToRegexp from 'path-to-regexp' ; import yaml from 'js-yaml' ; import { slugify , logger } from '../utils' ; import { Nuxtent } from '../../types' ; import Anchor from 'markdown-it-anchor' ; import Token from 'markdown-it/lib/token' ; const permalinkCompiler = pathToRegexp . compile ; param { string } fileName returns { string } const getSlug = ( fileName ) : string => { const onlyName = fileName . replace ( /(\.comp)?(\.[0-9a-z]+)$/ , '' ) . replace ( /!?(\d{4}-\d{2}-\d{2}-)/ , '' ) ; return slugify ( onlyName ) . toLowerCase ( ) ; } ; param { string } date returns { { year : string , month : string , day : string } } const splitDate = ( date ) : { year : string ; month : string ; day : string ; } => { const [ year , month , day ] = date . split ( '-' ) ; return { day , month , year , } ; } ; declare interface IPrivateFileMeta extends Nuxtent . Database . FileMeta { filePath : string ; } const isDev = process . env . NODE_ENV !== 'production' ; export default class Page { /**
     * Gets the meta but hides the file path
     */ get meta ( ) : Nuxtent . Database . FileMeta { const cleanedMeta = Object . assign ( { } , this . __meta ) ; delete cleanedMeta . filePath ; return cleanedMeta ; } /**
     * Gets the path of the file
     */ get path ( ) { if ( ! this . config . page ) { return this . permalink ; } if ( isDev || ! this . cached . path ) { const nestedPath = /([^_][a-zA-z]*?)\/[^a-z_]*/ ; const matchedPath = this . config . page . match ( nestedPath ) ; if ( matchedPath && matchedPath [ 1 ] !== 'index' ) { this . cached . path = join ( matchedPath [ 1 ] , this . permalink ) . replace ( /\\|\/\// , '/' ) ; } else { this . cached . path = this . permalink . replace ( /\\|\/\// , '/' ) ; } } return this . cached . path ; } /**
     * Gets the valid permalink for this page
     */ get permalink ( ) { if ( isDev || ! this . cached . permalink ) { const date = this . date . toString ( ) ; const { section , fileName } = this . meta ; const slug = getSlug ( fileName ) ; const { year , month , day } = splitDate ( date ) ; const params = { section , slug , date , year , month , day } ; const toPermalink = permalinkCompiler ( this . config . permalink ) ; let permalink = join ( '/' , toPermalink ( params ) . replace ( /%2F/gi , '/' ) ) ; if ( permalink . length > 6 && permalink . substr ( - 6 ) === '/index' ) { permalink = permalink . substr ( 0 , permalink . length - 6 ) ; } this . cached . permalink = permalink . replace ( /\\|\\\\/g , '/' ) ; } return this . cached . permalink ; } /**
     * Gets all the attributes
     */ get attributes ( ) { if ( typeof this . config . data === 'object' ) { return { ... this . _rawData . attributes , ... this . config . data } ; } return this . _rawData . attributes ; } /**
     * Gets the body contents for the object
     */ get body ( ) : Nuxtent . Page . Body { if ( isDev || this . cached . body === null || ( typeof this . cached . body === 'string' && this . cached . body . length === 0 ) || ( typeof this . cached . body === 'object' && ! this . cached . body . relativePath ) ) { const { dirName , section , fileName , filePath } = this . __meta ; if ( fileName . search ( /\.comp\.md$/ ) > - 1 ) { let relativePath = '.' + join ( dirName , section , fileName ) ; relativePath = relativePath . replace ( /\\/ , '/' ) ; if ( ! relativePath ) { logger . error ( 'Path not found for ' + this . _rawData . fileName ) ; } this . cached . body = { content : this . _rawData . body . content , relativePath , } ; } else if ( fileName . search ( /\.md$/ ) > - 1 ) { if ( this . config . markdown . plugins . toc ) { const tocPlugin = this . config . markdown . plugins . toc as Nuxtent . Config . MarkdownItPluginArray ; tocPlugin [ 1 ] . callback = this . tocParserCallback ; } if ( this . config . markdown . parser ) { if ( ! this . _rawData . body . content ) { logger . warn ( `Empty content on ${ this . path }` ) ; } this . cached . body = this . config . markdown . parser . render ( this . _rawData . body . content || '' ) ; } else { logger . error ( `The ${ this . config . permalink } markdown config is wrong` ) ; } } else if ( fileName . endsWith ( '.html' ) ) { this . cached . body = this . _rawData . body . content || '' ; } else if ( fileName . search ( /\.(yaml|yml)$/ ) > - 1 ) { const source = readFileSync ( filePath ) . toString ( ) ; const body = yaml . load ( source ) ; this . cached . body = body ; } else { logger . error ( 'This file is not supported ' + this . __meta . fileName ) ; } } if ( this . cached . body === null ) { throw new Error ( 'Unexpected result on get body' ) ; } return this . cached . body ; } get date ( ) { if ( isDev || ! this . cached . date ) { const { filePath , fileName , section } = this . __meta ; if ( this . config . isPost ) { const fileDate = fileName . match ( /!?(\d{4}-\d{2}-\d{2})/ ) ; if ( ! fileDate ) { throw new Error ( `File "${ fileName }" on ${ section } Needs a date in YYYY-MM-DD-filename.md!` ) ; } this . cached . date = fileDate [ 0 ] ; } else { const stats = statSync ( filePath ) ; this . cached . date = dateFns . format ( stats . ctime , 'YYYY-MM-DD' ) ; } } return this . cached . date ; } get _rawData ( ) : Nuxtent . Page . RawData { if ( isDev || ! this . cached . data . fileName ) { const source = readFileSync ( this . __meta . filePath ) . toString ( ) ; const fileName = this . __meta . fileName ; this . cached . data . fileName = fileName ; if ( fileName . search ( /\.(md|html)$/ ) !== - 1 ) { const result = matter ( source , { excerpt : ! ! this . config . excerpt , } ) ; this . cached . data . attributes = result . data ; if ( ! ! this . config . excerpt ) { this . cached . data . attributes . excerpt = fileName . endsWith ( 'md' ) && this . config . markdown . parser && result . excerpt ? this . config . markdown . parser . render ( result . excerpt ) : result . excerpt ; } this . cached . data . body . content = result . content ; } else if ( fileName . search ( /\.(yaml|yml)$/ ) !== - 1 ) { this . cached . data . body . content = yaml . load ( source ) ; } else if ( fileName . endsWith ( '.json' ) ) { this . cached . data . body . content = JSON . parse ( source ) ; } else { logger . warn ( `The file ${ fileName } is not compatible with nuxtent` ) ; } } return this . cached . data ; } set toc ( entry ) { if ( ! this . config . toc || ! entry ) { return ; } if ( typeof this . cached . toc === 'undefined' ) { this . cached . toc = { } ; } if ( typeof this . cached . toc [ this . permalink ] === 'undefined' ) { this . cached . toc [ this . permalink ] = { items : { } , slug : entry . slug , topLevel : Infinity , } ; } if ( ! entry . slug || typeof this . cached . toc [ this . permalink ] . items [ entry . slug ] !== 'undefined' ) { return ; } const tocEntry = { level : entry . tag ? parseInt ( entry . tag . substr ( 1 ) , 10 ) : 1 , link : '#' + entry . slug , title : entry . title , } ; if ( tocEntry . level < this . cached . toc [ this . permalink ] . topLevel ) { this . cached . toc [ this . permalink ] . topLevel = tocEntry . level ; } if ( typeof this . cached . toc [ this . permalink ] . items [ entry . slug ] === 'undefined' ) { this . cached . toc [ this . permalink ] . items [ entry . slug ] = tocEntry ; } } get toc ( ) : Nuxtent . Page . PageToc | null { if ( ! this . config . toc || ! this . cached . toc ) { return null ; } return this . cached . toc [ this . permalink ] ; } private cached : Nuxtent . Page . PageData = { attributes : { } , body : null , data : { attributes : { } , body : { } , } , date : null , path : null , permalink : null , } ; private __meta : IPrivateFileMeta ; private config : Nuxtent . Config . Content ; private propsSet : Set < Nuxtent . Page . PageProp | string > = new Set ( [ 'meta' , 'date' , 'path' , 'permalink' , 'breadcrumbs' , 'attributes' , 'body' , ] ) ; get breadcrumbs ( ) { if ( Array . isArray ( this . cached . breadcrumbs ) ) { return this . cached . breadcrumbs ; } return [ ] ; } set breadcrumbs ( crumbs ) { this . cached . breadcrumbs = crumbs ; } param meta param contentConfig memberOf constructor ( meta , contentConfig ) { this . __meta = meta ; this . config = contentConfig ; if ( contentConfig . toc !== false ) { this . propsSet . add ( 'toc' ) ; } } description param params param params . exclude returns { Object } memberOf public create ( params ) { const excludes = params . exclude || [ ] ; const props = Array . from ( this . propsSet ) ; excludes . forEach ( prop => { if ( props . includes ( prop ) ) { props . splice ( props . indexOf ( prop ) , 1 ) ; } } ) ; const data = { attributes : { } , body : '' , date : null , path : null , permalink : '' , } ; props . forEach ( prop => { if ( prop === 'attributes' ) { Object . assign ( data , this [ prop ] ) ; } else if ( this [ prop ] !== undefined ) { data [ prop ] = this [ prop ] ; } } ) ; return data ; } description param token param token . attrs param token . tag param info param info . title param info . slug returns { void } memberOf private tocParserCallback ( token , info ) { let addToToc = true ; if ( typeof token . attrs !== 'undefined' ) { const classValue = token . attrGet ( 'class' ) ; if ( classValue && classValue . includes ( 'notoc' ) ) { addToToc = true ; } } if ( addToToc ) { this . toc = { items : { } , slug : info . slug , tag : token . tag , title : info . title , topLevel : 0 , } ; } } }