@mikitasazan/notify 1.15.0 → 1.15.2

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.
Files changed (2) hide show
  1. package/dist/render.js +44 -14
  2. package/package.json +1 -1
package/dist/render.js CHANGED
@@ -350,12 +350,28 @@ export const markdownToTelegram = (text) => {
350
350
  .filter((c) => c !== '')
351
351
  .join(' · ');
352
352
  }
353
+ // Each replace used to run over the OUTPUT of the previous one, so a tag
354
+ // this function had just written was still visible to the next pattern.
355
+ // Two shapes came out illegal for Telegram, and Telegram answers 4xx to
356
+ // both — the package does not retry a 4xx, so the whole card is lost:
357
+ // `[![alt](img)](url)` — the README badge — became `<a><a>…</a></a>`,
358
+ // `` `a **b** c` `` became `<code>a <b>b</b> c</code>`.
359
+ // A produced tag is therefore parked in `spans` behind a sentinel that no
360
+ // pattern below can match, and put back only after the last replace.
361
+ // `plain` unwraps a parked span to its text, which is how a link label
362
+ // that already holds a link (or code) is flattened instead of nested.
363
+ const spans = [];
364
+ const park = (html) => `\u0000${spans.push(html) - 1}\u0000`;
365
+ const plain = (s) => s.replace(/\u0000(\d+)\u0000/g, (_, i) => spans[Number(i)].replace(/<[^>]+>/g, ''));
366
+ const bold = (s) => s.replace(/\*\*([^*\n]+)\*\*/g, '<b>$1</b>');
353
367
  row = row
354
- .replace(/!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/g, (_, alt, url) => `<a href="${url}">${alt.trim() || 'image'}</a>`)
355
- .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, '<a href="$2">$1</a>')
356
- .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1')
368
+ // Code first: nothing inside a `code` span may become a tag.
369
+ .replace(/`([^`\n]+)`/g, (_, code) => park(`<code>${code}</code>`))
370
+ .replace(/!\[([^\]]*)\]\((https?:\/\/[^\s)]+)\)/g, (_, alt, url) => park(`<a href="${url}">${plain(alt).trim() || 'image'}</a>`))
371
+ .replace(/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g, (_, label, url) => park(`<a href="${url}">${bold(plain(label)).trim() || 'link'}</a>`))
372
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, (_, label) => plain(label))
357
373
  .replace(/\*\*([^*\n]+)\*\*/g, '<b>$1</b>')
358
- .replace(/`([^`\n]+)`/g, '<code>$1</code>');
374
+ .replace(/\u0000(\d+)\u0000/g, (_, i) => spans[Number(i)]);
359
375
  out.push(row);
360
376
  }
361
377
  if (fence) {
@@ -374,12 +390,20 @@ export const markdownToTelegram = (text) => {
374
390
  */
375
391
  const firstSection = (text) => {
376
392
  const lines = text.replace(/<!--[\s\S]*?-->/g, '').split('\n');
377
- let headings = 0;
393
+ // The cut is made at the next heading of the FIRST heading's level, not at
394
+ // any heading at all. `#{1,6}` treated a `###` subsection inside the first
395
+ // section as the second heading, so everything after it was silently
396
+ // dropped from a merged PR or a closed issue — the body the card exists to
397
+ // carry. A deeper heading belongs to the section it sits in and is kept.
398
+ let level = 0;
378
399
  const kept = [];
379
400
  for (const line of lines) {
380
- if (/^\s*#{1,6}\s+/.test(line)) {
381
- headings += 1;
382
- if (headings === 2) {
401
+ const m = line.match(/^\s*(#{1,6})\s+/);
402
+ if (m) {
403
+ if (level === 0) {
404
+ level = m[1].length;
405
+ }
406
+ else if (m[1].length <= level) {
383
407
  break;
384
408
  }
385
409
  }
@@ -593,7 +617,7 @@ const twoBlocks = (run, change) => {
593
617
  // the action text becomes the link: `Report: <a>Analytics for 12.08</a>`.
594
618
  // `aside` is the one qualifier an identifier needs to be readable on its own —
595
619
  // which day a report covers, which day its arrows are measured against. It
596
- // rides in brackets ON the type line instead of taking a row of its own,
620
+ // rides in brackets on the TYPE WORD instead of taking a row of its own,
597
621
  // because a row of its own reads as another fact about the subject rather than
598
622
  // as part of the name.
599
623
  const typeLine = (icon, type, action, url, aside) => {
@@ -601,7 +625,13 @@ const typeLine = (icon, type, action, url, aside) => {
601
625
  // template prints the word "null". That is how line 2 of a card became
602
626
  // `ℹ️ null` — reachable through `--json` and through a direct call from JS,
603
627
  // where there are no types.
604
- const tail = aside ? ` (${esc(aside)})` : '';
628
+ // The qualifier sits ON the type word, before the colon — `Deploy (OK):`,
629
+ // `Report (20.08):` — never after the name. After the name it read as a
630
+ // remark about the title («Deploy to Beget (OK)») rather than about the
631
+ // event, and the owner asked on 03.09.2026 why the outcome trailed the
632
+ // title at all. `field`/`fieldLink` escape the label, so the raw aside is
633
+ // safe to fold into it.
634
+ const label = aside ? `${cap(type)} (${aside})` : type;
605
635
  const name = action?.trim();
606
636
  // No name: the card must not invent one. It used to write the word `open`
607
637
  // into the identifier slot, so a report with a link and no title arrived as
@@ -611,11 +641,11 @@ const typeLine = (icon, type, action, url, aside) => {
611
641
  // something else instead. The type word itself takes the link, so nothing
612
642
  // clickable is lost and nothing false is said.
613
643
  if (!name) {
614
- const bare = `<b>${esc(cap(type))}</b>`;
615
- return `${icon} ${url ? `<a href="${esc(url)}">${bare}</a>` : bare}${tail}`;
644
+ const bare = `<b>${esc(cap(label))}</b>`;
645
+ return `${icon} ${url ? `<a href="${esc(url)}">${bare}</a>` : bare}`;
616
646
  }
617
- const line = url ? fieldLink(type, url, name) : field(type, name);
618
- return line === null ? `${icon} <b>${esc(cap(type))}</b>${tail}` : `${icon} ${line}${tail}`;
647
+ const line = url ? fieldLink(label, url, name) : field(label, name);
648
+ return line === null ? `${icon} <b>${esc(cap(label))}</b>` : `${icon} ${line}`;
619
649
  };
620
650
  // `workflowUrl ?? url`: half the senders send the run link under the name
621
651
  // `--url` — that name was in the package before and stayed in their calls.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mikitasazan/notify",
3
- "version": "1.15.0",
3
+ "version": "1.15.2",
4
4
  "description": "Единая типизированная отправка Telegram-уведомлений (форум-темы, маршрутизация, ретраи) для всех проектов",
5
5
  "type": "module",
6
6
  "license": "MIT",