Home Manual Reference Source Test Repository

lib/url.js

  1. import moment from 'moment';
  2. import slug from 'slug';
  3. import path from 'path';
  4.  
  5. /**
  6. * Are we running within a dist build (i.e. pre-compiled)?
  7. * @type {boolean} True if we're running in the dist folder.
  8. */
  9. const isDistBuild = __dirname.indexOf('dist') > -1;
  10.  
  11. // Cached slug options.
  12. let slugOptions;
  13.  
  14. /**
  15. * @param {string} filePath A file path.
  16. * @param {Array.<string>} markdownExtensions Array of known markdown file
  17. * extensions.
  18. * @return {number} Index of the filepath extension.
  19. */
  20. function getMarkdownExtensionIndexForFilePath(filePath, markdownExtensions) {
  21. // Get file extension of file. i.e. 'post.md' would give 'md'.
  22. const fileExtension = path.extname(filePath).replace(/^\./, '');
  23. return markdownExtensions.indexOf(fileExtension);
  24. }
  25.  
  26. const Url = {
  27. /**
  28. * Interpolates variables into a permalink structure.
  29. * @example
  30. * // returns '/hello-world/'
  31. * interpolatePermalink('/:title/', {
  32. * title: 'hello-world'
  33. * });
  34. * @param {string} permalink A permalink template.
  35. * @param {Object} context An object with keys that if matched to the
  36. * permalink will have the value interpolated to the string.
  37. * @return {string} Actual permalink value.
  38. */
  39. interpolatePermalink(permalink, context) {
  40. // eslint-disable-next-line no-useless-escape
  41. const PERMALINK_REGEX = /:(\w+[\|A-Z]*)/g;
  42.  
  43. const params = permalink.match(PERMALINK_REGEX);
  44.  
  45. // If we found no tags in the permalink then just return the given string.
  46. if (!params) {
  47. return permalink;
  48. }
  49.  
  50. let result = permalink;
  51.  
  52. params.forEach(param => {
  53. // Replace ':title' -> 'title'.
  54. let paramKey = param.substr(1);
  55.  
  56. let paramPipe;
  57. if (paramKey.includes('|')) {
  58. [paramKey, paramPipe] = paramKey.split('|');
  59. }
  60.  
  61. let paramValue = context[paramKey];
  62.  
  63. if (paramValue) {
  64. if (paramPipe) {
  65. paramValue = moment.utc(paramValue).format(paramPipe);
  66. }
  67. const sanitized = Url.slug(paramValue);
  68. result = result.replace(param, sanitized);
  69. } else {
  70. throw new Error(
  71. `${'interpolatePermalink: could not find param value ' +
  72. 'for key: '}${paramKey}`
  73. );
  74. }
  75. });
  76.  
  77. return result;
  78. },
  79.  
  80. /**
  81. * Wrapper around the slug module. Handles taking a string and making it
  82. * into a slug, a URL safe string.
  83. * @param {string} str String to slugify.
  84. * @param {Object} options Slug options.
  85. * @return {string} Slugified string.
  86. */
  87. slug(str, options) {
  88. return slug(str, {
  89. ...slugOptions,
  90. options,
  91. });
  92. },
  93.  
  94. /**
  95. * Set slug options to be used by Url.slug.
  96. * @param {Object} options Options
  97. */
  98. setSlugOptions(options) {
  99. slugOptions = options;
  100. },
  101.  
  102. /**
  103. * Check if given filePath matches a markdown extension.
  104. * @param {string} filePath A file path.
  105. * @param {Array.<string>} markdownExtensions Array of known markdown file
  106. * extensions.
  107. * @return {boolean} Whether this filePath matches any of our known markdown
  108. * extensions.
  109. */
  110. pathHasMarkdownExtension(filePath, markdownExtensions) {
  111. return (
  112. getMarkdownExtensionIndexForFilePath(filePath, markdownExtensions) > -1
  113. );
  114. },
  115.  
  116. /**
  117. * Replaces a markdown file path with `.html` if it's a known markdown file.
  118. * @param {string} filePath A file path.
  119. * @param {Array.<string>} markdownExtensions Array of known markdown file
  120. * extensions.
  121. * @return {string} Modified file path.
  122. */
  123. replaceMarkdownExtension(filePath, markdownExtensions) {
  124. const index = getMarkdownExtensionIndexForFilePath(
  125. filePath,
  126. markdownExtensions
  127. );
  128.  
  129. let result = filePath;
  130.  
  131. // Is this file's extension one of our known markdown extensions?
  132. if (index > -1) {
  133. const foundExtension = markdownExtensions[index];
  134. result = filePath.replace(new RegExp(`.${foundExtension}$`), '.html');
  135. }
  136.  
  137. return result;
  138. },
  139.  
  140. /**
  141. * When writing to the file system update the permalink so that it renders
  142. * correctly.
  143. * @example
  144. * // returns '/hello-world/index.html'
  145. * Url.makeUrlFileSystemSafe('/hello-world');
  146. * @param {string} url Url to make file system safe.
  147. * @return {string} Safe url.
  148. */
  149. makeUrlFileSystemSafe(url) {
  150. let result = url;
  151.  
  152. // If the url does not end with an extension then we need to modify the URL.
  153. if (!path.extname(url)) {
  154. // If we don't have a leading / then add it.
  155. if (!url.startsWith('/')) {
  156. result = `/${url}`;
  157. }
  158.  
  159. // If we don't have a trailing / then add it.
  160. if (!url.endsWith('/')) {
  161. result += '/';
  162. }
  163.  
  164. // Append the default file name.
  165. result += 'index.html';
  166. }
  167.  
  168. return result;
  169. },
  170.  
  171. /**
  172. * Given a URL that ends with 'index.html' it'll strip it off and return the
  173. * resulting value. Useful when creating URLs in a template.
  174. * @param {string} url Url to augment.
  175. * @return {string} Augmented url.
  176. */
  177. makePretty(url) {
  178. const makePrettyRegEx = /\/index.html$/;
  179. return url.replace(makePrettyRegEx, '/');
  180. },
  181.  
  182. /**
  183. * Resolve a path from the root of the project, taking into account
  184. * the relative depth we need to go to get to the root of the project,
  185. * depending if we're in a pre-compiled build or not.
  186. * @param {...string} args Splat of strings.
  187. * @return {string} Full path.
  188. */
  189. pathFromRoot(...args) {
  190. // Push relative distance from root of project.
  191. args.unshift(isDistBuild ? '../../' : '../');
  192.  
  193. // Add dirname relative from.
  194. args.unshift(__dirname);
  195.  
  196. return path.resolve(...args);
  197. },
  198. };
  199.  
  200. export default Url;