talon-agent 3.12.2 → 3.12.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -52,8 +52,18 @@ function isWellFormedHtml(html: string): boolean {
|
|
|
52
52
|
return stack.length === 0;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/** `\x00URLn\x00` → the stashed URL, or undefined when it isn't a placeholder. */
|
|
56
|
+
function resolveUrlPlaceholder(
|
|
57
|
+
target: string,
|
|
58
|
+
urls: string[],
|
|
59
|
+
): string | undefined {
|
|
60
|
+
// oxlint-disable-next-line no-control-regex
|
|
61
|
+
const match = /^\x00URL(\d+)\x00$/.exec(target);
|
|
62
|
+
return match ? urls[Number(match[1])] : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
55
65
|
/** Apply the inline delimiter passes to already-escaped text. */
|
|
56
|
-
function applyInlineFormatting(input: string): string {
|
|
66
|
+
function applyInlineFormatting(input: string, urls: string[]): string {
|
|
57
67
|
let out = input;
|
|
58
68
|
// Bold+italic: ***text*** — must run before the ** and * passes, which
|
|
59
69
|
// would otherwise split it into the crossed pair `<b><i>x</b></i>`.
|
|
@@ -64,13 +74,16 @@ function applyInlineFormatting(input: string): string {
|
|
|
64
74
|
out = out.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<i>$1</i>");
|
|
65
75
|
// Italic: _text_ (surrounded by non-word or start/end)
|
|
66
76
|
out = out.replace(/(?<!\w)_(.+?)_(?!\w)/g, "<i>$1</i>");
|
|
67
|
-
// Links: [text](url) — only safe URL schemes become anchors.
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
)
|
|
77
|
+
// Links: [text](url) — only safe URL schemes become anchors. The target
|
|
78
|
+
// arrives as a `\x00URLn\x00` placeholder (step 2b), so the passes above
|
|
79
|
+
// never saw the URL's own punctuation; resolve it back here. Both text and
|
|
80
|
+
// url were already HTML-escaped (quotes included, so the href attribute
|
|
81
|
+
// can't be broken out of); escaping again here corrupted every & in a
|
|
82
|
+
// query string into &amp;.
|
|
83
|
+
out = out.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_, text, target) => {
|
|
84
|
+
const url = resolveUrlPlaceholder(target, urls) ?? target;
|
|
85
|
+
return /^https?:\/\//i.test(url) ? `<a href="${url}">${text}</a>` : text;
|
|
86
|
+
});
|
|
74
87
|
// Strikethrough: ~~text~~
|
|
75
88
|
out = out.replace(/~~(.+?)~~/g, "<s>$1</s>");
|
|
76
89
|
return out;
|
|
@@ -110,6 +123,23 @@ export function markdownToTelegramHtml(text: string): string {
|
|
|
110
123
|
return placeholder;
|
|
111
124
|
});
|
|
112
125
|
|
|
126
|
+
// Step 2b: Stash link targets. The emphasis passes run before the link
|
|
127
|
+
// pass and have no idea what a URL is, so punctuation inside the target
|
|
128
|
+
// used to be consumed as a delimiter: a Chomikuj path like
|
|
129
|
+
// `Jak+Si*c4*99+Bawi*c4*85+Ludzie.mp3` reads as two italic runs, the
|
|
130
|
+
// resulting tags cross, and the well-formedness guard then discards the
|
|
131
|
+
// formatting for the *whole* message. Placeholders carry no delimiters,
|
|
132
|
+
// so the URL is invisible to every pass but the link one.
|
|
133
|
+
const urls: string[] = [];
|
|
134
|
+
processed = processed.replace(
|
|
135
|
+
/(\[[^\]\n]*\])\(([^()\s]+)\)/g,
|
|
136
|
+
(_match, label: string, url: string) => {
|
|
137
|
+
const placeholder = `\x00URL${urls.length}\x00`;
|
|
138
|
+
urls.push(escapeHtml(url));
|
|
139
|
+
return `${label}(${placeholder})`;
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
|
|
113
143
|
// Step 3: Escape HTML in remaining plain text (before applying formatting).
|
|
114
144
|
// Escape HTML in plain text segments (skip placeholders marked with \x00)
|
|
115
145
|
// oxlint-disable-next-line no-control-regex
|
|
@@ -119,11 +149,18 @@ export function markdownToTelegramHtml(text: string): string {
|
|
|
119
149
|
// actually parseable. `processed` at this point holds escaped text plus
|
|
120
150
|
// tag-free placeholders, so it is a safe unformatted fallback.
|
|
121
151
|
const unformatted = processed;
|
|
122
|
-
processed = applyInlineFormatting(processed);
|
|
152
|
+
processed = applyInlineFormatting(processed, urls);
|
|
123
153
|
if (!isWellFormedHtml(processed)) {
|
|
124
154
|
processed = unformatted;
|
|
125
155
|
}
|
|
126
156
|
|
|
157
|
+
// Step 4b: Restore any link target that never became an anchor — a
|
|
158
|
+
// non-http scheme, or the unformatted fallback above. Anchors already
|
|
159
|
+
// resolved their placeholder inline.
|
|
160
|
+
for (let i = 0; i < urls.length; i++) {
|
|
161
|
+
processed = processed.replace(`\x00URL${i}\x00`, () => urls[i]!);
|
|
162
|
+
}
|
|
163
|
+
|
|
127
164
|
// Steps 5+6: Restore code spans and fenced blocks. The replacement MUST
|
|
128
165
|
// go through a function: with a string, String.replace interprets $-
|
|
129
166
|
// substitution patterns in the *code content* ($& re-inserts the
|