confluence-md-sync 0.8.1 → 0.8.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.
@@ -25,6 +25,7 @@ import { processMacros } from '../macros/registry.js';
25
25
  import { defaultMacroRegistry } from '../macros/index.js';
26
26
  import { renderToStorage } from '../markdown/render.js';
27
27
  import { compareStorage } from './canonical.js';
28
+ import { NOTICE_MACRO_ID } from '../publish/notice.js';
28
29
  import { decodeEntities, elements, getAttr, hasNamespacedElements, parseStorage, serializeStorage, textContent, } from './xhtml.js';
29
30
  /** Конвертирует storage-фрагмент страницы в Markdown. */
30
31
  export function storageToMarkdown(storage, opts = {}) {
@@ -283,7 +284,14 @@ class Converter {
283
284
  }
284
285
  // p / td / th / li / caption и прочие «инлайн-контейнеры» → инлайн.
285
286
  // multiline: это свободный поток (не ячейка) — <br> станет переносом.
286
- const inline = this.inlineToMd(el.children, { multiline: true }).trim();
287
+ let inline = '';
288
+ try {
289
+ inline = this.inlineToMd(el.children, { multiline: true }).trim();
290
+ }
291
+ catch (e) {
292
+ if (!(e instanceof Unrepresentable))
293
+ throw e; /* → голый текст ниже */
294
+ }
287
295
  if (inline !== '')
288
296
  return inline.split('\n').map((l) => guardLineStart(l)).join('\n');
289
297
  // Совсем ничего не вышло — голый текст (может быть пустым).
@@ -301,8 +309,6 @@ class Converter {
301
309
  anchor: 'anchor', detailssummary: 'properties-report',
302
310
  };
303
311
  tryNativeMd(el, name) {
304
- if (this.readable)
305
- return null; // readable-путь остаётся прежним
306
312
  const parts = this.macroParts(el);
307
313
  if (parts === null)
308
314
  return null;
@@ -355,7 +361,7 @@ class Converter {
355
361
  if (norm === null)
356
362
  return null;
357
363
  const cand = `::: properties${dp.join('')}\n${norm.md}\n:::`;
358
- if (!this.verifyNormalizedDetails(cand, norm.rows))
364
+ if (!this.readable && !this.verifyNormalizedDetails(cand, norm.rows))
359
365
  return null;
360
366
  this.stats.normalized++;
361
367
  return cand; // канонической эквивалентности нет по построению — верифицирован текст
@@ -402,6 +408,8 @@ class Converter {
402
408
  }
403
409
  if (candidate === null)
404
410
  return null;
411
+ if (this.readable)
412
+ return candidate; // readable: без канонической верификации (режим и так lossy)
405
413
  return this.verifyMacroMarker(el, candidate) ? candidate : null;
406
414
  }
407
415
  /** Разбирает macro-элемент на параметры и тела; null — посторонние дети. */
@@ -438,6 +446,9 @@ class Converter {
438
446
  }
439
447
  }
440
448
  macroToMd(el) {
449
+ // Баннер managedNotice — артефакт публикации, в md-источник не попадает никогда.
450
+ if (getAttr(el, 'ac:macro-id') === NOTICE_MACRO_ID)
451
+ return '';
441
452
  const name = getAttr(el, 'ac:name') ?? '';
442
453
  const nativeMd = this.tryNativeMd(el, name);
443
454
  if (nativeMd !== null) {
@@ -1226,6 +1237,21 @@ class Converter {
1226
1237
  return `{{img:${filename}${attrStr}}}`;
1227
1238
  }
1228
1239
  acLinkToMd(el) {
1240
+ // readable безотказен: непредставимая ссылка (user-mention, ссылка без цели,
1241
+ // card-appearance) деградирует до своего текста.
1242
+ if (this.readable) {
1243
+ try {
1244
+ return this.acLinkStrict(el);
1245
+ }
1246
+ catch (e) {
1247
+ if (!(e instanceof Unrepresentable))
1248
+ throw e;
1249
+ return escapeMdText(textContent(el.children), {}, true).trim();
1250
+ }
1251
+ }
1252
+ return this.acLinkStrict(el);
1253
+ }
1254
+ acLinkStrict(el) {
1229
1255
  if (el.attrs.length > 0)
1230
1256
  throw new Unrepresentable();
1231
1257
  const kids = elements(el.children);
@@ -28,6 +28,7 @@ export interface ManagedNoticeOptions {
28
28
  }
29
29
  export declare const DEFAULT_MANAGED_NOTICE_TEXT: string;
30
30
  export declare const DEFAULT_MANAGED_NOTICE_LINK_TEXT = "docs-studio";
31
+ export declare const NOTICE_MACRO_ID = "0f0e0d0c-0b0a-4009-8008-000000000001";
31
32
  /** Строит storage-разметку баннера (одиночный `<ac:structured-macro>`). */
32
33
  export declare function buildManagedNotice(opts: ManagedNoticeOptions): string;
33
34
  /** Дописывает баннер в начало (top) или конец (bottom) storage-контента. */
@@ -12,7 +12,7 @@ export const DEFAULT_MANAGED_NOTICE_LINK_TEXT = 'docs-studio';
12
12
  // Фиксированный ac:macro-id: баннер обязан давать БАЙТ-В-БАЙТ одинаковый
13
13
  // storage при каждой публикации. Со случайным id (generateMacroId) content-
14
14
  // hash менялся бы каждый раз → страница вечно считалась бы изменённой.
15
- const NOTICE_MACRO_ID = '0f0e0d0c-0b0a-4009-8008-000000000001';
15
+ export const NOTICE_MACRO_ID = '0f0e0d0c-0b0a-4009-8008-000000000001';
16
16
  /** Собирает `<a href>`-ссылку на источник. */
17
17
  function noticeLink(opts) {
18
18
  const text = opts.linkText ?? DEFAULT_MANAGED_NOTICE_LINK_TEXT;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "confluence-md-sync",
3
- "version": "0.8.1",
3
+ "version": "0.8.2",
4
4
  "description": "Publish Markdown to Confluence (Data Center & Cloud): idempotent page sync, attachment dedup, tables and a pluggable macro system",
5
5
  "keywords": [
6
6
  "confluence",