renderer.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. const _ = require('lodash')
  2. const cheerio = require('cheerio')
  3. const uslug = require('uslug')
  4. const pageHelper = require('../../../helpers/page')
  5. const URL = require('url').URL
  6. /* global WIKI */
  7. module.exports = {
  8. async render() {
  9. const $ = cheerio.load(this.input)
  10. if ($.root().children().length < 1) {
  11. return ''
  12. }
  13. for (let child of this.children) {
  14. const renderer = require(`../${_.kebabCase(child.key)}/renderer.js`)
  15. renderer.init($, child.config)
  16. }
  17. // --------------------------------
  18. // Detect internal / external links
  19. // --------------------------------
  20. let internalRefs = []
  21. const reservedPrefixes = /^\/[a-z]\//gi
  22. const exactReservedPaths = /^\/[a-z]$/gi
  23. const isHostSet = WIKI.config.host.length > 7 && WIKI.config.host !== 'http://'
  24. if (!isHostSet) {
  25. WIKI.logger.warn('Host is not set. You must set the Site Host under General in the Administration Area!')
  26. }
  27. $('a').each((i, elm) => {
  28. let href = $(elm).attr('href')
  29. // -> Ignore empty / anchor links
  30. if (!href || href.length < 1 || href.indexOf('#') === 0 || href.indexOf('mailto:') === 0) {
  31. return
  32. }
  33. // -> Strip host from local links
  34. if (isHostSet && href.indexOf(WIKI.config.host) === 0) {
  35. href = href.replace(WIKI.config.host, '')
  36. }
  37. // -> Assign local / external tag
  38. if (href.indexOf('://') < 0) {
  39. // -> Remove trailing slash
  40. if (_.endsWith('/')) {
  41. href = href.slice(0, -1)
  42. }
  43. // -> Check for system prefix
  44. if (!reservedPrefixes.test(href) && !exactReservedPaths.test(href)) {
  45. let pagePath = null
  46. // -> Add locale prefix if using namespacing
  47. if (WIKI.config.lang.namespacing) {
  48. // -> Reformat paths
  49. if (href.indexOf('/') !== 0) {
  50. href = `/${this.page.localeCode}/${this.page.path}/${href}`
  51. } else if (href.charAt(3) !== '/') {
  52. href = `/${this.page.localeCode}${href}`
  53. }
  54. try {
  55. const parsedUrl = new URL(`http://x${href}`)
  56. pagePath = pageHelper.parsePath(parsedUrl.pathname)
  57. } catch (err) {
  58. return
  59. }
  60. } else {
  61. // -> Reformat paths
  62. if (href.indexOf('/') !== 0) {
  63. href = `/${this.page.path}/${href}`
  64. }
  65. try {
  66. const parsedUrl = new URL(`http://x${href}`)
  67. pagePath = pageHelper.parsePath(parsedUrl.pathname)
  68. } catch (err) {
  69. return
  70. }
  71. }
  72. // -> Save internal references
  73. internalRefs.push({
  74. localeCode: pagePath.locale,
  75. path: pagePath.path
  76. })
  77. $(elm).addClass(`is-internal-link`)
  78. } else {
  79. $(elm).addClass(`is-system-link`)
  80. }
  81. } else {
  82. $(elm).addClass(`is-external-link`)
  83. }
  84. // -> Update element
  85. $(elm).attr('href', href)
  86. })
  87. // --------------------------------
  88. // Detect internal link states
  89. // --------------------------------
  90. const pastLinks = await this.page.$relatedQuery('links')
  91. if (internalRefs.length > 0) {
  92. // -> Find matching pages
  93. const results = await WIKI.models.pages.query().column('id', 'path', 'localeCode').where(builder => {
  94. internalRefs.forEach((ref, idx) => {
  95. if (idx < 1) {
  96. builder.where(ref)
  97. } else {
  98. builder.orWhere(ref)
  99. }
  100. })
  101. })
  102. // -> Apply tag to internal links for found pages
  103. $('a.is-internal-link').each((i, elm) => {
  104. const href = $(elm).attr('href')
  105. let hrefObj = {}
  106. try {
  107. const parsedUrl = new URL(`http://x${href}`)
  108. hrefObj = pageHelper.parsePath(parsedUrl.pathname)
  109. } catch (err) {
  110. return
  111. }
  112. if (_.some(results, r => {
  113. return r.localeCode === hrefObj.locale && r.path === hrefObj.path
  114. })) {
  115. $(elm).addClass(`is-valid-page`)
  116. } else {
  117. $(elm).addClass(`is-invalid-page`)
  118. }
  119. })
  120. // -> Add missing links
  121. const missingLinks = _.differenceWith(internalRefs, pastLinks, (nLink, pLink) => {
  122. return nLink.localeCode === pLink.localeCode && nLink.path === pLink.path
  123. })
  124. if (missingLinks.length > 0) {
  125. if (WIKI.config.db.type === 'postgres') {
  126. await WIKI.models.pageLinks.query().insert(missingLinks.map(lnk => ({
  127. pageId: this.page.id,
  128. path: lnk.path,
  129. localeCode: lnk.localeCode
  130. })))
  131. } else {
  132. for (const lnk of missingLinks) {
  133. await WIKI.models.pageLinks.query().insert({
  134. pageId: this.page.id,
  135. path: lnk.path,
  136. localeCode: lnk.localeCode
  137. })
  138. }
  139. }
  140. }
  141. }
  142. // -> Remove outdated links
  143. if (pastLinks) {
  144. const outdatedLinks = _.differenceWith(pastLinks, internalRefs, (nLink, pLink) => {
  145. return nLink.localeCode === pLink.localeCode && nLink.path === pLink.path
  146. })
  147. if (outdatedLinks.length > 0) {
  148. await WIKI.models.pageLinks.query().delete().whereIn('id', _.map(outdatedLinks, 'id'))
  149. }
  150. }
  151. // --------------------------------
  152. // Add header handles
  153. // --------------------------------
  154. let headers = []
  155. $('h1,h2,h3,h4,h5,h6').each((i, elm) => {
  156. if ($(elm).attr('id')) {
  157. return
  158. }
  159. let headerSlug = uslug($(elm).text())
  160. // -> Cannot start with a number (CSS selector limitation)
  161. if (headerSlug.match(/^\d/)) {
  162. headerSlug = `h-${headerSlug}`
  163. }
  164. // -> Make sure header is unique
  165. if (headers.indexOf(headerSlug) >= 0) {
  166. let isUnique = false
  167. let hIdx = 1
  168. while (!isUnique) {
  169. const headerSlugTry = `${headerSlug}-${hIdx}`
  170. if (headers.indexOf(headerSlugTry) < 0) {
  171. isUnique = true
  172. headerSlug = headerSlugTry
  173. }
  174. hIdx++
  175. }
  176. }
  177. // -> Add anchor
  178. $(elm).attr('id', headerSlug).addClass('toc-header')
  179. $(elm).prepend(`<a class="toc-anchor" href="#${headerSlug}">&#xB6;</a> `)
  180. headers.push(headerSlug)
  181. })
  182. return $.html('body').replace('<body>', '').replace('</body>', '')
  183. }
  184. }