diff --git a/core/frontend/helpers/excerpt.js b/core/frontend/helpers/excerpt.js index e7035dd38e..f0f73aae23 100644 --- a/core/frontend/helpers/excerpt.js +++ b/core/frontend/helpers/excerpt.js @@ -17,8 +17,6 @@ module.exports = function excerpt(options) { if (this.custom_excerpt) { excerptText = String(this.custom_excerpt); - } else if (this.html) { - excerptText = String(this.html); } else if (this.excerpt) { excerptText = String(this.excerpt); } else { diff --git a/core/frontend/meta/description.js b/core/frontend/meta/description.js index 077fc1819e..96c8d83069 100644 --- a/core/frontend/meta/description.js +++ b/core/frontend/meta/description.js @@ -47,7 +47,7 @@ function getDescription(data, root, options = {}) { description = data.post[`${options.property}_description`] || data.post.custom_excerpt || data.post.meta_description - || generateExcerpt(data.post.html || '', {words: 50}) + || generateExcerpt(data.post.excerpt || '', {words: 50}) || settingsCache.get('description') || ''; } else { @@ -59,7 +59,7 @@ function getDescription(data, root, options = {}) { description = data.post[`${options.property}_description`] || data.post.custom_excerpt || data.post.meta_description - || generateExcerpt(data.post.html || '', {words: 50}) + || generateExcerpt(data.post.excerpt || '', {words: 50}) || settingsCache.get('description') || ''; } else { @@ -70,7 +70,7 @@ function getDescription(data, root, options = {}) { description = data.page[`${options.property}_description`] || data.page.custom_excerpt || data.page.meta_description - || generateExcerpt(data.page.html || '', {words: 50}) + || generateExcerpt(data.page.excerpt || '', {words: 50}) || settingsCache.get('description') || ''; } else { diff --git a/core/frontend/meta/excerpt.js b/core/frontend/meta/excerpt.js index 453c3f989b..c7c79a214a 100644 --- a/core/frontend/meta/excerpt.js +++ b/core/frontend/meta/excerpt.js @@ -10,10 +10,9 @@ function getExcerpt(data) { // 1. CASE: custom_excerpt is populated via the UI // 2. CASE: no custom_excerpt, but meta_description is poplated via the UI // 3. CASE: fall back to automated excerpt of 50 words if neither custom_excerpt nor meta_description is provided - // @TODO: https://github.com/TryGhost/Ghost/issues/10062 const customExcerpt = data.post.excerpt || data.post.custom_excerpt; const metaDescription = data.post.meta_description; - const fallbackExcerpt = data.post.html ? generateExcerpt(data.post.html, {words: 50}) : ''; + const fallbackExcerpt = data.post.excerpt ? generateExcerpt(data.post.excerpt, {words: 50}) : ''; return customExcerpt ? customExcerpt : metaDescription ? metaDescription : fallbackExcerpt; } diff --git a/core/frontend/meta/generate-excerpt.js b/core/frontend/meta/generate-excerpt.js index df3f040eed..51292cefd7 100644 --- a/core/frontend/meta/generate-excerpt.js +++ b/core/frontend/meta/generate-excerpt.js @@ -1,22 +1,13 @@ const downsize = require('downsize'); -function generateExcerpt(html, truncateOptions) { +function generateExcerpt(excerpt, truncateOptions) { truncateOptions = truncateOptions || {}; - // Strip inline and bottom footnotes - let excerpt = html.replace(/.*?<\/a>/gi, ''); - excerpt = excerpt.replace(/
    .*?<\/ol><\/div>/, ''); - - // Make sure to have space between paragraphs and new lines - excerpt = excerpt.replace(/(<\/p>|
    )/gi, ' '); - - // Strip other html - excerpt = excerpt.replace(/<\/?[^>]+>/gi, ''); - excerpt = excerpt.replace(/(\r\n|\n|\r)+/gm, ' '); if (!truncateOptions.words && !truncateOptions.characters) { truncateOptions.words = 50; } + // Just uses downsize to truncate, not format return downsize(excerpt, truncateOptions); } diff --git a/core/shared/html-to-plaintext.js b/core/shared/html-to-plaintext.js index 1ccba54a96..7922912a8a 100644 --- a/core/shared/html-to-plaintext.js +++ b/core/shared/html-to-plaintext.js @@ -45,7 +45,10 @@ const loadConverters = () => { const excerptSettings = mergeSettings({ selectors: [ {selector: 'a', options: {ignoreHref: true}}, - {selector: 'figcaption', format: 'skip'} + {selector: 'figcaption', format: 'skip'}, + // Strip inline and bottom footnotes + {selector: 'a[rel=footnote]', format: 'skip'}, + {selector: 'div.footnotes', format: 'skip'} ] }); diff --git a/test/unit/frontend/helpers/excerpt.test.js b/test/unit/frontend/helpers/excerpt.test.js index baa45305b0..456c6ad3c9 100644 --- a/test/unit/frontend/helpers/excerpt.test.js +++ b/test/unit/frontend/helpers/excerpt.test.js @@ -1,155 +1,109 @@ const should = require('should'); // Stuff we are testing -const excerpt = require('../../../../core/frontend/helpers/excerpt'); +const excerptHelper = require('../../../../core/frontend/helpers/excerpt'); describe('{{excerpt}} Helper', function () { - it('renders empty string when html, excerpt, and custom_excerpt are null', function () { - const html = null; - const rendered = excerpt.call({ - html: html, - custom_excerpt: null, - excerpt: null - }); - + function shouldCompileToExpected(data, hash, expected) { + const rendered = excerptHelper.call(data, hash); should.exist(rendered); - rendered.string.should.equal(''); + rendered.string.should.equal(expected); + } + + it('renders empty string when html, excerpt, and custom_excerpt are null', function () { + const expected = ''; + + shouldCompileToExpected( + { + html: null, + custom_excerpt: null, + excerpt: null + }, + {}, + expected); }); it('can render custom_excerpt', function () { - const html = 'Hello World'; - const rendered = excerpt.call({ - html: html, - custom_excerpt: '' - }); + const custom_excerpt = 'Hello World'; - should.exist(rendered); - rendered.string.should.equal(html); + shouldCompileToExpected( + { + html: '', + custom_excerpt + }, + {}, + custom_excerpt); }); it('can render excerpt when other fields are empty', function () { - const html = ''; - const rendered = excerpt.call({ - html: html, - custom_excerpt: '', - excerpt: 'Regular excerpt' - }); - - should.exist(rendered); - rendered.string.should.equal('Regular excerpt'); + shouldCompileToExpected( + { + html: '', + custom_excerpt: '', + excerpt: 'Regular excerpt' + }, + {}, + 'Regular excerpt'); }); - it('does not output HTML', function () { - const html = '

    There are
    10
    types
    of people in the world:' + - 'c those who ' + - 'understand trinary,

    those who don\'t
    and' + - '< test > those<<< test >>> who mistake it <for> binary.'; - const expected = 'There are 10 types of people in the world: those who understand trinary, those who ' + - 'don\'t and those>> who mistake it <for> binary.'; - const rendered = excerpt.call({ - html: html, - custom_excerpt: '' - }); - - should.exist(rendered); - rendered.string.should.equal(expected); - }); - - it('strips multiple inline footnotes', function () { - const html = '

    Testing1, my footnotes. And stuff. Footnote2with a link right after.'; - const expected = 'Testing, my footnotes. And stuff. Footnotewith a link right after.'; - const rendered = excerpt.call({ - html: html, - custom_excerpt: '' - }); - - should.exist(rendered); - rendered.string.should.equal(expected); - }); - - it('strips inline and bottom footnotes', function () { - const html = '

    Testing1 a very short post with a single footnote.

    \n' + - ''; - const expected = 'Testing a very short post with a single footnote.'; - const rendered = excerpt.call({ - html: html, - custom_excerpt: '' - }); - - should.exist(rendered); - rendered.string.should.equal(expected); - }); - - it('can truncate html by word', function () { - const html = '

    Hello World! It\'s me!

    '; + it('can truncate excerpt by word', function () { + const excerpt = 'Hello World! It\'s me!'; const expected = 'Hello World!'; - const rendered = ( - excerpt.call( - { - html: html, - custom_excerpt: '' - }, - {hash: {words: '2'}} - ) - ); - should.exist(rendered); - rendered.string.should.equal(expected); + shouldCompileToExpected( + { + excerpt, + custom_excerpt: '' + }, + {hash: {words: '2'}}, + expected); }); - it('can truncate html with non-ascii characters by word', function () { - const html = '

    Едквюэ опортэат праэчынт ючю но, квуй эю

    '; + it('can truncate excerpt with non-ascii characters by word', function () { + const excerpt = 'Едквюэ опортэат праэчынт ючю но, квуй эю'; const expected = 'Едквюэ опортэат'; - const rendered = ( - excerpt.call( - { - html: html, - custom_excerpt: '' - }, - {hash: {words: '2'}} - ) + shouldCompileToExpected( + { + excerpt, + custom_excerpt: '' + }, + {hash: {words: '2'}}, + expected ); - - should.exist(rendered); - rendered.string.should.equal(expected); }); it('can truncate html by character', function () { - const html = '

    Hello World! It\'s me!

    '; + const excerpt = 'Hello World! It\'s me!'; const expected = 'Hello Wo'; - const rendered = ( - excerpt.call( - { - html: html, - custom_excerpt: '' - }, - {hash: {characters: '8'}} - ) - ); - should.exist(rendered); - rendered.string.should.equal(expected); + shouldCompileToExpected( + { + excerpt, + custom_excerpt: '' + }, + {hash: {characters: '8'}}, + expected + + ); }); it('uses custom_excerpt if provided instead of truncating html', function () { - const html = '

    Hello World! It\'s me!

    '; + const excerpt = 'Hello World! It\'s me!'; const customExcerpt = 'My Custom Excerpt wins!'; const expected = 'My Custom Excerpt wins!'; - const rendered = ( - excerpt.call( - { - html: html, - custom_excerpt: customExcerpt - } - ) - ); - should.exist(rendered); - rendered.string.should.equal(expected); + shouldCompileToExpected( + { + excerpt, + custom_excerpt: customExcerpt + }, + {}, + expected + ); }); it('does not truncate custom_excerpt if characters options is provided', function () { - const html = '

    Hello World! It\'s me!

    '; + const excerpt = 'Hello World! It\'s me!'; const customExcerpt = 'This is a custom excerpt. It should always be rendered in full length and not being cut ' + 'off. The maximum length of a custom excerpt is 300 characters. Enough to tell a bit about ' + 'your story and make a nice summary for your readers. It\s only allowed to truncate anything ' + @@ -157,23 +111,20 @@ describe('{{excerpt}} Helper', function () { const expected = 'This is a custom excerpt. It should always be rendered in full length and not being cut ' + 'off. The maximum length of a custom excerpt is 300 characters. Enough to tell a bit about ' + 'your story and make a nice summary for your readers. It\s only allowed to truncate anything ' + - 'after 300 characters. This give'; - const rendered = ( - excerpt.call( - { - html: html, - custom_excerpt: customExcerpt - }, - {hash: {characters: '8'}} - ) - ); + 'after 300 characters. This give'; - should.exist(rendered); - rendered.string.should.equal(expected); + shouldCompileToExpected( + { + excerpt, + custom_excerpt: customExcerpt + }, + {hash: {characters: '8'}}, + expected + ); }); it('does not truncate custom_excerpt if words options is provided', function () { - const html = '

    Hello World! It\'s me!

    '; + const excerpt = 'Hello World! It\'s me!'; const customExcerpt = 'This is a custom excerpt. It should always be rendered in full length and not being cut ' + 'off. The maximum length of a custom excerpt is 300 characters. Enough to tell a bit about ' + 'your story and make a nice summary for your readers. It\s only allowed to truncate anything ' + @@ -181,68 +132,15 @@ describe('{{excerpt}} Helper', function () { const expected = 'This is a custom excerpt. It should always be rendered in full length and not being cut ' + 'off. The maximum length of a custom excerpt is 300 characters. Enough to tell a bit about ' + 'your story and make a nice summary for your readers. It\s only allowed to truncate anything ' + - 'after 300 characters. This give'; - const rendered = ( - excerpt.call( - { - html: html, - custom_excerpt: customExcerpt - }, - {hash: {words: '10'}} - ) + 'after 300 characters. This give'; + + shouldCompileToExpected( + { + excerpt, + custom_excerpt: customExcerpt + }, + {hash: {words: '10'}}, + expected ); - - should.exist(rendered); - rendered.string.should.equal(expected); - }); - - it('puts additional space after closing paragraph', function () { - const html = '

    Testing.

    Space before this text.

    And this as well!

    '; - const expected = 'Testing. Space before this text. And this as well!'; - const rendered = ( - excerpt.call( - { - html: html, - custom_excerpt: '' - } - ) - ); - - should.exist(rendered); - rendered.string.should.equal(expected); - }); - - it('puts additional space instead of
    tag', function () { - const html = '

    Testing.
    Space before this text.
    And this as well!

    '; - const expected = 'Testing. Space before this text. And this as well!'; - const rendered = ( - excerpt.call( - { - html: html, - custom_excerpt: '' - } - ) - ); - - should.exist(rendered); - rendered.string.should.equal(expected); - }); - - it('puts additional space between paragraph in markup generated by Ghost', function () { - const html = '

    put space in excerpt.

    before this paragraph.

    ' + - '
    ' + - '

    and skip the image.

    '; - const expected = 'put space in excerpt. before this paragraph. and skip the image.'; - const rendered = ( - excerpt.call( - { - html: html, - custom_excerpt: '' - } - ) - ); - - should.exist(rendered); - rendered.string.should.equal(expected); }); }); diff --git a/test/unit/frontend/helpers/ghost_head.test.js b/test/unit/frontend/helpers/ghost_head.test.js index 2f43d03fd0..ac6a0002eb 100644 --- a/test/unit/frontend/helpers/ghost_head.test.js +++ b/test/unit/frontend/helpers/ghost_head.test.js @@ -196,6 +196,7 @@ describe('{{ghost_head}} helper', function () { posts.push(createPost({// Post 4 title: 'Welcome to Ghost', mobiledoc: testUtils.DataGenerator.markdownToMobiledoc('This is a short post'), + excerpt: 'This is a short post', authors: [ authors[3] ], @@ -266,6 +267,7 @@ describe('{{ghost_head}} helper', function () { posts.push(createPost({// Post 9 title: 'Welcome to Ghost', mobiledoc: testUtils.DataGenerator.markdownToMobiledoc('This is a short post'), + excerpt: 'This is a short post', tags: [ createTag({name: 'tag1'}), createTag({name: 'tag2'}), diff --git a/test/unit/frontend/meta/description.test.js b/test/unit/frontend/meta/description.test.js index 44754812e5..f38d40782b 100644 --- a/test/unit/frontend/meta/description.test.js +++ b/test/unit/frontend/meta/description.test.js @@ -208,7 +208,7 @@ describe('getMetaDescription', function () { it('has correct fallbacks for context: post', function () { const post = { - html: '

    Post html

    ', + excerpt: 'Post html', custom_excerpt: 'Post custom excerpt', meta_description: 'Post meta description', og_description: 'Post og description' @@ -232,7 +232,7 @@ describe('getMetaDescription', function () { getMetaDescription({post}, {context: 'post'}, options) .should.equal('Post html'); - post.html = ''; + post.excerpt = ''; getMetaDescription({post}, {context: 'post'}, options) .should.equal('Site description'); @@ -240,7 +240,7 @@ describe('getMetaDescription', function () { it('has correct fallbacks for context: page', function () { const page = { - html: '

    Page html

    ', + excerpt: 'Page html', custom_excerpt: 'Page custom excerpt', meta_description: 'Page meta description', og_description: 'Page og description' @@ -264,7 +264,7 @@ describe('getMetaDescription', function () { getMetaDescription({page}, {context: 'page'}, options) .should.equal('Page html'); - page.html = ''; + page.excerpt = ''; getMetaDescription({page}, {context: 'page'}, options) .should.equal('Site description'); @@ -273,7 +273,7 @@ describe('getMetaDescription', function () { // NOTE: this is a legacy format and should be resolved with https://github.com/TryGhost/Ghost/issues/10042 it('has correct fallbacks for context: page (legacy format)', function () { const post = { - html: '

    Page html

    ', + excerpt: 'Page html', custom_excerpt: 'Page custom excerpt', meta_description: 'Page meta description', og_description: 'Page og description' @@ -297,7 +297,7 @@ describe('getMetaDescription', function () { getMetaDescription({post}, {context: 'page'}, options) .should.equal('Page html'); - post.html = ''; + post.excerpt = ''; getMetaDescription({post}, {context: 'page'}, options) .should.equal('Site description'); @@ -438,7 +438,7 @@ describe('getMetaDescription', function () { it('has correct fallbacks for context: post', function () { const post = { - html: '

    Post html

    ', + excerpt: 'Post html', custom_excerpt: 'Post custom excerpt', meta_description: 'Post meta description', twitter_description: 'Post twitter description' @@ -462,7 +462,7 @@ describe('getMetaDescription', function () { getMetaDescription({post}, {context: 'post'}, options) .should.equal('Post html'); - post.html = ''; + post.excerpt = ''; getMetaDescription({post}, {context: 'post'}, options) .should.equal('Site description'); @@ -470,7 +470,7 @@ describe('getMetaDescription', function () { it('has correct fallbacks for context: page', function () { const page = { - html: '

    Page html

    ', + excerpt: 'Page html', custom_excerpt: 'Page custom excerpt', meta_description: 'Page meta description', twitter_description: 'Page twitter description' @@ -494,7 +494,7 @@ describe('getMetaDescription', function () { getMetaDescription({page}, {context: 'page'}, options) .should.equal('Page html'); - page.html = ''; + page.excerpt = ''; getMetaDescription({page}, {context: 'page'}, options) .should.equal('Site description'); @@ -503,7 +503,7 @@ describe('getMetaDescription', function () { // NOTE: this is a legacy format and should be resolved with https://github.com/TryGhost/Ghost/issues/10042 it('has correct fallbacks for context: page (legacy format)', function () { const post = { - html: '

    Page html

    ', + excerpt: 'Page html', custom_excerpt: 'Page custom excerpt', meta_description: 'Page meta description', twitter_description: 'Page twitter description' @@ -527,7 +527,7 @@ describe('getMetaDescription', function () { getMetaDescription({post}, {context: 'page'}, options) .should.equal('Page html'); - post.html = ''; + post.excerpt = ''; getMetaDescription({post}, {context: 'page'}, options) .should.equal('Site description'); diff --git a/test/unit/frontend/meta/generate-excerpt.test.js b/test/unit/frontend/meta/generate-excerpt.test.js index a51afd8a50..ec378407d3 100644 --- a/test/unit/frontend/meta/generate-excerpt.test.js +++ b/test/unit/frontend/meta/generate-excerpt.test.js @@ -1,90 +1,28 @@ -const should = require('should'); +const assert = require('assert'); const generateExcerpt = require('../../../../core/frontend/meta/generate-excerpt'); describe('generateExcerpt', function () { - it('should return html excerpt with no html', function () { - const html = '

    There are
    10
    types
    of people in the world:' + - 'c those who ' + - 'understand trinary,

    those who don\'t
    and' + - '< test > those<<< test >>> who mistake it <for> binary.'; + it('should fallback to 50 words if not specified', function () { + const html = 'This is an auto-generated excerpt. It contains a plaintext version of the first part of your content. Images, footnotes and links are all stripped out as the excerpt is not HTML, but plaintext as I already mentioned. This excerpt will be stripped down to 50 words if it is longer and no options are provided to tell us to do otherwise.'; - const expected = 'There are 10 types of people in the world: those who understand trinary, those who ' + - 'don\'t and those>> who mistake it <for> binary.'; + const expected = 'This is an auto-generated excerpt. It contains a plaintext version of the first part of your content. Images, footnotes and links are all stripped out as the excerpt is not HTML, but plaintext as I already mentioned. This excerpt will be stripped down to 50 words if it is longer'; - generateExcerpt(html, {}).should.equal(expected); + assert.equal(generateExcerpt(html), expected); }); - it('should return html excerpt strips multiple inline footnotes', function () { - const html = '

    Testing1, ' + - 'my footnotes. And stuff. Footnote2with a link ' + - 'right after.'; + it('should truncate by words if specified', function () { + const html = 'This is an auto-generated excerpt. It contains a plaintext version of the first part of your content. Images, footnotes and links are all stripped out as the excerpt is not HTML, but plaintext as I already mentioned. This excerpt will be stripped down to 50 words if it is longer and no options are provided to tell us to do otherwise.'; - const expected = 'Testing, my footnotes. And stuff. Footnotewith a link right after.'; + const expected = 'This is an auto-generated excerpt.'; - generateExcerpt(html, {}).should.equal(expected); + assert.equal(generateExcerpt(html, {words: 5}), expected); }); - it('should return html excerpt striping inline and bottom footnotes', function () { - const html = '

    Testing1' + - ' a very short post with a single footnote.

    \n' + - ''; + it('should truncate by characters if specified', function () { + const html = 'This is an auto-generated excerpt. It contains a plaintext version of the first part of your content. Images, footnotes and links are all stripped out as the excerpt is not HTML, but plaintext as I already mentioned. This excerpt will be stripped down to 50 words if it is longer and no options are provided to tell us to do otherwise.'; - const expected = 'Testing a very short post with a single footnote.'; + const expected = 'This is an auto-generated excerpt. It contains a plaintext version of the first part of your content'; - generateExcerpt(html, {}).should.equal(expected); + assert.equal(generateExcerpt(html, {characters: 100}), expected); }); - - it('should return html excerpt truncated by word', function () { - const html = '

    Hello World! It\'s me!

    '; - const expected = 'Hello World!'; - - generateExcerpt(html, {words: '2'}).should.equal(expected); - }); - - it('should return html excerpt truncated by words with non-ascii characters', - function () { - const html = '

    Едквюэ опортэат праэчынт ючю но, квуй эю

    '; - const expected = 'Едквюэ опортэат'; - - generateExcerpt(html, {words: '2'}).should.equal(expected); - }); - - it('should return html excerpt truncated by character', - function () { - const html = '

    Hello World! It\'s me!

    '; - const expected = 'Hello Wo'; - - generateExcerpt(html, {characters: '8'}).should.equal(expected); - }); - - it('should fall back to 50 words if not specified', - function () { - const html = '

    There are
    10
    types
    of people in the world:' + - 'c those who ' + - 'understand trinary,

    those who don\'t
    and' + - '< test > those<<< test >>> who mistake it <for> binary.'; - - const expected = 'There are 10 types of people in the world: those who understand trinary, those who ' + - 'don\'t and those>> who mistake it <for> binary.'; - - generateExcerpt(html).should.equal(expected); - }); - - it('should truncate plain text for custom excerpts', - function () { - const html = 'This is a custom excerpt. It should always be rendered in full length and not being cut ' + - 'off. The maximum length of a custom excerpt is 300 characters. Enough to tell a bit about ' + - 'your story and make a nice summary for your readers. It\s only allowed to truncate anything ' + - 'after 300 characters. This give'; - - const expected = 'This is a custom excerpt. It should always be rendered in full length and not being cut ' + - 'off. The maximum length of a custom excerpt is 300 characters. Enough to tell a bit about ' + - 'your story and make a nice summary for your readers. It\s only allowed to truncate anything ' + - 'after 300 characters. This give'; - - generateExcerpt(html, {characters: '300'}).should.equal(expected); - }); }); diff --git a/test/unit/shared/html-to-plaintext.test.js b/test/unit/shared/html-to-plaintext.test.js index c8e06c0006..54e8aabdc6 100644 --- a/test/unit/shared/html-to-plaintext.test.js +++ b/test/unit/shared/html-to-plaintext.test.js @@ -53,4 +53,21 @@ describe('Html to Plaintext', function () { assert.match(email, /Ghost Admin → Settings → Theme/); }); }); + + describe('footnotes', function () { + it('strips multiple inline footnotes', function () { + const html = '

    Testing1, my footnotes. And stuff. Footnote2with a link right after.'; + const expected = 'Testing, my footnotes. And stuff. Footnotewith a link right after.'; + const {excerpt} = getEmailandExcert(html); + assert.equal(excerpt, expected); + }); + + it('strips inline and bottom footnotes', function () { + const html = '

    Testing1 a very short post with a single footnote.

    \n' + + ''; + const expected = 'Testing a very short post with a single footnote.\n'; + const {excerpt} = getEmailandExcert(html); + assert.equal(excerpt, expected); + }); + }); });