detach-code-blocks.js 3.7 KB

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