diff --git a/lib/shared/markdown.js b/lib/shared/markdown.js --- a/lib/shared/markdown.js +++ b/lib/shared/markdown.js @@ -87,6 +87,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 +232,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 @@ -7,7 +7,9 @@ import * as SimpleMarkdown from 'simple-markdown'; import { relativeMemberInfoSelectorForMembersOfThread } from 'lib/selectors/user-selectors'; +import type { State } from 'lib/shared/markdown'; 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 +217,24 @@ 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: State) => { + if (state.quotationsCount >= maxNestedQuotations) { + return undefined; + } + return SharedMarkdown.blockQuoteRegex.exec(source); + }, parse( capture: SharedMarkdown.Capture, parse: SharedMarkdown.Parser, state: SharedMarkdown.State, ) { const content = capture[1].replace(/^ *> ?/gm, ''); + const currentQuotationsCount = state.quotationsCount ?? 0; return { - content: parse(content, state), + content: parse(content, { + ...state, + quotationsCount: currentQuotationsCount + 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,8 @@ import { relativeMemberInfoSelectorForMembersOfThread } from 'lib/selectors/user-selectors'; import * as SharedMarkdown from 'lib/shared/markdown'; +import { maxNestedQuotations } from 'lib/shared/markdown'; +import type { State } from 'lib/shared/markdown.js'; import type { RelativeMemberInfo } from 'lib/types/thread-types'; import { useSelector } from '../redux/redux-utils'; @@ -78,15 +80,24 @@ 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: State) => { + if (state.quotationsCount >= maxNestedQuotations) { + return undefined; + } + return SharedMarkdown.blockQuoteRegex.exec(source); + }, parse( capture: SharedMarkdown.Capture, parse: SharedMarkdown.Parser, state: SharedMarkdown.State, ) { const content = capture[1].replace(/^ *> ?/gm, ''); + const currentQuotationsCount = state.quotationsCount ?? 0; return { - content: parse(content, state), + content: parse(content, { + ...state, + quotationsCount: currentQuotationsCount + 1, + }), }; }, },