diff --git a/lib/extend/tag.js b/lib/extend/tag.js index 31f9f2aa5b..5fc396ff50 100644 --- a/lib/extend/tag.js +++ b/lib/extend/tag.js @@ -4,8 +4,6 @@ const stripIndent = require('strip-indent'); const { cyan } = require('chalk'); const nunjucks = require('nunjucks'); const Promise = require('bluebird'); -const placeholder = '\uFFFC'; -const rPlaceholder = /(?:<|<)!--\uFFFC(\d+)--(?:>|>)/g; class NunjucksTag { constructor(name, fn) { @@ -77,7 +75,7 @@ class NunjucksBlock extends NunjucksTag { } run(context, args, body) { - return this._run(context, args, trimBody(body)); + return this._run(context, args, trimBody(body)) + '\n'; } } @@ -111,7 +109,7 @@ class NunjucksAsyncBlock extends NunjucksBlock { body = () => result || ''; this._run(context, args, trimBody(body)).then(result => { - callback(err, result); + callback(err, `${result}\n`); }); }); } @@ -228,15 +226,8 @@ class Tag { options = {}; } - const cache = []; - - const escapeContent = str => ``; - - str = str.replace(/
[\s\S]*?<\/code><\/pre>/gm, escapeContent);
-
     return Promise.fromCallback(cb => { this.env.renderString(str, options, cb); })
-      .catch(err => Promise.reject(formatNunjucksError(err, str)))
-      .then(result => result.replace(rPlaceholder, (_, index) => cache[index]));
+      .catch(err => Promise.reject(formatNunjucksError(err, str)));
   }
 }
 
diff --git a/lib/hexo/post.js b/lib/hexo/post.js
index 1d62074eca..09b590691e 100644
--- a/lib/hexo/post.js
+++ b/lib/hexo/post.js
@@ -1,6 +1,5 @@
 'use strict';
 
-const assert = require('assert');
 const moment = require('moment');
 const Promise = require('bluebird');
 const { join, extname } = require('path');
@@ -12,47 +11,6 @@ const yfm = require('hexo-front-matter');
 
 const preservedKeys = ['title', 'slug', 'path', 'layout', 'date', 'content'];
 
