| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288 | const _ = require('lodash')const cheerio = require('cheerio')const uslug = require('uslug')const pageHelper = require('../../../helpers/page')const URL = require('url').URLconst mustacheRegExp = /(\{|{?){2}(.+?)(\}|}?){2}/i/* global WIKI */module.exports = {  async render() {    const $ = cheerio.load(this.input, {      decodeEntities: true    })    if ($.root().children().length < 1) {      return ''    }    // --------------------------------    // STEP: PRE    // --------------------------------    for (let child of _.reject(this.children, ['step', 'post'])) {      const renderer = require(`../${_.kebabCase(child.key)}/renderer.js`)      await renderer.init($, child.config)    }    // --------------------------------    // Detect internal / external links    // --------------------------------    let internalRefs = []    const reservedPrefixes = /^\/[a-z]\//i    const exactReservedPaths = /^\/[a-z]$/i    const isHostSet = WIKI.config.host.length > 7 && WIKI.config.host !== 'http://'    if (!isHostSet) {      WIKI.logger.warn('Host is not set. You must set the Site Host under General in the Administration Area!')    }    $('a').each((i, elm) => {      let href = $(elm).attr('href')      // -> Ignore empty / anchor links, e-mail addresses, and telephone numbers      if (!href || href.length < 1 || href.indexOf('#') === 0 ||        href.indexOf('mailto:') === 0 || href.indexOf('tel:') === 0) {        return      }      // -> Strip host from local links      if (isHostSet && href.indexOf(WIKI.config.host) === 0) {        href = href.replace(WIKI.config.host, '')      }      // -> Assign local / external tag      if (href.indexOf('://') < 0) {        // -> Remove trailing slash        if (_.endsWith('/')) {          href = href.slice(0, -1)        }        // -> Check for system prefix        if (reservedPrefixes.test(href) || exactReservedPaths.test(href)) {          $(elm).addClass(`is-system-link`)        } else if (href.indexOf('.') >= 0) {          $(elm).addClass(`is-asset-link`)        } else {          let pagePath = null          // -> Add locale prefix if using namespacing          if (WIKI.config.lang.namespacing) {            // -> Reformat paths            if (href.indexOf('/') !== 0) {              if (this.config.absoluteLinks) {                href = `/${this.page.localeCode}/${href}`              } else {                href = (this.page.path === 'home') ? `/${this.page.localeCode}/${href}` : `/${this.page.localeCode}/${this.page.path}/${href}`              }            } else if (href.charAt(3) !== '/') {              href = `/${this.page.localeCode}${href}`            }            try {              const parsedUrl = new URL(`http://x${href}`)              pagePath = pageHelper.parsePath(parsedUrl.pathname)            } catch (err) {              return            }          } else {            // -> Reformat paths            if (href.indexOf('/') !== 0) {              if (this.config.absoluteLinks) {                href = `/${href}`              } else {                href = (this.page.path === 'home') ? `/${href}` : `/${this.page.path}/${href}`              }            }            try {              const parsedUrl = new URL(`http://x${href}`)              pagePath = pageHelper.parsePath(parsedUrl.pathname)            } catch (err) {              return            }          }          // -> Save internal references          internalRefs.push({            localeCode: pagePath.locale,            path: pagePath.path          })          $(elm).addClass(`is-internal-link`)        }      } else {        $(elm).addClass(`is-external-link`)        if (this.config.openExternalLinkNewTab) {          $(elm).attr('target', '_blank')          $(elm).attr('rel', this.config.relAttributeExternalLink)        }      }      // -> Update element      $(elm).attr('href', href)    })    // --------------------------------    // Detect internal link states    // --------------------------------    const pastLinks = await this.page.$relatedQuery('links')    if (internalRefs.length > 0) {      // -> Find matching pages      const results = await WIKI.models.pages.query().column('id', 'path', 'localeCode').where(builder => {        internalRefs.forEach((ref, idx) => {          if (idx < 1) {            builder.where(ref)          } else {            builder.orWhere(ref)          }        })      })      // -> Apply tag to internal links for found pages      $('a.is-internal-link').each((i, elm) => {        const href = $(elm).attr('href')        let hrefObj = {}        try {          const parsedUrl = new URL(`http://x${href}`)          hrefObj = pageHelper.parsePath(parsedUrl.pathname)        } catch (err) {          return        }        if (_.some(results, r => {          return r.localeCode === hrefObj.locale && r.path === hrefObj.path        })) {          $(elm).addClass(`is-valid-page`)        } else {          $(elm).addClass(`is-invalid-page`)        }      })      // -> Add missing links      const missingLinks = _.differenceWith(internalRefs, pastLinks, (nLink, pLink) => {        return nLink.localeCode === pLink.localeCode && nLink.path === pLink.path      })      if (missingLinks.length > 0) {        if (WIKI.config.db.type === 'postgres') {          await WIKI.models.pageLinks.query().insert(missingLinks.map(lnk => ({            pageId: this.page.id,            path: lnk.path,            localeCode: lnk.localeCode          })))        } else {          for (const lnk of missingLinks) {            await WIKI.models.pageLinks.query().insert({              pageId: this.page.id,              path: lnk.path,              localeCode: lnk.localeCode            })          }        }      }    }    // -> Remove outdated links    if (pastLinks) {      const outdatedLinks = _.differenceWith(pastLinks, internalRefs, (nLink, pLink) => {        return nLink.localeCode === pLink.localeCode && nLink.path === pLink.path      })      if (outdatedLinks.length > 0) {        await WIKI.models.pageLinks.query().delete().whereIn('id', _.map(outdatedLinks, 'id'))      }    }    // --------------------------------    // Add header handles    // --------------------------------    let headers = []    $('h1,h2,h3,h4,h5,h6').each((i, elm) => {      if ($(elm).attr('id')) {        return      }      let headerSlug = uslug($(elm).text())      // -> Cannot start with a number (CSS selector limitation)      if (headerSlug.match(/^\d/)) {        headerSlug = `h-${headerSlug}`      }      // -> Make sure header is unique      if (headers.indexOf(headerSlug) >= 0) {        let isUnique = false        let hIdx = 1        while (!isUnique) {          const headerSlugTry = `${headerSlug}-${hIdx}`          if (headers.indexOf(headerSlugTry) < 0) {            isUnique = true            headerSlug = headerSlugTry          }          hIdx++        }      }      // -> Add anchor      $(elm).attr('id', headerSlug).addClass('toc-header')      $(elm).prepend(`<a class="toc-anchor" href="#${headerSlug}">¶</a> `)      headers.push(headerSlug)    })    // --------------------------------    // Wrap root text nodes    // --------------------------------    $('body').contents().toArray().forEach(item => {      if (item.type === 'text' && item.parent.name === 'body') {        $(item).wrap('<div></div>')      }    })    // --------------------------------    // Escape mustache expresions    // --------------------------------    function iterateMustacheNode (node) {      const list = $(node).contents().toArray()      list.forEach(item => {        if (item.type === 'text') {          const rawText = $(item).text()          if (mustacheRegExp.test(rawText)) {            $(item).parent().attr('v-pre', true)          }        } else {          iterateMustacheNode(item)        }      })    }    iterateMustacheNode($.root())    // --------------------------------    // STEP: POST    // --------------------------------    let output = decodeEscape($.html('body').replace('<body>', '').replace('</body>', ''))    for (let child of _.sortBy(_.filter(this.children, ['step', 'post']), ['order'])) {      const renderer = require(`../${_.kebabCase(child.key)}/renderer.js`)      output = await renderer.init(output, child.config)    }    return output  }}function decodeEscape (string) {  return string.replace(/&#x([0-9a-f]{1,6});/ig, (entity, code) => {    code = parseInt(code, 16)    // Don't unescape ASCII characters, assuming they're encoded for a good reason    if (code < 0x80) return entity    return String.fromCodePoint(code)  })}
 |