page.mjs 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import qs from 'querystring'
  2. import { fromPairs, get, head, initial, invert, isEmpty, last } from 'lodash-es'
  3. import crypto from 'node:crypto'
  4. import path from 'node:path'
  5. const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
  6. const localeFolderRegex = /^([a-z]{2}(?:-[a-z]{2})?\/)?(.*)/i
  7. // eslint-disable-next-line no-control-regex
  8. const unsafeCharsRegex = /[\x00-\x1f\x80-\x9f\\"|<>:*?]/
  9. const contentToExt = {
  10. markdown: 'md',
  11. html: 'html'
  12. }
  13. const extToContent = invert(contentToExt)
  14. /**
  15. * Parse raw url path and make it safe
  16. */
  17. export function parsePath (rawPath, opts = {}) {
  18. const pathObj = {
  19. // TODO: use site base lang
  20. locale: 'en', // WIKI.config.lang.code,
  21. path: 'home',
  22. explicitLocale: false
  23. }
  24. // Clean Path
  25. rawPath = qs.unescape(rawPath).trim()
  26. if (rawPath.startsWith('/')) { rawPath = rawPath.substring(1) }
  27. rawPath = rawPath.replace(unsafeCharsRegex, '')
  28. if (rawPath === '') { rawPath = 'home' }
  29. rawPath = rawPath.replace(/\\/g, '').replace(/\/\//g, '').replace(/\.\.+/ig, '')
  30. // Extract Info
  31. let pathParts = rawPath.split('/').filter(p => {
  32. p = p.trim()
  33. return !isEmpty(p) && p !== '..' && p !== '.'
  34. })
  35. if (pathParts.length > 1 && pathParts[0].startsWith('_')) {
  36. pathParts.shift()
  37. }
  38. if (localeSegmentRegex.test(pathParts[0])) {
  39. pathObj.locale = pathParts[0]
  40. pathObj.explicitLocale = true
  41. pathParts.shift()
  42. }
  43. // Strip extension
  44. if (opts.stripExt && pathParts.length > 0) {
  45. const lastPart = last(pathParts)
  46. if (lastPart.indexOf('.') > 0) {
  47. pathParts.pop()
  48. const lastPartMeta = path.parse(lastPart)
  49. pathParts.push(lastPartMeta.name)
  50. }
  51. }
  52. pathObj.path = pathParts.join('/')
  53. return pathObj
  54. }
  55. /**
  56. * Generate unique hash from page
  57. */
  58. export function generateHash(opts) {
  59. return crypto.createHash('sha1').update(`${opts.locale}|${opts.path}|${opts.privateNS}`).digest('hex')
  60. }
  61. /**
  62. * Inject Page Metadata
  63. */
  64. export function injectPageMetadata(page) {
  65. const meta = [
  66. ['title', page.title],
  67. ['description', page.description],
  68. ['published', page.isPublished.toString()],
  69. ['date', page.updatedAt],
  70. ['tags', page.tags ? page.tags.map(t => t.tag).join(', ') : ''],
  71. ['editor', page.editorKey],
  72. ['dateCreated', page.createdAt]
  73. ]
  74. switch (page.contentType) {
  75. case 'markdown':
  76. return '---\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n---\n\n' + page.content
  77. case 'html':
  78. return '<!--\n' + meta.map(mt => `${mt[0]}: ${mt[1]}`).join('\n') + '\n-->\n\n' + page.content
  79. case 'json':
  80. return {
  81. ...page.content,
  82. _meta: fromPairs(meta)
  83. }
  84. default:
  85. return page.content
  86. }
  87. }
  88. /**
  89. * Check if path is a reserved path
  90. */
  91. export function isReservedPath(rawPath) {
  92. const firstSection = head(rawPath.split('/'))
  93. if (firstSection.length < 1) {
  94. return true
  95. } else if (localeSegmentRegex.test(firstSection)) {
  96. return true
  97. } else if (
  98. WIKI.data.reservedPaths.some(p => {
  99. return p === firstSection
  100. })) {
  101. return true
  102. } else {
  103. return false
  104. }
  105. }
  106. /**
  107. * Get file extension from content type
  108. */
  109. export function getFileExtension(contentType) {
  110. return get(contentToExt, contentType, 'txt')
  111. }
  112. /**
  113. * Get content type from file extension
  114. */
  115. export function getContentType (filePath) {
  116. const ext = last(filePath.split('.'))
  117. return get(extToContent, ext, false)
  118. }
  119. /**
  120. * Get Page Meta object from disk path
  121. */
  122. export function getPagePath (filePath) {
  123. let fpath = filePath
  124. if (process.platform === 'win32') {
  125. fpath = filePath.replace(/\\/g, '/')
  126. }
  127. let meta = {
  128. locale: WIKI.config.lang.code,
  129. path: initial(fpath.split('.')).join('')
  130. }
  131. const result = localeFolderRegex.exec(meta.path)
  132. if (result[1]) {
  133. meta = {
  134. locale: result[1].replace('/', ''),
  135. path: result[2]
  136. }
  137. }
  138. return meta
  139. }