-const _escapeContent = (cache, str) => {
-  const placeholder = '\uFFFC';
-  return ``;
-};
-
-class PostRenderCache {
-  constructor() {
-    this.cache = [];
-  }
-
-  escapeContent(str) {
-    const rEscapeContent = /]*)>([\s\S]*?)<\/escape>/g;
-    return str.replace(rEscapeContent, (_, content) => _escapeContent(this.cache, content));
-  }
-
-  loadContent(str) {
-    const rPlaceholder = /(?:<|<)!--\uFFFC(\d+)--(?:>|>)/g;
-    const restored = str.replace(rPlaceholder, (_, index) => {
-      assert(this.cache[index]);
-      const value = this.cache[index];
-      this.cache[index] = null;
-      return value;
-    });
-    if (restored === str) return restored;
-    return this.loadContent(restored); // self-recursive for nexted escaping
-  }
-
-  escapeAllSwigTags(str) {
-    const rSwigVar = /\{\{[\s\S]*?\}\}/g;
-    const rSwigComment = /\{#[\s\S]*?#\}/g;
-    const rSwigBlock = /\{%[\s\S]*?%\}/g;
-    const rSwigFullBlock = /\{% *(.+?)(?: *| +.*?)%\}[\s\S]+?\{% *end\1 *%\}/g;
-
-    const escape = _str => _escapeContent(this.cache, _str);
-    return str.replace(rSwigFullBlock, escape)
-      .replace(rSwigBlock, escape)
-      .replace(rSwigComment, '')
-      .replace(rSwigVar, escape);
-  }
-}
-
 const prepareFrontMatter = data => {
   for (const [key, item] of Object.entries(data)) {
     if (moment.isMoment(item)) {
@@ -96,7 +54,7 @@ class Post {
     const ctx = this.context;
     const { config } = ctx;
 
-    data.slug = slugize((data.slug || data.title).toString(), {transform: config.filename_case});
+    data.slug = slugize((data.slug || data.title).toString(), { transform: config.filename_case });
     data.layout = (data.layout || config.default_layout).toLowerCase();
     data.date = data.date ? moment(data.date) : moment();
 
@@ -135,7 +93,7 @@ class Post {
     let yfmSplit;
 
     return this._getScaffold(data.layout).then(scaffold => {
-      const frontMatter = prepareFrontMatter({...data});
+      const frontMatter = prepareFrontMatter({ ...data });
       yfmSplit = yfm.split(scaffold);
 
       return tag.render(yfmSplit.data, frontMatter);
@@ -183,7 +141,7 @@ class Post {
     const ctx = this.context;
     const { config } = ctx;
     const draftDir = join(ctx.source_dir, '_drafts');
-    const slug = slugize(data.slug.toString(), {transform: config.filename_case});
+    const slug = slugize(data.slug.toString(), { transform: config.filename_case });
     data.slug = slug;
     const regex = new RegExp(`^${escapeRegExp(slug)}(?:[^\\/\\\\]+)`);
     let src = '';
@@ -248,52 +206,45 @@ class Post {
     // disable Nunjucks when the renderer spcify that.
     const disableNunjucks = ext && ctx.render.renderer.get(ext) && !!ctx.render.renderer.get(ext).disableNunjucks;
 
-    const cacheObj = new PostRenderCache();
-
     return promise.then(content => {
       data.content = content;
 
       // Run "before_post_render" filters
-      return ctx.execFilter('before_post_render', data, {context: ctx});
+      return ctx.execFilter('before_post_render', data, { context: ctx });
     }).then(() => {
-      data.content = cacheObj.escapeContent(data.content);
-
       if (isSwig) {
-        // Render with Nunjucks if this is a swig file
+        // Render with Nunjucks if the post is a swig file
         return tag.render(data.content, data);
       }
 
-      // Escape all Swig tags
-      if (!disableNunjucks) {
-        data.content = cacheObj.escapeAllSwigTags(data.content);
-      }
-
       const options = data.markdown || {};
       if (!config.highlight.enable) options.highlight = null;
-
       ctx.log.debug('Rendering post: %s', magenta(source));
-      // Render with markdown or other renderer
-      return ctx.render.render({
-        text: data.content,
+
+      const promise = text => ctx.render.render({
+        text,
         path: source,
         engine: data.engine,
         toString: true,
         onRenderEnd(content) {
-          // Replace cache data with real contents
-          data.content = cacheObj.loadContent(content);
-
-          // Return content after replace the placeholders
-          if (disableNunjucks) return data.content;
-
-          // Render with Nunjucks
-          return tag.render(data.content, data);
+          data.content = content.replace(//g, '');
+          return data.content;
         }
       }, options);
+
+      if (!disableNunjucks) {
+        return tag.render(data.content, data).then(promise);
+      }
+
+      // Nunjucks is disabled
+      return promise(data.content);
+
     }).then(content => {
       data.content = content;
 
       // Run "after_post_render" filters
-      return ctx.execFilter('after_post_render', data, {context: ctx});
+      return ctx.execFilter('after_post_render', data, { context: ctx });
     }).asCallback(callback);
   }
 }
diff --git a/lib/plugins/filter/before_post_render/backtick_code_block.js b/lib/plugins/filter/before_post_render/backtick_code_block.js
index a97af39210..8d6c51df93 100644
--- a/lib/plugins/filter/before_post_render/backtick_code_block.js
+++ b/lib/plugins/filter/before_post_render/backtick_code_block.js
@@ -63,7 +63,7 @@ function backtickCodeBlock(data) {
       .replace(/{/g, '{')
       .replace(/}/g, '}');
 
-    return `${start}${content}${end}`;
+    return `${start}${end}`;
   });
 }
 
diff --git a/test/fixtures/post_render.js b/test/fixtures/post_render.js
index c6f7f067a0..3943683b5c 100644
--- a/test/fixtures/post_render.js
+++ b/test/fixtures/post_render.js
@@ -7,7 +7,7 @@ const code = [
   '  sleep()'
 ].join('\n');
 
-const content = [
+exports.content = [
   '# Title',
   '``` python',
   code,
@@ -24,7 +24,19 @@ const content = [
   '{% endquote %}'
 ].join('\n');
 
-exports.content = content;
+exports.content_for_issue_3346 = [
+  '# Title',
+  '```',
+  '{% test1 %}',
+  '{{ test2 }}',
+  '```',
+  'some content',
+  '',
+  '## Another title',
+  '{% blockquote %}',
+  'quote content',
+  '{% endblockquote %}'
+].join('\n');
 
 exports.expected = [
   '

Title

', @@ -33,7 +45,7 @@ exports.expected = [ '

Another title

', '
', '

quote content

\n', - '
\n\n', + '\n\n\n', '

quote content

\n', '
Hello World
' ].join(''); @@ -50,3 +62,13 @@ exports.expected_disable_nunjucks = [ 'quote content
', '{% endquote %}

' ].join(''); + +exports.expected_for_issue_3346 = [ + '

Title

', + highlight('{% test1 %}\n{{ test2 }}').replace(/{/g, '{').replace(/}/g, '}'), // Escaped by backtick_code_block + '\n

some content

\n', + '

Another title

', + '
', + '

quote content

\n', + '
' +].join(''); diff --git a/test/scripts/extend/tag.js b/test/scripts/extend/tag.js index 193ee64890..ff28e44e6b 100644 --- a/test/scripts/extend/tag.js +++ b/test/scripts/extend/tag.js @@ -37,7 +37,7 @@ describe('Tag', () => { ].join('\n'); const result = await tag.render(str); - result.should.eql('foo bar test content'); + result.should.eql('foo bar test content\n'); }); it('register() - async block', async () => { @@ -52,7 +52,7 @@ describe('Tag', () => { ].join('\n'); const result = await tag.render(str); - result.should.eql('foo bar test content'); + result.should.eql('foo bar test content\n'); }); it('register() - nested test', async () => { @@ -111,7 +111,7 @@ describe('Tag', () => { ].join('\n'); const result = await tag.render(str); - result.should.eql('test content'); + result.should.eql('test content\n'); }); it('register() - async callback', async () => { diff --git a/test/scripts/filters/backtick_code_block.js b/test/scripts/filters/backtick_code_block.js index 247e0dbd75..5f1f0bbfd9 100644 --- a/test/scripts/filters/backtick_code_block.js +++ b/test/scripts/filters/backtick_code_block.js @@ -67,7 +67,7 @@ describe('Backtick code block', () => { }; codeBlock(data); - data.content.should.eql('' + highlight(code, {lang: 'js'}) + ''); + data.content.should.eql(''); }); it('without language name', () => { @@ -82,7 +82,7 @@ describe('Backtick code block', () => { const expected = highlight(code); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('without language name - ignore tab character', () => { @@ -97,7 +97,7 @@ describe('Backtick code block', () => { const expected = highlight(code); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('title', () => { @@ -115,7 +115,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('url', () => { @@ -133,7 +133,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('link text', () => { @@ -151,7 +151,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('indent', () => { @@ -171,7 +171,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('line number false', () => { @@ -191,7 +191,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('line number false, don`t first_line_number always1', () => { @@ -212,7 +212,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('line number false, don`t care first_line_number inilne', () => { @@ -233,7 +233,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('line number true', () => { @@ -253,7 +253,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('line number, first_line_number always1, js=', () => { @@ -275,7 +275,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('line number, first_line_number inline, js', () => { @@ -297,7 +297,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('line number, first_line_number inline, js=1', () => { @@ -319,7 +319,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('line number, first_line_number inline, js=2', () => { @@ -341,7 +341,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('tab replace', () => { @@ -367,7 +367,7 @@ describe('Backtick code block', () => { }); codeBlock(data); - data.content.should.eql('' + expected + ''); + data.content.should.eql(''); }); it('wrap', () => { @@ -382,7 +382,7 @@ describe('Backtick code block', () => { }; codeBlock(data); - data.content.should.eql('' + highlight(code, { lang: 'js', wrap: false }) + ''); + data.content.should.eql(''); hexo.config.highlight.wrap = true; }); diff --git a/test/scripts/hexo/post.js b/test/scripts/hexo/post.js index 846c3ef3f5..8678c18dbb 100644 --- a/test/scripts/hexo/post.js +++ b/test/scripts/hexo/post.js @@ -645,7 +645,7 @@ describe('Post', () => { }).then(data => { data.content.trim().should.eql([ highlighted, - '', + '\n', highlighted ].join('\n')); }); @@ -910,7 +910,7 @@ describe('Post', () => { engine: 'markdown' }).then(data => { data.content.trim().should.eql([ - '

content1

\n
\n\n', + '

content1

\n
\n\n\n', '

This is a following paragraph

\n', '

content2

\n
' ].join('')); @@ -932,11 +932,59 @@ describe('Post', () => { engine: 'markdown' }).then(data => { data.content.trim().should.eql([ - '

content1

\n
\n\n', + '

content1

\n
\n\n\n', '

This is a following paragraph

\n', '

content2

\n
' ].join('')); }); }); + // test for Issue #3346 + it('render() - swig tag inside backtick code block', () => { + const content = fixture.content_for_issue_3346; + + return post.render(null, { + content, + engine: 'markdown' + }).then(data => { + data.content.trim().should.eql(fixture.expected_for_issue_3346); + }); + }); + + // test for https://github.com/hexojs/hexo/pull/4171#issuecomment-594412367 + it('render() - markdown content right after swig tag', async () => { + const content = [ + '{% pullquote warning %}', + 'Text', + '{% endpullquote %}', + '# Title 0', + '{% pullquote warning %}', + 'Text', + '{% endpullquote %}', + '{% pullquote warning %}', + 'Text', + '{% endpullquote %}', + '# Title 1', + '{% pullquote warning %}', + 'Text', + '{% endpullquote %}', + '{% pullquote warning %}Text{% endpullquote %}', + '# Title 2', + '{% pullquote warning %}Text{% endpullquote %}', + '{% pullquote warning %}Text{% endpullquote %}', + '# Title 3', + '{% pullquote warning %}Text{% endpullquote %}' + ].join('\n'); + + const data = await post.render(null, { + content, + engine: 'markdown' + }); + + // We only to make sure markdown content is rendered correctly + data.content.trim().should.include('

Title 0

'); + data.content.trim().should.include('

Title 1

'); + data.content.trim().should.include('

Title 2

'); + data.content.trim().should.include('

Title 3

'); + }); });