diff --git a/lib/shared/markdown.js b/lib/shared/markdown.js --- a/lib/shared/markdown.js +++ b/lib/shared/markdown.js @@ -9,6 +9,7 @@ export type State = { key?: string | number | void, inline?: ?boolean, + quotationsDepth?: number, [string]: any, }; @@ -87,6 +88,7 @@ const blockQuoteRegex: RegExp = /^( *>[^\n]+(?:\n[^\n]+)*)(?:\n|$)/; const blockQuoteStripFollowingNewlineRegex: RegExp = /^( *>[^\n]+(?:\n[^\n]+)*)(?:\n|$){2}/; +const maxNestedQuotations = 5; const urlRegex: RegExp = /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/i; @@ -231,6 +233,7 @@ codeBlockStripTrailingNewlineRegex, fenceRegex, fenceStripTrailingNewlineRegex, + maxNestedQuotations, jsonMatch, jsonPrint, matchList, diff --git a/native/markdown/rules.react.js b/native/markdown/rules.react.js --- a/native/markdown/rules.react.js +++ b/native/markdown/rules.react.js @@ -8,6 +8,7 @@ import { relativeMemberInfoSelectorForMembersOfThread } from 'lib/selectors/user-selectors'; import * as SharedMarkdown from 'lib/shared/markdown'; +import { maxNestedQuotations } from 'lib/shared/markdown'; import type { RelativeMemberInfo } from 'lib/types/thread-types'; import { useSelector } from '../redux/redux-utils'; @@ -215,17 +216,27 @@ blockQuote: { ...SimpleMarkdown.defaultRules.blockQuote, // match end of blockQuote by either \n\n or end of string - match: SimpleMarkdown.blockRegex( - SharedMarkdown.blockQuoteStripFollowingNewlineRegex, - ), + match: (source: string, state: SharedMarkdown.State) => { + if ( + state.quotationsDepth && + state.quotationsDepth >= maxNestedQuotations + ) { + return null; + } + return SharedMarkdown.blockQuoteStripFollowingNewlineRegex.exec(source); + }, parse( capture: SharedMarkdown.Capture, parse: SharedMarkdown.Parser, state: SharedMarkdown.State, ) { const content = capture[1].replace(/^ *> ?/gm, ''); + const currentQuotationsDepth = state.quotationsDepth ?? 0; return { - content: parse(content, state), + content: parse(content, { + ...state, + quotationsDepth: currentQuotationsDepth + 1, + }), }; }, // eslint-disable-next-line react/display-name diff --git a/web/markdown/rules.react.js b/web/markdown/rules.react.js --- a/web/markdown/rules.react.js +++ b/web/markdown/rules.react.js @@ -6,6 +6,7 @@ import { relativeMemberInfoSelectorForMembersOfThread } from 'lib/selectors/user-selectors'; import * as SharedMarkdown from 'lib/shared/markdown'; +import { maxNestedQuotations } from 'lib/shared/markdown'; import type { RelativeMemberInfo } from 'lib/types/thread-types'; import { useSelector } from '../redux/redux-utils'; @@ -78,15 +79,27 @@ blockQuote: { ...SimpleMarkdown.defaultRules.blockQuote, // match end of blockQuote by either \n\n or end of string - match: SimpleMarkdown.blockRegex(SharedMarkdown.blockQuoteRegex), + match: (source: string, state: SharedMarkdown.State) => { + if ( + state.quotationsDepth && + state.quotationsDepth >= maxNestedQuotations + ) { + return null; + } + return SharedMarkdown.blockQuoteRegex.exec(source); + }, parse( capture: SharedMarkdown.Capture, parse: SharedMarkdown.Parser, state: SharedMarkdown.State, ) { const content = capture[1].replace(/^ *> ?/gm, ''); + const currentQuotationsDepth = state.quotationsDepth ?? 0; return { - content: parse(content, state), + content: parse(content, { + ...state, + quotationsDepth: currentQuotationsDepth + 1, + }), }; }, },