| @@ -2637,9 +2637,17 @@ | ||
| 2637 | 2637 | }); |
| 2638 | 2638 | |
| 2639 | 2639 | // Process standalone URLs - but NOT if they're already in <a> tags or brackets |
| 2640 | 2640 | // Updated pattern to be more careful about what it matches |
| 2641 | - const urlPattern = /(^|[^">=\[\]])(https?:\/\/[^\s<"\[\]]+)(?![^<]*<\/a>)(?!\])/gim; | |
| 2641 | + // | |
| 2642 | + // The prefix group (plan 78618d) is the start of a line, an ordinary | |
| 2643 | + // character, OR a tag this formatter itself opened or closed (paragraph, | |
| 2644 | + // list item, table cell, heading, emphasis, line break), so an address | |
| 2645 | + // that opens a paragraph or sits inside **bold** is linked too. Never | |
| 2646 | + // an anchor (its text would be linked twice; the lookahead guards that | |
| 2647 | + // as well) and never a code span, which stays as typed. No lookbehind, | |
| 2648 | + // for Safari. | |
| 2649 | + const urlPattern = /(^|[^">=\[\]]|<\/?(?:p|li|td|th|h[1-6]|strong|em|b|i|del|br)(?:\s[^<>]*)?\/?>)(https?:\/\/[^\s<"\[\]]+)(?![^<]*<\/a>)(?!\])/gim; | |
| 2642 | 2650 | processedText = processedText.replace(urlPattern, (match, prefix, url) => { |
| 2643 | 2651 | // Extra check: make sure this isn't already linked |
| 2644 | 2652 | if (match.includes('href=') || match.includes('</a>')) { |
| 2645 | 2653 | return match; |
| @@ -2650,10 +2658,14 @@ | ||
| 2650 | 2658 | const safeUrl = safeEncodeUrl(cleanUrl); |
| 2651 | 2659 | return `${prefix}<a href="${safeUrl}" target="${linkTarget}">${cleanUrl}</a>`; |
| 2652 | 2660 | }); |
| 2653 | 2661 | |
| 2654 | - // Process www. URLs - but NOT if they're already in <a> tags or brackets | |
| 2655 | - const wwwPattern = /(^|[^">/\[\]])(www\.[\S]+)(?![^<]*<\/a>)(?!\])/gim; | |
| 2662 | + // Process www. URLs - but NOT if they're already in <a> tags or brackets. | |
| 2663 | + // The address stops at the same characters the https pattern stops at | |
| 2664 | + // (plan 78618d): it used to run through the closing paragraph tag, so | |
| 2665 | + // "Try www.example.com/page." linked to www.example.com/page.%3C/p%3E. | |
| 2666 | + // Same prefix group as the https pattern, same reasons. | |
| 2667 | + const wwwPattern = /(^|[^">/\[\]]|<\/?(?:p|li|td|th|h[1-6]|strong|em|b|i|del|br)(?:\s[^<>]*)?\/?>)(www\.[^\s<"\[\]]+)(?![^<]*<\/a>)(?!\])/gim; | |
| 2656 | 2668 | processedText = processedText.replace(wwwPattern, (match, prefix, url) => { |
| 2657 | 2669 | // Extra check: make sure this isn't already linked |
| 2658 | 2670 | if (match.includes('href=') || match.includes('</a>')) { |
| 2659 | 2671 | return match; |
| @@ -2717,13 +2729,19 @@ | ||
| 2717 | 2729 | protectedSegments.push(match); |
| 2718 | 2730 | return placeholder; |
| 2719 | 2731 | }); |
| 2720 | 2732 | |
| 2721 | - // Step 1c: Also protect bare URLs that might exist | |
| 2733 | + // Step 1c: Also protect bare URLs that might exist. Trailing emphasis | |
| 2734 | + // markers are not part of the address (plan 78618d): **https://x.com** | |
| 2735 | + // used to protect "https://x.com**", so the bold never paired and the | |
| 2736 | + // asterisks ended up inside the link. The markers stay outside the | |
| 2737 | + // placeholder so the styling rules below can pair them. | |
| 2722 | 2738 | protectedText = protectedText.replace(/(https?:\/\/[^\s<>"]+)/gi, function(match) { |
| 2739 | + const markers = match.match(/[*_~]+$/); | |
| 2740 | + const url = markers ? match.slice(0, match.length - markers[0].length) : match; | |
| 2723 | 2741 | const placeholder = `__PROTECTED_${protectedSegments.length}__`; |
| 2724 | - protectedSegments.push(match); | |
| 2725 | - return placeholder; | |
| 2742 | + protectedSegments.push(url); | |
| 2743 | + return placeholder + (markers ? markers[0] : ''); | |
| 2726 | 2744 | }); |
| 2727 | 2745 | |
| 2728 | 2746 | // Step 2: Now apply text styling to the protected text |
| 2729 | 2747 | // Handle bold text (**text**) |