Ghost/ghost/html-to-plaintext/lib/html-to-plaintext.js
Kevin Ansfield 79f41dc679 Added in-reply-to support to comments API
ref https://linear.app/tryghost/issue/PLG-230

- adds `in_reply_to_id` to API output
- adds `in_reply_to_snippet` to API output
  - dynamically generated from the HTML of the replied-to comment
  - excluded if the replied-to comment has been deleted or hidden
- adds `commentSnippet` to `@tryghost/html-to-plaintext`
  - skips anchor tag URLs as they won't be useful for snippet purposes
  - skips blockquotes so the snippet is more likely to contain the unique content of the replied-to comment when it's quoting a previous comment
  - returns a single line (no newline chars)
- allows setting `in_reply_to_id` when creating comments
  - id must reference a reply with the same parent
  - id must reference a published comment
- adds email notification for the original reply author when their comment is replied to
2024-11-07 09:20:03 +00:00

121 lines
3.5 KiB
JavaScript

const _ = require('lodash');
const mergeSettings = (extraSettings) => {
return _.mergeWith({}, baseSettings, extraSettings, function customizer(objValue, srcValue) {
if (_.isArray(objValue)) {
return objValue.concat(srcValue);
}
});
};
const baseSettings = {
wordwrap: false,
preserveNewlines: true,
// equiv returnDomByDefault: true,
baseElements: {returnDomByDefault: true},
selectors: [
// Ignore images, equiv ignoreImage: true
{selector: 'img', format: 'skip'},
// disable uppercase headings, equiv uppercaseHeadings: false
{selector: 'h1', options: {uppercase: false}},
{selector: 'h2', options: {uppercase: false}},
{selector: 'h3', options: {uppercase: false}},
{selector: 'h4', options: {uppercase: false}},
{selector: 'h5', options: {uppercase: false}},
{selector: 'h6', options: {uppercase: false}},
{selector: 'table', options: {uppercaseHeaderCells: false}},
// Backwards compatibility with html-to-text 5.1.1
{selector: 'div', format: 'inline'}
]
};
let excerptConverter;
let emailConverter;
let commentConverter;
let commentSnippetConverter;
const loadConverters = () => {
if (excerptConverter && emailConverter) {
return;
}
const {compile} = require('html-to-text');
const excerptSettings = mergeSettings({
selectors: [
{selector: 'a', options: {ignoreHref: true}},
{selector: 'figcaption', format: 'skip'},
// Strip inline and bottom footnotes
{selector: 'a[rel=footnote]', format: 'skip'},
{selector: 'div.footnotes', format: 'skip'},
// Don't output hrs
{selector: 'hr', format: 'skip'},
// Don't output > in blockquotes
{selector: 'blockquote', format: 'block'},
// Don't include signup cards in excerpts
{selector: '.kg-signup-card', format: 'skip'}
]
});
const emailSettings = mergeSettings({
selectors: [
// equiv hideLinkHrefIfSameAsText: true
{selector: 'a', options: {hideLinkHrefIfSameAsText: true}},
// Don't include html .preheader in email
{selector: '.preheader', format: 'skip'}
]
});
const commentSettings = mergeSettings({
preserveNewlines: false,
selectors: [
// equiv hideLinkHrefIfSameAsText: true
{selector: 'a', options: {hideLinkHrefIfSameAsText: true}},
// No space between <p> tags. An empty <p> is needed
{selector: 'p', options: {leadingLineBreaks: 1, trailingLineBreaks: 1}}
]
});
const commentSnippetSettings = mergeSettings({
preserveNewlines: false,
ignoreHref: true,
selectors: [
{selector: 'blockquote', format: 'skip'}
]
});
excerptConverter = compile(excerptSettings);
emailConverter = compile(emailSettings);
commentConverter = compile(commentSettings);
commentSnippetConverter = compile(commentSnippetSettings);
};
module.exports.excerpt = (html) => {
loadConverters();
return excerptConverter(html);
};
module.exports.email = (html) => {
loadConverters();
return emailConverter(html);
};
module.exports.comment = (html) => {
loadConverters();
return commentConverter(html);
};
module.exports.commentSnippet = (html) => {
loadConverters();
return commentSnippetConverter(html)
.replace(/\n/g, ' ')
.replace(/\s+/g, ' ');
};