detach-code-blocks.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. import { BasicInterceptor } from '@growi/core';
  2. import loggerFactory from '~/utils/logger';
  3. class DetachCodeBlockUtil {
  4. static createReplaceStr(replaceId) {
  5. return `<pre class="detached-code-block">${replaceId}</pre>`;
  6. }
  7. }
  8. /**
  9. * The interceptor that detach code blocks
  10. */
  11. export class DetachCodeBlockInterceptor extends BasicInterceptor {
  12. constructor() {
  13. super();
  14. this.logger = loggerFactory('growi:interceptor:DetachCodeBlockInterceptor');
  15. }
  16. /**
  17. * @inheritdoc
  18. */
  19. isInterceptWhen(contextName) {
  20. return /^prePreProcess|prePostProcess$/.test(contextName);
  21. }
  22. getTargetKey(contextName) {
  23. if (contextName === 'prePreProcess') {
  24. return 'markdown';
  25. }
  26. if (contextName === 'prePostProcess') {
  27. return 'parsedHTML';
  28. }
  29. }
  30. /**
  31. * @inheritdoc
  32. */
  33. process(contextName, ...args) {
  34. this.logger.debug(`processing: 'contextName'=${contextName}`);
  35. const context = Object.assign(args[0]); // clone
  36. const targetKey = this.getTargetKey(contextName);
  37. context.dcbContextMap = {};
  38. // see: https://regex101.com/r/8PAEcC/5
  39. // eslint-disable-next-line max-len
  40. context[targetKey] = context[targetKey].replace(/(^(```|~~~)(.|[\r\n])*?(```|~~~)$)|(`[^\r\n]*?`)|(<pre>(.|[\r\n])*?<\/pre>)|(<pre\s[^>]*>(.|[\r\n])*?<\/pre>)/gm, (all) => {
  41. // create ID
  42. const replaceId = `dcb-${this.createRandomStr(8)}`;
  43. this.logger.debug(`'replaceId'=${replaceId} : `, all);
  44. // register to context
  45. const dcbContext = {};
  46. dcbContext.content = all;
  47. dcbContext.substituteContent = DetachCodeBlockUtil.createReplaceStr(replaceId);
  48. context.dcbContextMap[replaceId] = dcbContext;
  49. // return substituteContent
  50. return dcbContext.substituteContent;
  51. });
  52. // resolve
  53. return Promise.resolve(context);
  54. }
  55. /**
  56. * @see http://qiita.com/ryounagaoka/items/4736c225bdd86a74d59c
  57. *
  58. * @param {number} length
  59. * @return random strings
  60. */
  61. createRandomStr(length) {
  62. const bag = 'abcdefghijklmnopqrstuvwxyz0123456789';
  63. let generated = '';
  64. for (let i = 0; i < length; i++) {
  65. generated += bag[Math.floor(Math.random() * bag.length)];
  66. }
  67. return generated;
  68. }
  69. }
  70. /**
  71. * The interceptor that restore detached code blocks
  72. */
  73. export class RestoreCodeBlockInterceptor extends BasicInterceptor {
  74. constructor() {
  75. super();
  76. this.logger = loggerFactory('growi:interceptor:DetachCodeBlockInterceptor');
  77. }
  78. /**
  79. * @inheritdoc
  80. */
  81. isInterceptWhen(contextName) {
  82. return /^postPreProcess|preRenderHtml|preRenderPreviewHtml|preRenderCommentHtml|preRenderCommentPreviewHtml$/.test(contextName);
  83. }
  84. getTargetKey(contextName) {
  85. if (contextName === 'postPreProcess') {
  86. return 'markdown';
  87. }
  88. if (contextName === 'preRenderHtml' || contextName === 'preRenderPreviewHtml'
  89. || contextName === 'preRenderCommentHtml' || contextName === 'preRenderCommentPreviewHtml') {
  90. return 'parsedHTML';
  91. }
  92. }
  93. /**
  94. * @inheritdoc
  95. */
  96. process(contextName, ...args) {
  97. this.logger.debug(`processing: 'contextName'=${contextName}`);
  98. const context = Object.assign(args[0]); // clone
  99. const targetKey = this.getTargetKey(contextName);
  100. // forEach keys of dcbContextMap
  101. Object.keys(context.dcbContextMap).forEach((replaceId) => {
  102. // get context object from context
  103. const dcbContext = context.dcbContextMap[replaceId];
  104. // replace it with content by using getter function so that the doller sign does not work
  105. // see: https://github.com/weseek/growi/issues/285
  106. context[targetKey] = context[targetKey].replace(dcbContext.substituteContent, () => { return dcbContext.content });
  107. });
  108. // resolve
  109. return Promise.resolve(context);
  110. }
  111. }