@asciidoctor/core 4.0.0-alpha.2 → 4.0.0-alpha.4
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/build/browser/index.js +649 -131
- package/build/node/index.cjs +652 -131
- package/package.json +3 -2
- package/src/abstract_node.js +8 -13
- package/src/attribute_entry.js +17 -1
- package/src/browser/reader.js +2 -2
- package/src/browser.js +21 -0
- package/src/convert.js +10 -3
- package/src/converter/html5.js +8 -27
- package/src/converter/template.js +1 -1
- package/src/document.js +19 -9
- package/src/extensions.js +266 -8
- package/src/helpers.js +20 -2
- package/src/http_cache.js +139 -0
- package/src/index.js +16 -0
- package/src/load.js +30 -13
- package/src/logging.js +65 -5
- package/src/parser.js +1 -1
- package/src/path_resolver.js +20 -15
- package/src/reader.js +3 -2
- package/src/stylesheets.js +15 -0
- package/types/abstract_node.d.ts +1 -2
- package/types/attribute_entry.d.ts +8 -0
- package/types/browser/reader.d.ts +1 -1
- package/types/extensions.d.ts +203 -9
- package/types/http_cache.d.ts +59 -0
- package/types/index.d.cts +12 -1
- package/types/index.d.ts +12 -1
- package/types/logging.d.ts +15 -4
- package/types/path_resolver.d.ts +0 -5
- package/types/stylesheets.d.ts +1 -0
- package/types/table.d.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@asciidoctor/core",
|
|
3
|
-
"version": "4.0.0-alpha.
|
|
3
|
+
"version": "4.0.0-alpha.4",
|
|
4
4
|
"description": "Asciidoctor.js: AsciiDoc in JavaScript powered by Asciidoctor",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "build/node/index.cjs",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"build:types": "tsc && node scripts/strip-internal.js && node scripts/patch-jsdoc.js",
|
|
29
29
|
"docs": "typedoc --tsconfig tsconfig.docs.json",
|
|
30
30
|
"test": "node --test test/**.test.js",
|
|
31
|
+
"test:deno": "deno test --allow-env --no-check --allow-read --allow-sys --allow-write --allow-run --allow-net test/*.test.js",
|
|
31
32
|
"test:coverage": "node --test --experimental-test-coverage --test-coverage-include='src/**' test/**.test.js",
|
|
32
33
|
"test:browser": "vitest run --config vitest.browser.config.js",
|
|
33
34
|
"lint": "biome lint src test",
|
|
@@ -65,7 +66,7 @@
|
|
|
65
66
|
},
|
|
66
67
|
"homepage": "https://github.com/asciidoctor/asciidoctor.js",
|
|
67
68
|
"volta": {
|
|
68
|
-
"node": "24.
|
|
69
|
+
"node": "24.15.0"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
72
|
"@biomejs/biome": "^2.4.13",
|
package/src/abstract_node.js
CHANGED
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
extname,
|
|
32
32
|
prepareSourceString,
|
|
33
33
|
} from './helpers.js'
|
|
34
|
+
import { fetchUri } from './http_cache.js'
|
|
34
35
|
|
|
35
36
|
// ── Node.js fs (lazy, optional) ───────────────────────────────────────────────
|
|
36
37
|
// Loaded on first use in Node.js; silently absent in browser/WebWorker environments.
|
|
@@ -485,10 +486,7 @@ export class AbstractNode {
|
|
|
485
486
|
(targetImage = this.normalizeWebPath(targetImage, imagesBase, false)))
|
|
486
487
|
) {
|
|
487
488
|
return doc.hasAttribute('allow-uri-read')
|
|
488
|
-
? this.generateDataUriFromUri(
|
|
489
|
-
targetImage,
|
|
490
|
-
doc.hasAttribute('cache-uri')
|
|
491
|
-
)
|
|
489
|
+
? this.generateDataUriFromUri(targetImage)
|
|
492
490
|
: targetImage
|
|
493
491
|
}
|
|
494
492
|
return this.generateDataUri(targetImage, assetDirKey)
|
|
@@ -541,10 +539,7 @@ export class AbstractNode {
|
|
|
541
539
|
)
|
|
542
540
|
: this.normalizeSystemPath(targetImage)
|
|
543
541
|
if (isUriish(imagePath)) {
|
|
544
|
-
return await this.generateDataUriFromUri(
|
|
545
|
-
imagePath,
|
|
546
|
-
this.document.hasAttribute('cache-uri')
|
|
547
|
-
)
|
|
542
|
+
return await this.generateDataUriFromUri(imagePath)
|
|
548
543
|
}
|
|
549
544
|
if (await isReadable(imagePath)) {
|
|
550
545
|
const data = await _fsp.readFile(imagePath)
|
|
@@ -564,12 +559,12 @@ export class AbstractNode {
|
|
|
564
559
|
* imageUri, the caller must await the returned Promise.
|
|
565
560
|
*
|
|
566
561
|
* @param {string} imageUri - The URI from which to read the image data (http/https/ftp).
|
|
567
|
-
* @param {boolean} [cacheUri=false] - A Boolean to control caching (not yet supported in JS).
|
|
568
562
|
* @returns {Promise<string>} a Promise resolving to a String data URI.
|
|
569
563
|
*/
|
|
570
|
-
async generateDataUriFromUri(imageUri
|
|
564
|
+
async generateDataUriFromUri(imageUri) {
|
|
571
565
|
try {
|
|
572
|
-
const
|
|
566
|
+
const doc = this.document
|
|
567
|
+
const response = await fetchUri(imageUri, doc)
|
|
573
568
|
if (response.ok) {
|
|
574
569
|
const mimetype = (
|
|
575
570
|
response.headers.get('content-type') || 'application/octet-stream'
|
|
@@ -740,7 +735,7 @@ export class AbstractNode {
|
|
|
740
735
|
) {
|
|
741
736
|
if (doc.hasAttribute('allow-uri-read')) {
|
|
742
737
|
try {
|
|
743
|
-
const response = await
|
|
738
|
+
const response = await fetchUri(resolvedTarget, doc)
|
|
744
739
|
const text = await response.text()
|
|
745
740
|
contents = opts.normalize ? prepareSourceString(text).join(LF) : text
|
|
746
741
|
} catch {
|
|
@@ -765,7 +760,7 @@ export class AbstractNode {
|
|
|
765
760
|
})
|
|
766
761
|
}
|
|
767
762
|
|
|
768
|
-
if (contents && opts.warnIfEmpty && contents.length === 0) {
|
|
763
|
+
if (contents != null && opts.warnIfEmpty && contents.length === 0) {
|
|
769
764
|
this.logger.warn(`contents of ${label} is empty: ${resolvedTarget}`)
|
|
770
765
|
}
|
|
771
766
|
return contents
|
package/src/attribute_entry.js
CHANGED
|
@@ -1,3 +1,19 @@
|
|
|
1
|
+
// Symbol key used to store attribute entries on a block-attributes object without
|
|
2
|
+
// polluting the public attributes (invisible to Object.keys / for-in / JSON.stringify).
|
|
3
|
+
// Spread ({ ...attrs }) copies Symbol-keyed properties, so the entry survives the
|
|
4
|
+
// shallow clone made in AbstractNode's constructor.
|
|
5
|
+
export const ATTR_ENTRIES_KEY = Symbol('attribute_entries')
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Return the attribute entries stored for the given block attributes object,
|
|
9
|
+
* or undefined if none have been saved.
|
|
10
|
+
* @param {Object} blockAttributes
|
|
11
|
+
* @returns {AttributeEntry[]|undefined}
|
|
12
|
+
*/
|
|
13
|
+
export function getAttributeEntries(blockAttributes) {
|
|
14
|
+
return blockAttributes[ATTR_ENTRIES_KEY]
|
|
15
|
+
}
|
|
16
|
+
|
|
1
17
|
export class AttributeEntry {
|
|
2
18
|
constructor(name, value, negate = null) {
|
|
3
19
|
this.name = name
|
|
@@ -6,7 +22,7 @@ export class AttributeEntry {
|
|
|
6
22
|
}
|
|
7
23
|
|
|
8
24
|
saveTo(blockAttributes) {
|
|
9
|
-
;(blockAttributes
|
|
25
|
+
;(blockAttributes[ATTR_ENTRIES_KEY] ??= []).push(this)
|
|
10
26
|
return this
|
|
11
27
|
}
|
|
12
28
|
}
|
package/src/browser/reader.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
//
|
|
6
6
|
// This logic is specific to Asciidoctor.js and has no equivalent in the upstream Ruby asciidoctor
|
|
7
7
|
// implementation. It handles the case where the document is loaded in a browser environment
|
|
8
|
-
//
|
|
8
|
+
// where paths can be file:// or http(s):// URIs.
|
|
9
9
|
//
|
|
10
10
|
// The key behavioural differences from the standard file-system resolver:
|
|
11
11
|
// - Relative targets are resolved by string concatenation against a URI context dir,
|
|
@@ -55,7 +55,7 @@ function _linkReplacement(reader, target, attrlist) {
|
|
|
55
55
|
* 1. target starts with file:// → inc_path = relpath = target
|
|
56
56
|
* 2. target is a URI → must descend from baseDir or allow-uri-read; else → link
|
|
57
57
|
* 3. target is an absolute OS path → prepend file:// (or file:///)
|
|
58
|
-
* 4. baseDir == '.' → inc_path = relpath = target (resolved by
|
|
58
|
+
* 4. baseDir == '.' → inc_path = relpath = target (resolved by fetch)
|
|
59
59
|
* 5. baseDir starts with file:// OR baseDir is not a URI → inc_path = baseDir/target; relpath = target
|
|
60
60
|
* 6. baseDir is an absolute URL → inc_path = baseDir/target; relpath = target
|
|
61
61
|
*
|
package/src/browser.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
RevisionInfo,
|
|
11
11
|
} from './document.js'
|
|
12
12
|
import { Logger, LoggerManager, MemoryLogger, NullLogger } from './logging.js'
|
|
13
|
+
import { HttpCache, MemoryHttpCache, HttpCacheManager } from './http_cache.js'
|
|
13
14
|
import { SafeMode, ContentModel } from './constants.js'
|
|
14
15
|
import { Timings } from './timings.js'
|
|
15
16
|
import { AbstractNode } from './abstract_node.js'
|
|
@@ -28,9 +29,23 @@ import {
|
|
|
28
29
|
BlockMacroProcessor,
|
|
29
30
|
Extensions,
|
|
30
31
|
} from './extensions.js'
|
|
32
|
+
// Re-export DSL interface types for TypeScript consumers.
|
|
33
|
+
/**
|
|
34
|
+
* @typedef {import('./extensions.js').ProcessorDslInterface} ProcessorDslInterface
|
|
35
|
+
* @typedef {import('./extensions.js').DocumentProcessorDslInterface} DocumentProcessorDslInterface
|
|
36
|
+
* @typedef {import('./extensions.js').SyntaxProcessorDslInterface} SyntaxProcessorDslInterface
|
|
37
|
+
* @typedef {import('./extensions.js').IncludeProcessorDslInterface} IncludeProcessorDslInterface
|
|
38
|
+
* @typedef {import('./extensions.js').DocinfoProcessorDslInterface} DocinfoProcessorDslInterface
|
|
39
|
+
* @typedef {import('./extensions.js').BlockProcessorDslInterface} BlockProcessorDslInterface
|
|
40
|
+
* @typedef {import('./extensions.js').MacroProcessorDslInterface} MacroProcessorDslInterface
|
|
41
|
+
* @typedef {import('./extensions.js').InlineMacroProcessorDslInterface} InlineMacroProcessorDslInterface
|
|
42
|
+
*/
|
|
31
43
|
import {
|
|
32
44
|
Converter,
|
|
45
|
+
ConverterBase,
|
|
46
|
+
CustomFactory,
|
|
33
47
|
DefaultFactory as DefaultConverterFactory,
|
|
48
|
+
deriveBackendTraits,
|
|
34
49
|
} from './converter.js'
|
|
35
50
|
import { Inline } from './inline.js'
|
|
36
51
|
import { Block } from './block.js'
|
|
@@ -97,6 +112,9 @@ export {
|
|
|
97
112
|
LoggerManager,
|
|
98
113
|
MemoryLogger,
|
|
99
114
|
NullLogger,
|
|
115
|
+
HttpCache,
|
|
116
|
+
MemoryHttpCache,
|
|
117
|
+
HttpCacheManager,
|
|
100
118
|
SafeMode,
|
|
101
119
|
ContentModel,
|
|
102
120
|
Timings,
|
|
@@ -114,7 +132,10 @@ export {
|
|
|
114
132
|
Extensions,
|
|
115
133
|
Cursor,
|
|
116
134
|
Converter as ConverterFactory,
|
|
135
|
+
ConverterBase,
|
|
136
|
+
CustomFactory as ConverterCustomFactory,
|
|
117
137
|
DefaultConverterFactory,
|
|
138
|
+
deriveBackendTraits,
|
|
118
139
|
DefaultSyntaxHighlighterFactory,
|
|
119
140
|
Html5Converter,
|
|
120
141
|
SyntaxHighlighter,
|
package/src/convert.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// - Ruby File.write → async writeFile() via node:fs/promises.
|
|
12
12
|
// - Ruby Helpers.mkdir_p → mkdirP() from helpers.js.
|
|
13
13
|
// - Ruby Helpers.uriish? → isUriish() from helpers.js.
|
|
14
|
-
// - Ruby Stylesheets.instance.write_primary_stylesheet →
|
|
14
|
+
// - Ruby Stylesheets.instance.write_primary_stylesheet → Stylesheets.instance.writePrimaryStylesheet() in stylesheets.js; returns false in browser environments.
|
|
15
15
|
// - Ruby doc.syntax_highlighter → doc.syntaxHighlighter.
|
|
16
16
|
// - Ruby syntax_hl.write_stylesheet? doc → syntaxHl.writeStylesheet(doc).
|
|
17
17
|
// - Ruby syntax_hl.write_stylesheet doc, dir → syntaxHl.writeStylesheetToDisk(doc, dir).
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
import { load } from './load.js'
|
|
25
25
|
import { isUriish, mkdirP } from './helpers.js'
|
|
26
26
|
import { SafeMode, DEFAULT_STYLESHEET_KEYS } from './constants.js'
|
|
27
|
+
import { Stylesheets } from './stylesheets.js'
|
|
27
28
|
|
|
28
29
|
// ── convert ───────────────────────────────────────────────────────────────────
|
|
29
30
|
|
|
@@ -214,7 +215,7 @@ export async function convert(input, options = {}) {
|
|
|
214
215
|
let copyAsciidoctorStylesheet = false
|
|
215
216
|
let copyUserStylesheet = false
|
|
216
217
|
const stylesheet = doc.getAttribute('stylesheet')
|
|
217
|
-
if (stylesheet) {
|
|
218
|
+
if (stylesheet != null) {
|
|
218
219
|
if (DEFAULT_STYLESHEET_KEYS.has(stylesheet)) {
|
|
219
220
|
copyAsciidoctorStylesheet = true
|
|
220
221
|
} else if (!isUriish(stylesheet)) {
|
|
@@ -246,7 +247,13 @@ export async function convert(input, options = {}) {
|
|
|
246
247
|
}
|
|
247
248
|
|
|
248
249
|
if (copyAsciidoctorStylesheet) {
|
|
249
|
-
|
|
250
|
+
if (
|
|
251
|
+
!(await Stylesheets.instance.writePrimaryStylesheet(stylesoutdir))
|
|
252
|
+
) {
|
|
253
|
+
doc.logger.info(
|
|
254
|
+
'skipping default stylesheet copy: filesystem writes are not supported in this environment'
|
|
255
|
+
)
|
|
256
|
+
}
|
|
250
257
|
} else if (copyUserStylesheet) {
|
|
251
258
|
let stylesheetSrc = doc.getAttribute('copycss')
|
|
252
259
|
if (stylesheetSrc === '' || stylesheetSrc === true) {
|
package/src/converter/html5.js
CHANGED
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
INLINE_MATH_DELIMITERS,
|
|
30
30
|
} from '../constants.js'
|
|
31
31
|
import { XmlSanitizeRx } from '../rx.js'
|
|
32
|
-
import { extname
|
|
32
|
+
import { extname } from '../helpers.js'
|
|
33
33
|
import { Stylesheets } from '../stylesheets.js'
|
|
34
34
|
|
|
35
35
|
// ── Local regex constants ─────────────────────────────────────────────────────
|
|
@@ -1767,32 +1767,13 @@ Your browser does not support the video tag.
|
|
|
1767
1767
|
|
|
1768
1768
|
// NOTE expose readSvgContents for Bespoke converter
|
|
1769
1769
|
async readSvgContents(node, target) {
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
warnOnFailure: true,
|
|
1778
|
-
label: 'SVG',
|
|
1779
|
-
})
|
|
1780
|
-
resolvedPath = target
|
|
1781
|
-
} else {
|
|
1782
|
-
resolvedPath = node.normalizeSystemPath(target, imagesdir, null, {
|
|
1783
|
-
targetName: 'image',
|
|
1784
|
-
})
|
|
1785
|
-
svg = await node.readAsset(resolvedPath, {
|
|
1786
|
-
normalize: true,
|
|
1787
|
-
warnOnFailure: true,
|
|
1788
|
-
label: 'SVG',
|
|
1789
|
-
})
|
|
1790
|
-
}
|
|
1791
|
-
if (svg == null) return null // file not found/readable; warning already emitted
|
|
1792
|
-
if (!svg) {
|
|
1793
|
-
node.logger.warn(`contents of SVG is empty: ${resolvedPath}`)
|
|
1794
|
-
return null
|
|
1795
|
-
}
|
|
1770
|
+
let svg = await node.readContents(target, {
|
|
1771
|
+
start: node.document.getAttribute('imagesdir'),
|
|
1772
|
+
normalize: true,
|
|
1773
|
+
label: 'SVG',
|
|
1774
|
+
warnIfEmpty: true,
|
|
1775
|
+
})
|
|
1776
|
+
if (!svg) return null
|
|
1796
1777
|
if (!svg.startsWith('<svg')) svg = svg.replace(SvgPreambleRx, '')
|
|
1797
1778
|
// Fix incomplete SVG start tag (missing closing >) by inserting > before the first child element.
|
|
1798
1779
|
// This handles cases like: <svg width="500"\n<circle .../> where the > is missing.
|
package/src/document.js
CHANGED
|
@@ -68,7 +68,11 @@ export class ImageReference {
|
|
|
68
68
|
import { Footnote } from './footnote.js'
|
|
69
69
|
export { Footnote }
|
|
70
70
|
|
|
71
|
-
import {
|
|
71
|
+
import {
|
|
72
|
+
AttributeEntry,
|
|
73
|
+
getAttributeEntries,
|
|
74
|
+
ATTR_ENTRIES_KEY,
|
|
75
|
+
} from './attribute_entry.js'
|
|
72
76
|
export { AttributeEntry }
|
|
73
77
|
|
|
74
78
|
/**
|
|
@@ -927,8 +931,9 @@ export class Document extends AbstractBlock {
|
|
|
927
931
|
* @param {Object} blockAttributes
|
|
928
932
|
*/
|
|
929
933
|
playbackAttributes(blockAttributes) {
|
|
930
|
-
|
|
931
|
-
|
|
934
|
+
const entries = getAttributeEntries(blockAttributes)
|
|
935
|
+
if (!entries) return
|
|
936
|
+
for (const entry of entries) {
|
|
932
937
|
if (entry.negate) {
|
|
933
938
|
delete this.attributes[entry.name]
|
|
934
939
|
if (entry.name === 'compat-mode') this.compatMode = false
|
|
@@ -1657,6 +1662,12 @@ export class Document extends AbstractBlock {
|
|
|
1657
1662
|
* Handles titles (AbstractBlock), list item text, table cell text, and reftexts.
|
|
1658
1663
|
*/
|
|
1659
1664
|
async _resolveAllTexts(block) {
|
|
1665
|
+
// The header section lives outside document.blocks; pre-compute its title here so
|
|
1666
|
+
// that doc.doctitle() returns the fully-substituted title (with replacements applied,
|
|
1667
|
+
// e.g. ' → ’) rather than the header-subs-only fallback.
|
|
1668
|
+
if (block === this && this.header) {
|
|
1669
|
+
await this.header.precomputeTitle?.()
|
|
1670
|
+
}
|
|
1660
1671
|
// Skip title pre-computation for blocks with an explicit empty id ([id=]).
|
|
1661
1672
|
// In Ruby, apply_title_subs is lazy: it is never called during parsing for such
|
|
1662
1673
|
// blocks because section.title is never accessed. An explicit empty id is
|
|
@@ -1740,7 +1751,7 @@ export class Document extends AbstractBlock {
|
|
|
1740
1751
|
* @internal
|
|
1741
1752
|
*/
|
|
1742
1753
|
_clearPlaybackAttributes(attributes) {
|
|
1743
|
-
delete attributes
|
|
1754
|
+
delete attributes[ATTR_ENTRIES_KEY]
|
|
1744
1755
|
}
|
|
1745
1756
|
|
|
1746
1757
|
/**
|
|
@@ -2080,11 +2091,10 @@ export class Document extends AbstractBlock {
|
|
|
2080
2091
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
2081
2092
|
|
|
2082
2093
|
function _expandPath(p) {
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
}
|
|
2094
|
+
const resolver = new PathResolver()
|
|
2095
|
+
const posixed = p.replace(/\\/g, '/')
|
|
2096
|
+
if (resolver.absolutePath(posixed)) return resolver.expandPath(posixed)
|
|
2097
|
+
return resolver.expandPath(`${resolver.workingDir}/${posixed}`)
|
|
2088
2098
|
}
|
|
2089
2099
|
|
|
2090
2100
|
function _cwd() {
|