renderer.js 5.4 KB

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