page.mjs 3.8 KB

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