@asciidoctor/core 4.0.0 → 4.0.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.
@@ -1,4 +1,4 @@
1
- var version = "4.0.0";
1
+ var version = "4.0.2";
2
2
  const packageJson = {
3
3
  version: version};
4
4
 
@@ -1745,14 +1745,23 @@ Logger.BasicFormatter = class {
1745
1745
 
1746
1746
  Logger.AutoFormattingMessage = {
1747
1747
  /**
1748
- * Attach auto-formatting to any plain object carrying { text, source_location }.
1749
- * @param {{text: string, source_location?: string}} obj
1748
+ * Attach auto-formatting to any plain object carrying
1749
+ * { text, source_location, include_location }.
1750
+ *
1751
+ * The location(s) are rendered only by inspect()/toString() (used when a
1752
+ * stderr Logger formats the line); the structured `source_location` /
1753
+ * `include_location` remain on the object so a MemoryLogger can record them
1754
+ * on the resulting LogMessage without duplicating them inside `text`.
1755
+ * @param {{text: string, source_location?: any, include_location?: any}} obj
1750
1756
  * @returns {typeof obj} The same object with inspect() and toString() added.
1751
1757
  */
1752
1758
  attach(obj) {
1753
1759
  obj.inspect = function () {
1754
1760
  const sloc = this.source_location;
1755
- return sloc ? `${sloc}: ${this.text}` : this.text
1761
+ const iloc = this.include_location;
1762
+ let text = sloc ? `${sloc}: ${this.text}` : this.text;
1763
+ if (iloc) text += ` (${iloc})`;
1764
+ return text
1756
1765
  };
1757
1766
  obj.toString = obj.inspect;
1758
1767
  return obj
@@ -1763,27 +1772,45 @@ Logger.AutoFormattingMessage = {
1763
1772
 
1764
1773
  /** Wrapper stored by MemoryLogger; provides getSeverity/getText/getSourceLocation. */
1765
1774
  class LogMessage {
1775
+ /**
1776
+ * @param {string} severity - Severity label, e.g. 'ERROR'.
1777
+ * @param {string|{text: string, source_location?: import('./reader.js').Cursor}|null} message
1778
+ */
1766
1779
  constructor(severity, message) {
1767
1780
  this.message = message;
1781
+ /** @type {string} */
1768
1782
  this.severity = severity; // string label, e.g. 'ERROR'
1769
1783
  // AutoFormattingMessage objects carry { text, source_location }
1770
1784
  if (message !== null && typeof message === 'object' && 'text' in message) {
1771
- this._text = message.text;
1772
- this._sourceLocation = message.source_location ?? null;
1785
+ /** @type {string} */
1786
+ this.text = message.text;
1787
+ /** @type {import('./reader.js').Cursor|null} */
1788
+ this.sourceLocation = message.source_location ?? null;
1773
1789
  } else {
1774
- this._text = message != null ? String(message) : '';
1775
- this._sourceLocation = null;
1790
+ this.text = message != null ? String(message) : '';
1791
+ this.sourceLocation = null;
1776
1792
  }
1777
1793
  }
1778
1794
 
1795
+ /**
1796
+ * @returns {string} The severity label, e.g. 'ERROR'.
1797
+ */
1779
1798
  getSeverity() {
1780
1799
  return this.severity
1781
1800
  }
1801
+
1802
+ /**
1803
+ * @returns {string} The message text.
1804
+ */
1782
1805
  getText() {
1783
- return this._text
1806
+ return this.text
1784
1807
  }
1808
+
1809
+ /**
1810
+ * @returns {import('./reader.js').Cursor|undefined} The source location, if any.
1811
+ */
1785
1812
  getSourceLocation() {
1786
- return this._sourceLocation ?? undefined
1813
+ return this.sourceLocation ?? undefined
1787
1814
  }
1788
1815
  }
1789
1816
 
@@ -1796,6 +1823,7 @@ class MemoryLogger {
1796
1823
  // matching Ruby's MemoryLogger (level: UNKNOWN). The add() method stores all
1797
1824
  // messages unconditionally — level is only used by the isDebug() guard.
1798
1825
  this.level = Severity.UNKNOWN;
1826
+ /** @type {LogMessage[]} */
1799
1827
  this.messages = [];
1800
1828
  }
1801
1829
 
@@ -1803,6 +1831,9 @@ class MemoryLogger {
1803
1831
  return new MemoryLogger()
1804
1832
  }
1805
1833
 
1834
+ /**
1835
+ * @returns {LogMessage[]} The log messages recorded so far, in order.
1836
+ */
1806
1837
  getMessages() {
1807
1838
  return this.messages
1808
1839
  }
@@ -3001,9 +3032,10 @@ class AbstractNode {
3001
3032
  * Construct a URI reference or data URI to the target image.
3002
3033
  *
3003
3034
  * If the target image is already a URI it is left untouched (unless data-uri
3004
- * conversion is requested). The image is resolved relative to the directory
3005
- * named by assetDirKey. When data-uri is enabled and the safe level permits,
3006
- * the image is embedded as a Base64 data URI.
3035
+ * conversion is requested). If the target image is a data URI, then it is
3036
+ * already an embedded image, so it is returned as-is. The image is resolved
3037
+ * relative to the directory named by assetDirKey. When data-uri is enabled and
3038
+ * the safe level permits, the image is embedded as a Base64 data URI.
3007
3039
  *
3008
3040
  * NOTE: When the document has both 'data-uri' and 'allow-uri-read' enabled
3009
3041
  * and the resolved image URL is a remote URI, this method returns a Promise
@@ -3014,6 +3046,8 @@ class AbstractNode {
3014
3046
  * @returns {Promise<string>} a Promise resolving to a String reference or data URI.
3015
3047
  */
3016
3048
  async imageUri(targetImage, assetDirKey = 'imagesdir') {
3049
+ // A data URI is already an embedded image, so use it as-is rather than reading or re-encoding it.
3050
+ if (targetImage.startsWith('data:')) return targetImage
3017
3051
  const doc = this.document;
3018
3052
  if (doc.safe < SafeMode.SECURE && doc.hasAttribute('data-uri')) {
3019
3053
  let imagesBase;
@@ -5421,7 +5455,18 @@ class SecurityError extends Error {
5421
5455
  function applyBackendTraits(instance) {
5422
5456
  instance._backendTraits = null;
5423
5457
 
5424
- instance.basebackend = function (value = null) {
5458
+ // Install a Ruby-style trait accessor method, but never clobber a flat string
5459
+ // property the converter already declared (convention #2). Overwriting e.g.
5460
+ // `converter.outfilesuffix = '.html'` with a method would silently turn the
5461
+ // author's string into a function; the backend traits stay reachable through
5462
+ // `_getBackendTraits()` instead.
5463
+ const defineTraitAccessor = (name, fn) => {
5464
+ const existing = Object.getOwnPropertyDescriptor(instance, name);
5465
+ if (existing && typeof existing.value !== 'function') return
5466
+ instance[name] = fn;
5467
+ };
5468
+
5469
+ defineTraitAccessor('basebackend', function (value = null) {
5425
5470
  if (value) {
5426
5471
  const traits = (this._backendTraits ??= {});
5427
5472
  traits.basebackend = value;
@@ -5435,19 +5480,19 @@ function applyBackendTraits(instance) {
5435
5480
  return value
5436
5481
  }
5437
5482
  return this._getBackendTraits().basebackend
5438
- };
5439
- instance.filetype = function (value = null) {
5483
+ });
5484
+ defineTraitAccessor('filetype', function (value = null) {
5440
5485
  if (value) return (this._getBackendTraits().filetype = value)
5441
5486
  return this._getBackendTraits().filetype
5442
- };
5443
- instance.htmlsyntax = function (value = null) {
5487
+ });
5488
+ defineTraitAccessor('htmlsyntax', function (value = null) {
5444
5489
  if (value) return (this._getBackendTraits().htmlsyntax = value)
5445
5490
  return this._getBackendTraits().htmlsyntax
5446
- };
5447
- instance.outfilesuffix = function (value = null) {
5491
+ });
5492
+ defineTraitAccessor('outfilesuffix', function (value = null) {
5448
5493
  if (value) return (this._getBackendTraits().outfilesuffix = value)
5449
5494
  return this._getBackendTraits().outfilesuffix
5450
- };
5495
+ });
5451
5496
  instance.supportsTemplates = function (value = true) {
5452
5497
  this._getBackendTraits().supportsTemplates = value;
5453
5498
  };
@@ -5531,7 +5576,9 @@ function normalizeConverter(converter, backend) {
5531
5576
  }
5532
5577
  }
5533
5578
 
5534
- // Apply the BackendTraits mixin so Document can call the standard accessor methods.
5579
+ // Apply the BackendTraits mixin so Document can read traits via
5580
+ // _getBackendTraits(). Flat string properties (convention #2) are preserved:
5581
+ // applyBackendTraits does not overwrite an existing same-named data property.
5535
5582
  applyBackendTraits(converter);
5536
5583
  if (traits) {
5537
5584
  converter._backendTraits = traits;
@@ -6268,7 +6315,7 @@ function resolveBrowserIncludePath(reader, target, attrlist) {
6268
6315
  // ── Rule 2: target is an absolute URL (http:// / https:// / …) ───────────
6269
6316
  } else if (isUriish(pTarget)) {
6270
6317
  const descends = pathResolver.descendsFrom(pTarget, baseDir);
6271
- if (descends === false && !doc.getAttribute('allow-uri-read')) {
6318
+ if (descends === false && !doc.hasAttribute('allow-uri-read')) {
6272
6319
  return reader.replaceNextLine(_linkReplacement(reader, target, attrlist))
6273
6320
  }
6274
6321
  incPath = relpath = pTarget;
@@ -6302,7 +6349,7 @@ function resolveBrowserIncludePath(reader, target, attrlist) {
6302
6349
  } else {
6303
6350
  // Nested include: context dir is an absolute URL.
6304
6351
  const ctxDescends = pathResolver.descendsFrom(ctxDir, baseDir);
6305
- if (ctxDescends !== false || doc.getAttribute('allow-uri-read')) {
6352
+ if (ctxDescends !== false || doc.hasAttribute('allow-uri-read')) {
6306
6353
  incPath = `${ctxDir}/${pTarget}`;
6307
6354
  relpath = ctxDescends !== false ? incPath.slice(ctxDescends) : pTarget;
6308
6355
  } else {
@@ -6966,22 +7013,34 @@ class Reader {
6966
7013
  }
6967
7014
 
6968
7015
  /** @param {string} msg @param {{ sourceLocation?: any, includeLocation?: any }} [opts] */
6969
- _logWarn(msg, { sourceLocation, includeLocation } = {}) {
6970
- let text = sourceLocation ? `${sourceLocation.lineInfo}: ${msg}` : msg;
6971
- if (includeLocation) text += ` (${includeLocation.lineInfo})`;
6972
- this.logger.warn(text);
7016
+ _logWarn(msg, opts = {}) {
7017
+ this.logger.warn(this._messageWithContext(msg, opts));
6973
7018
  }
6974
7019
  _logError(msg, opts = {}) {
6975
- let text = opts.sourceLocation
6976
- ? `${opts.sourceLocation.lineInfo}: ${msg}`
6977
- : msg;
6978
- if (opts.includeLocation) text += ` (${opts.includeLocation.lineInfo})`;
6979
- this.logger.error(text);
7020
+ this.logger.error(this._messageWithContext(msg, opts));
6980
7021
  }
6981
7022
  /** @param {string} msg @param {{ sourceLocation?: any }} [opts] */
6982
- _logInfo(msg, { sourceLocation } = {}) {
6983
- const text = sourceLocation ? `${sourceLocation.lineInfo}: ${msg}` : msg;
6984
- this.logger.info(text);
7023
+ _logInfo(msg, opts = {}) {
7024
+ this.logger.info(this._messageWithContext(msg, opts));
7025
+ }
7026
+
7027
+ /**
7028
+ * Build an auto-formatting message that keeps the cursor as a structured
7029
+ * source_location (rather than baking it into the text). When displayed by a
7030
+ * stderr Logger the location is rendered as a "<path>: line <N>: " prefix, but
7031
+ * a MemoryLogger records it separately on the LogMessage so consumers can call
7032
+ * getSourceLocation(). Mirrors Ruby's Logging#message_with_context.
7033
+ * @param {string} msg
7034
+ * @param {{ sourceLocation?: any, includeLocation?: any }} [opts]
7035
+ * @internal
7036
+ */
7037
+ _messageWithContext(msg, { sourceLocation, includeLocation } = {}) {
7038
+ if (!sourceLocation && !includeLocation) return msg
7039
+ return Logger.AutoFormattingMessage.attach({
7040
+ text: msg,
7041
+ source_location: sourceLocation ?? null,
7042
+ include_location: includeLocation ?? null,
7043
+ })
6985
7044
  }
6986
7045
  }
6987
7046
 
@@ -7739,7 +7798,7 @@ class PreprocessorReader extends Reader {
7739
7798
  }
7740
7799
 
7741
7800
  if (isUriish(target) || typeof this._dir !== 'string') {
7742
- if (!doc.getAttribute('allow-uri-read')) {
7801
+ if (!doc.hasAttribute('allow-uri-read')) {
7743
7802
  this._logWarn(
7744
7803
  `cannot include contents of URI: ${target} (allow-uri-read attribute not enabled)`,
7745
7804
  { sourceLocation: this.cursor }
@@ -10349,7 +10408,12 @@ class Parser {
10349
10408
  }
10350
10409
  }
10351
10410
  (intro ?? section).blocks.push(newBlock);
10352
- for (const key of Object.keys(attributes)) delete attributes[key];
10411
+ // Reset the shared attributes object for the next block. Use Reflect.ownKeys
10412
+ // (not Object.keys) so the Symbol-keyed attribute entries (ATTR_ENTRIES_KEY)
10413
+ // are cleared too; otherwise the array of AttributeEntry objects leaks and
10414
+ // accumulates across blocks, causing reassigned attributes (e.g. a body-level
10415
+ // `:name:` redefined later) to all resolve to the final value at playback time.
10416
+ for (const key of Reflect.ownKeys(attributes)) delete attributes[key];
10353
10417
  }
10354
10418
  }
10355
10419
 
@@ -11196,7 +11260,13 @@ class Parser {
11196
11260
  }
11197
11261
  }
11198
11262
 
11199
- if (Object.keys(attributes).length > 0) block.updateAttributes(attributes);
11263
+ // Reflect.ownKeys (not Object.keys) so a block carrying only Symbol-keyed
11264
+ // attribute entries (ATTR_ENTRIES_KEY) — e.g. an `:attr:` entry immediately
11265
+ // preceding a list or table — still receives them, matching Ruby's
11266
+ // `attributes.empty?` where the `:attribute_entries` key is counted. Without this
11267
+ // the entries are dropped and the attribute is not played back for that block.
11268
+ if (Reflect.ownKeys(attributes).length > 0)
11269
+ block.updateAttributes(attributes);
11200
11270
  block.commitSubs();
11201
11271
 
11202
11272
  if (block.hasSub('callouts')) {
@@ -16037,7 +16107,7 @@ class Document extends AbstractBlock {
16037
16107
 
16038
16108
  const attrOverrides = this._attributeOverrides;
16039
16109
  attrOverrides.asciidoctor = '';
16040
- attrOverrides['asciidoctor-version'] = '3.0.0.dev'; // matches Ruby VERSION
16110
+ attrOverrides['asciidoctor-version'] = packageJson.version;
16041
16111
 
16042
16112
  const safeModeName = SafeMode.nameForValue(this.safe);
16043
16113
  attrOverrides['safe-mode-name'] = safeModeName;
@@ -16224,13 +16294,21 @@ class Document extends AbstractBlock {
16224
16294
  }
16225
16295
 
16226
16296
  // Pre-compute all async text values (titles, list item text, cell text, reftexts)
16227
- // so that synchronous getters work correctly during conversion.
16228
- await this._resolveAllTexts(this);
16229
- // Reset the footnote counter so that body-content footnotes (processed during conversion)
16230
- // start numbering from 1, reproducing Ruby's "out of sequence" quirk: title footnotes are
16231
- // numbered during parsing via apply_title_subs, then the counter restarts for body content.
16232
- delete this.attributes['footnote-number'];
16233
- delete this._counters['footnote-number'];
16297
+ // so that synchronous getters work correctly during conversion. _resolveAllTexts
16298
+ // replays attribute entries in document order (mirroring conversion) so body-level
16299
+ // attribute (re)assignments are in scope; snapshot and restore the document
16300
+ // attributes so the downstream steps and conversion still start from the restored
16301
+ // (header) state, matching Ruby's restore_attributes-before-convert invariant.
16302
+ //
16303
+ // This runs in two passes because list item / table cell / dlist text may contain
16304
+ // natural cross-references (e.g. <<Some section title>>) that resolve against the
16305
+ // reftext→id map. Pass 1 substitutes titles and reftexts only, so every section
16306
+ // reftext is known; the map is then built; pass 2 substitutes the block content
16307
+ // text, where resolveId() can now match those natural references.
16308
+ const attributesSnapshot = { ...this.attributes };
16309
+ // Pass 1: titles + reftexts (no block content text yet).
16310
+ await this._resolveAllTexts(this, false);
16311
+ this._restoreAttributeSnapshot(attributesSnapshot);
16234
16312
  // Pre-compute reftext for all registered inline anchor nodes.
16235
16313
  for (const ref of Object.values(this.catalog.refs)) {
16236
16314
  if (ref && typeof ref.precomputeReftext === 'function') {
@@ -16239,6 +16317,15 @@ class Document extends AbstractBlock {
16239
16317
  }
16240
16318
  // Build the reftext→id lookup map so that resolveId() is synchronous.
16241
16319
  await this._buildReftextsMap();
16320
+ // Pass 2: list item / table cell / dlist text, now that natural cross-references
16321
+ // can be resolved against the reftext→id map.
16322
+ await this._resolveAllTexts(this, true);
16323
+ this._restoreAttributeSnapshot(attributesSnapshot);
16324
+ // Reset the footnote counter so that body-content footnotes (processed during conversion)
16325
+ // start numbering from 1, reproducing Ruby's "out of sequence" quirk: title footnotes are
16326
+ // numbered during parsing via apply_title_subs, then the counter restarts for body content.
16327
+ delete this.attributes['footnote-number'];
16328
+ delete this._counters['footnote-number'];
16242
16329
 
16243
16330
  this._parsed = true;
16244
16331
  return this
@@ -17293,12 +17380,39 @@ class Document extends AbstractBlock {
17293
17380
  : ['attributes']
17294
17381
  }
17295
17382
 
17383
+ /**
17384
+ * @private
17385
+ * Restore the document attributes to a previously captured snapshot, discarding any
17386
+ * body-level (re)assignments replayed while pre-computing text. Mirrors Ruby's
17387
+ * restore_attributes-before-convert invariant.
17388
+ * @param {Object} snapshot - The attributes snapshot to restore.
17389
+ */
17390
+ _restoreAttributeSnapshot(snapshot) {
17391
+ for (const key of Reflect.ownKeys(this.attributes))
17392
+ delete this.attributes[key];
17393
+ Object.assign(this.attributes, snapshot);
17394
+ }
17395
+
17296
17396
  /**
17297
17397
  * @private
17298
17398
  * Walk the block tree and pre-compute all async text values.
17299
17399
  * Handles titles (AbstractBlock), list item text, table cell text, and reftexts.
17300
- */
17301
- async _resolveAllTexts(block) {
17400
+ *
17401
+ * Runs in two passes (see {@link parse}): with `resolveContent` false only titles and
17402
+ * reftexts are substituted (so the reftext→id map can be built); with `resolveContent`
17403
+ * true the list item / table cell / dlist text is substituted, resolving any natural
17404
+ * cross-references against the now-complete map. Title/reftext pre-computation is
17405
+ * idempotent (results are cached), so running it in both passes is a no-op the second time.
17406
+ * @param {AbstractBlock} block - The block to resolve.
17407
+ * @param {boolean} resolveContent - Whether to substitute list item / cell / dlist text.
17408
+ */
17409
+ async _resolveAllTexts(block, resolveContent) {
17410
+ // Replay this block's attribute entries (in document order, since the walk is
17411
+ // depth-first pre-order like conversion) so that body-level attribute assignments
17412
+ // and reassignments are in scope when the block's — and its descendants' and later
17413
+ // siblings' — title / list item / table cell / reftext values are substituted.
17414
+ // Mirrors AbstractBlock#convert, which calls playbackAttributes before converting.
17415
+ this.playbackAttributes(block.attributes);
17302
17416
  // The header section lives outside document.blocks; pre-compute its title here so
17303
17417
  // that doc.doctitle() returns the fully-substituted title (with replacements applied,
17304
17418
  // e.g. ' → &#8217;) rather than the header-subs-only fallback.
@@ -17318,12 +17432,12 @@ class Document extends AbstractBlock {
17318
17432
  // dlist.blocks is an array of [[term, ...], item_or_null] pairs.
17319
17433
  for (const [terms, item] of block.blocks ?? []) {
17320
17434
  for (const term of terms ?? []) {
17321
- await term.precomputeText?.();
17322
- await this._resolveAllTexts(term);
17435
+ if (resolveContent) await term.precomputeText?.();
17436
+ await this._resolveAllTexts(term, resolveContent);
17323
17437
  }
17324
17438
  if (item) {
17325
- await item.precomputeText?.();
17326
- await this._resolveAllTexts(item);
17439
+ if (resolveContent) await item.precomputeText?.();
17440
+ await this._resolveAllTexts(item, resolveContent);
17327
17441
  }
17328
17442
  }
17329
17443
  } else if (ctx === 'table') {
@@ -17333,14 +17447,14 @@ class Document extends AbstractBlock {
17333
17447
  ...(block.rows?.foot ?? []),
17334
17448
  ]) {
17335
17449
  for (const cell of row) {
17336
- await cell.precomputeText?.();
17450
+ if (resolveContent) await cell.precomputeText?.();
17337
17451
  await cell.precomputeReftext?.();
17338
17452
  }
17339
17453
  }
17340
17454
  } else {
17341
17455
  for (const child of block.blocks ?? []) {
17342
- await child.precomputeText?.();
17343
- await this._resolveAllTexts(child);
17456
+ if (resolveContent) await child.precomputeText?.();
17457
+ await this._resolveAllTexts(child, resolveContent);
17344
17458
  }
17345
17459
  }
17346
17460
  }
@@ -17632,14 +17746,19 @@ class Document extends AbstractBlock {
17632
17746
  let newBasebackend, newFiletype;
17633
17747
 
17634
17748
  if (converter && typeof converter._getBackendTraits === 'function') {
17635
- newBasebackend = converter.basebackend();
17636
- newFiletype = converter.filetype();
17637
- const htmlsyntax = converter.htmlsyntax();
17749
+ // Read the traits object directly rather than the same-named accessor
17750
+ // methods. A user converter that declares flat string properties
17751
+ // (`converter.outfilesuffix = '.html'`, convention #2) keeps them intact,
17752
+ // so callers reading `converter.outfilesuffix` still see the string.
17753
+ const traits = converter._getBackendTraits();
17754
+ newBasebackend = traits.basebackend;
17755
+ newFiletype = traits.filetype;
17756
+ const htmlsyntax = traits.htmlsyntax;
17638
17757
  if (htmlsyntax) attrs.htmlsyntax = htmlsyntax;
17639
17758
  if (init) {
17640
- attrs.outfilesuffix ??= converter.outfilesuffix();
17759
+ attrs.outfilesuffix ??= traits.outfilesuffix;
17641
17760
  } else if (!this.isAttributeLocked('outfilesuffix')) {
17642
- attrs.outfilesuffix = converter.outfilesuffix();
17761
+ attrs.outfilesuffix = traits.outfilesuffix;
17643
17762
  }
17644
17763
  } else if (converter) {
17645
17764
  const traits = deriveBackendTraits(newBackend);
@@ -18027,7 +18146,7 @@ const Substitutors = {
18027
18146
  *
18028
18147
  * @param {string|string[]} text - The text to process; must not be null.
18029
18148
  * @param {string[]} [subs=NORMAL_SUBS] - The substitutions to perform.
18030
- * @returns {string|string[]} Text with substitutions applied.
18149
+ * @returns {Promise<string|string[]>} Text with substitutions applied.
18031
18150
  */
18032
18151
  async applySubs(text, subs = NORMAL_SUBS) {
18033
18152
  const isEmpty = Array.isArray(text) ? text.length === 0 : text.length === 0;
@@ -18141,7 +18260,7 @@ const Substitutors = {
18141
18260
  * Substitute quoted text (emphasis, strong, monospaced, etc.)
18142
18261
  *
18143
18262
  * @param {string} text
18144
- * @returns {string}
18263
+ * @returns {Promise<string>}
18145
18264
  */
18146
18265
  async subQuotes(text) {
18147
18266
  const compat = this.document.compatMode;
@@ -18313,7 +18432,7 @@ const Substitutors = {
18313
18432
  * Substitute inline macros (links, images, etc.)
18314
18433
  *
18315
18434
  * @param {string} text
18316
- * @returns {string}
18435
+ * @returns {Promise<string>}
18317
18436
  */
18318
18437
  async subMacros(text) {
18319
18438
  const foundSquareBracket = text.includes('[');
@@ -19208,7 +19327,7 @@ const Substitutors = {
19208
19327
  * Substitute post replacements (hard line breaks).
19209
19328
  *
19210
19329
  * @param {string} text
19211
- * @returns {string}
19330
+ * @returns {Promise<string>}
19212
19331
  */
19213
19332
  async subPostReplacements(text) {
19214
19333
  if (
@@ -19246,7 +19365,7 @@ const Substitutors = {
19246
19365
  *
19247
19366
  * @param {string} source
19248
19367
  * @param {boolean} processCallouts
19249
- * @returns {string}
19368
+ * @returns {Promise<string>}
19250
19369
  */
19251
19370
  async subSource(source, processCallouts) {
19252
19371
  return processCallouts
@@ -19258,7 +19377,7 @@ const Substitutors = {
19258
19377
  * Substitute callout source references.
19259
19378
  *
19260
19379
  * @param {string} text
19261
- * @returns {string}
19380
+ * @returns {Promise<string>}
19262
19381
  */
19263
19382
  async subCallouts(text) {
19264
19383
  const calloutRx = this.hasAttribute('line-comment')
@@ -19287,7 +19406,7 @@ const Substitutors = {
19287
19406
  *
19288
19407
  * @param {string} source
19289
19408
  * @param {boolean} processCallouts
19290
- * @returns {string}
19409
+ * @returns {Promise<string>}
19291
19410
  */
19292
19411
  async highlightSource(source, processCallouts) {
19293
19412
  const syntaxHl = this.document.syntaxHighlighter;
@@ -19633,7 +19752,7 @@ const Substitutors = {
19633
19752
  * Restore passthrough text by reinserting into placeholder positions.
19634
19753
  *
19635
19754
  * @param {string} text
19636
- * @returns {string}
19755
+ * @returns {Promise<string>}
19637
19756
  */
19638
19757
  async restorePassthroughs(text) {
19639
19758
  if (!text.includes(PASS_START)) return text
@@ -19825,7 +19944,7 @@ const Substitutors = {
19825
19944
  * @param {string} attrlist
19826
19945
  * @param {string[]} [posattrs=[]]
19827
19946
  * @param {Object} [opts={}]
19828
- * @returns {Object}
19947
+ * @returns {Promise<Object>}
19829
19948
  */
19830
19949
  async parseAttributes(attrlist, posattrs = [], opts = {}) {
19831
19950
  if (!attrlist || attrlist.length === 0) return {}
@@ -20886,7 +21005,7 @@ class Html5Converter extends ConverterBase {
20886
21005
  let reproducible;
20887
21006
  if (!(reproducible = node.hasAttribute('reproducible'))) {
20888
21007
  result.push(
20889
- `<meta name="generator" content="Asciidoctor ${node.getAttribute('asciidoctor-version')}"${slash}>`
21008
+ `<meta name="generator" content="Asciidoctor.js ${node.getAttribute('asciidoctor-version')}"${slash}>`
20890
21009
  );
20891
21010
  }
20892
21011
  if (node.hasAttribute('app-name')) {
@@ -21603,7 +21722,9 @@ ${await node.content()}
21603
21722
  const slash = this._voidSlash;
21604
21723
  let img, src;
21605
21724
  if (
21606
- (node.hasAttribute('format', 'svg') || target.includes('.svg')) &&
21725
+ (node.hasAttribute('format', 'svg') ||
21726
+ target.includes('.svg') ||
21727
+ target.startsWith('data:image/svg+xml')) &&
21607
21728
  node.document.safe < SafeMode.SECURE
21608
21729
  ) {
21609
21730
  if (node.hasOption('inline')) {
@@ -22444,7 +22565,9 @@ Your browser does not support the video tag.
22444
22565
  if (node.hasAttribute('title'))
22445
22566
  attrs += ` title="${node.getAttribute('title')}"`;
22446
22567
  if (
22447
- (node.hasAttribute('format', 'svg') || target.includes('.svg')) &&
22568
+ (node.hasAttribute('format', 'svg') ||
22569
+ target.includes('.svg') ||
22570
+ target.startsWith('data:image/svg+xml')) &&
22448
22571
  node.document.safe < SafeMode.SECURE
22449
22572
  ) {
22450
22573
  if (node.hasOption('inline')) {
@@ -22537,12 +22660,17 @@ Your browser does not support the video tag.
22537
22660
 
22538
22661
  // NOTE expose readSvgContents for Bespoke converter
22539
22662
  async readSvgContents(node, target) {
22540
- let svg = await node.readContents(target, {
22541
- start: node.document.getAttribute('imagesdir'),
22542
- normalize: true,
22543
- label: 'SVG',
22544
- warnIfEmpty: true,
22545
- });
22663
+ // A data-URI carries the SVG in the target itself (e.g. an embedded diagram
22664
+ // produced with `:data-uri:`), so decode it directly rather than trying to
22665
+ // read it as a file or remote URI.
22666
+ let svg = target.startsWith('data:')
22667
+ ? this._decodeDataUri(target)
22668
+ : await node.readContents(target, {
22669
+ start: node.document.getAttribute('imagesdir'),
22670
+ normalize: true,
22671
+ label: 'SVG',
22672
+ warnIfEmpty: true,
22673
+ });
22546
22674
  if (!svg) return null
22547
22675
  if (!svg.startsWith('<svg')) svg = svg.replace(SvgPreambleRx, '');
22548
22676
  // Fix incomplete SVG start tag (missing closing >) by inserting > before the first child element.
@@ -22574,6 +22702,27 @@ Your browser does not support the video tag.
22574
22702
 
22575
22703
  // ── Private helpers ─────────────────────────────────────────────────────────
22576
22704
 
22705
+ /**
22706
+ * Decode an inline `data:` URI to its text contents (e.g. an SVG document) so
22707
+ * an image whose target is a data-URI can be embedded inline. Supports both
22708
+ * Base64 (`;base64,`) and percent-encoded payloads. Returns null when the
22709
+ * payload is missing.
22710
+ *
22711
+ * @internal
22712
+ * @private
22713
+ */
22714
+ _decodeDataUri(target) {
22715
+ const comma = target.indexOf(',');
22716
+ if (comma === -1) return null
22717
+ const meta = target.slice('data:'.length, comma);
22718
+ const data = target.slice(comma + 1);
22719
+ if (/;base64\b/i.test(meta)) {
22720
+ const bytes = Uint8Array.from(atob(data), (c) => c.charCodeAt(0));
22721
+ return new TextDecoder('utf-8').decode(bytes)
22722
+ }
22723
+ return decodeURIComponent(data)
22724
+ }
22725
+
22577
22726
  /**
22578
22727
  * @internal
22579
22728
  * @private
@@ -23938,7 +24087,7 @@ class ManPageConverter extends ConverterBase {
23938
24087
  `'\\" t
23939
24088
  .\\" Title: ${mantitle}
23940
24089
  .\\" Author: ${node.hasAttribute('authors') ? node.getAttribute('authors') : '[see the "AUTHOR(S)" section]'}
23941
- .\\" Generator: Asciidoctor ${node.getAttribute('asciidoctor-version')}`,
24090
+ .\\" Generator: Asciidoctor.js ${node.getAttribute('asciidoctor-version')}`,
23942
24091
  ];
23943
24092
 
23944
24093
  if (docdate) result.push(`.\\" Date: ${docdate}`);
@@ -24769,4 +24918,4 @@ const manpage = /*#__PURE__*/Object.freeze({
24769
24918
  default: ManPageConverter
24770
24919
  });
24771
24920
 
24772
- export { AbstractBlock, AbstractNode, Author, Block, BlockMacroProcessor, BlockProcessor, ContentModel, ConverterBase, CustomFactory$1 as ConverterCustomFactory, Converter as ConverterFactory, Cursor, DefaultFactory$1 as DefaultConverterFactory, DefaultFactory as DefaultSyntaxHighlighterFactory, DocinfoProcessor, Document, DocumentTitle, Extensions, Footnote, Html5Converter, HttpCache, HttpCacheManager, ImageReference, IncludeProcessor, Inline, InlineMacroProcessor, List, ListItem, Logger, LoggerManager, MemoryHttpCache, MemoryLogger, NullLogger, Postprocessor, Preprocessor, Processor, ProcessorExtension, Reader, Registry, RevisionInfo, SafeMode, Section, SyntaxHighlighter, SyntaxHighlighterBase, Timings, TreeProcessor, convert, deriveBackendTraits, getCoreVersion, getVersion, load };
24921
+ export { AbstractBlock, AbstractNode, Author, Block, BlockMacroProcessor, BlockProcessor, ContentModel, ConverterBase, CustomFactory$1 as ConverterCustomFactory, Converter as ConverterFactory, Cursor, DefaultFactory$1 as DefaultConverterFactory, DefaultFactory as DefaultSyntaxHighlighterFactory, DocinfoProcessor, Document, DocumentTitle, Extensions, Footnote, Html5Converter, HttpCache, HttpCacheManager, ImageReference, IncludeProcessor, Inline, InlineMacroProcessor, List, ListItem, LogMessage, Logger, LoggerManager, MemoryHttpCache, MemoryLogger, NullLogger, Postprocessor, Preprocessor, Processor, ProcessorExtension, Reader, Registry, RevisionInfo, SafeMode, Section, SyntaxHighlighter, SyntaxHighlighterBase, Timings, TreeProcessor, convert, deriveBackendTraits, getCoreVersion, getVersion, load };