fix(desktop): preserve matched URL parentheses

(cherry picked from commit 6e126d8575859f9ca7a6d20240e2e2ba61c74b7b)
This commit is contained in:
harjoth
2026-07-10 18:16:05 -07:00
committed by brooklyn!
parent d51727f9ba
commit ef9a5f9889
2 changed files with 37 additions and 16 deletions

View File

@@ -134,6 +134,24 @@ describe('preprocessMarkdown', () => {
expect(output).toContain('<https://example.com/wiki/Foo_(bar)>')
})
it('preserves a trailing balanced pair after an earlier stray closing paren', () => {
const output = preprocessMarkdown('See https://example.com/a)(b)')
expect(output).toContain('<https://example.com/a)(b)>')
})
it('strips three wrapper closing parens from raw-url autolinks', () => {
const output = preprocessMarkdown('(((https://example.com/page)))')
expect(output).toBe('(((<https://example.com/page>)))')
})
it('preserves an unmatched opening paren at the end of a raw URL', () => {
const output = preprocessMarkdown('https://example.com/foo(')
expect(output).toContain('<https://example.com/foo(>')
})
it('strips only the wrapper closing paren around balanced-paren URLs', () => {
const output = preprocessMarkdown('(https://example.com/wiki/Foo_(bar))')

View File

@@ -211,29 +211,32 @@ function isUrlOnlyBlock(lines: string[]): boolean {
// Strip only the *unbalanced* trailing `)` — the closing paren of a prose
// wrapper like `(https://example.com/page)`, which the bare-URL matcher
// greedily captures into the href. Parens that belong to the URL itself
// (`…/wiki/Foo_(bar)`) stay intact: a trailing `)` is peeled only while the
// URL holds more `)` than `(`. Count once, then trim from the end — the loop
// only ever removes trailing closers, so `opens` never changes.
// (`…/wiki/Foo_(bar)`) stay intact. Track normal left-to-right pairing and
// peel only a suffix made entirely of unmatched closing parens; this avoids
// sacrificing a valid trailing pair when an earlier stray `)` exists.
function trimUnbalancedTrailingParens(url: string): string {
let opens = 0
let closes = 0
let depth = 0
let unmatchedSuffixStart: number | null = null
for (let index = 0; index < url.length; index += 1) {
const char = url[index]
for (const char of url) {
if (char === '(') {
opens += 1
depth += 1
unmatchedSuffixStart = null
} else if (char === ')') {
closes += 1
if (depth > 0) {
depth -= 1
unmatchedSuffixStart = null
} else if (unmatchedSuffixStart === null) {
unmatchedSuffixStart = index
}
} else {
unmatchedSuffixStart = null
}
}
let end = url.length
while (closes > opens && url[end - 1] === ')') {
closes -= 1
end -= 1
}
return url.slice(0, end)
return unmatchedSuffixStart === null ? url : url.slice(0, unmatchedSuffixStart)
}
function autoLinkRawUrls(text: string): string {