diff --git a/lib/shared/markdown.js b/lib/shared/markdown.js --- a/lib/shared/markdown.js +++ b/lib/shared/markdown.js @@ -251,6 +251,58 @@ } const spoilerRegex: RegExp = /^\|\|([^\n]+?)\|\|/g; +const spoilerReplacement: string = '⬛⬛⬛'; + +function traverseASTForSpoilers(ast: SingleASTNode[]): SingleASTNode[] { + return traverseASTRecursively(ast, false); +} + +function traverseASTRecursively( + ast: SingleASTNode[], + isTextASpoiler: boolean, +): SingleASTNode[] { + return ast.map(node => { + return { + ...node, + content: replaceSpoilersFromMarkdownAST(node, isTextASpoiler), + }; + }); +} + +const replaceSpoilersFromMarkdownAST = ( + node: ASTNode, + isTextASpoiler: boolean, +): string => { + // We check if the node is an array, so we continue traversing and join + if (Array.isArray(node)) { + return node + .map(n => replaceSpoilersFromMarkdownAST(n, isTextASpoiler)) + .join(''); + } + const { content, items, type } = node; + const isTypeASpoiler = type === 'spoiler'; + + // If the text content is contained within a spoiler node, replace it + if (content && typeof content === 'string') { + return isTextASpoiler ? spoilerReplacement : content; + } + // If the node is a list, we need to traverse the items + else if (items) { + return replaceSpoilersFromMarkdownAST( + items, + isTextASpoiler || isTypeASpoiler, + ); + } + // If there is content, we need to traverse it + else if (content) { + return replaceSpoilersFromMarkdownAST( + content, + isTextASpoiler || isTypeASpoiler, + ); + } + + return ''; +}; export { paragraphRegex, @@ -272,4 +324,5 @@ matchList, parseList, matchMentions, + traverseASTForSpoilers, }; diff --git a/lib/shared/messages/text-message-spec.js b/lib/shared/messages/text-message-spec.js --- a/lib/shared/messages/text-message-spec.js +++ b/lib/shared/messages/text-message-spec.js @@ -16,7 +16,11 @@ import type { NotifTexts } from '../../types/notif-types'; import type { ThreadInfo } from '../../types/thread-types'; import type { RelativeUserInfo } from '../../types/user-types'; -import { type ASTNode, type SingleASTNode } from '../markdown'; +import { + type ASTNode, + type SingleASTNode, + traverseASTForSpoilers, +} from '../markdown'; import { threadIsGroupChat } from '../thread-utils'; import { stringForUser } from '../user-utils'; import type { @@ -85,8 +89,9 @@ messageTitle({ messageInfo, markdownRules }) { const { text } = messageInfo; const parser = SimpleMarkdown.parserFor(markdownRules); - const ast = parser(text, { disableAutoBlockNewlines: true }); - + const ast = traverseASTForSpoilers( + parser(text, { disableAutoBlockNewlines: true }), + ); return getFirstNonQuotedRawLine(ast).trim(); },