@antora/assembler 1.0.0-alpha.7 → 1.0.0-alpha.8
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/lib/assemble-content.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const loadConfig = require('./load-config')
|
|
4
4
|
const produceAggregateDocuments = require('./produce-aggregate-documents')
|
|
5
5
|
const PromiseQueue = require('./util/promise-queue')
|
|
6
|
+
const runCommand = require('./util/run-command')
|
|
6
7
|
|
|
7
8
|
async function assembleContent (playbook, contentCatalog, converter, { siteCatalog, configSource }) {
|
|
8
9
|
// Q: could we get ContentCatalog#getComponentVersionStartPage() in Antora core?
|
|
@@ -23,7 +24,7 @@ async function assembleContent (playbook, contentCatalog, converter, { siteCatal
|
|
|
23
24
|
// TODO: pass more information to converter so it doesn't have to compute internal stuff
|
|
24
25
|
// Q: don't we need to pass in the combined/resolved AsciiDoc attributes per file or component version?
|
|
25
26
|
return new PromiseQueue({ concurrency: buildConfig.processLimit })
|
|
26
|
-
.add(aggregateDocuments.map((doc) => () => converter.call(this, doc, buildConfig)))
|
|
27
|
+
.add(aggregateDocuments.map((doc) => () => converter.call(this, doc, buildConfig, runCommand)))
|
|
27
28
|
.toPromise()
|
|
28
29
|
.then((files) => {
|
|
29
30
|
if (buildConfig.publish && siteCatalog) siteCatalog.addFiles(files)
|
|
@@ -21,11 +21,11 @@ function compilePatterns (patterns) {
|
|
|
21
21
|
if (patterns[0].charAt() === '!') patterns = ['**', ...patterns]
|
|
22
22
|
return patterns.map((pattern) => {
|
|
23
23
|
const negated = pattern.charAt() === '!'
|
|
24
|
-
if (negated) pattern = pattern.
|
|
24
|
+
if (negated) pattern = pattern.slice(1)
|
|
25
25
|
let version
|
|
26
26
|
const separatorIdx = pattern.search(VERSION_SEPARATOR_RX)
|
|
27
27
|
if (~separatorIdx) {
|
|
28
|
-
pattern = (version = true) && `${pattern.
|
|
28
|
+
pattern = (version = true) && `${pattern.slice(0, separatorIdx)}%${pattern.slice(separatorIdx + 1) || '*'}`
|
|
29
29
|
}
|
|
30
30
|
return Object.assign(makePicomatchRx(pattern, PICOMATCH_OPTS), {
|
|
31
31
|
globstar: pattern === '**',
|
package/lib/load-config.js
CHANGED
|
@@ -29,6 +29,7 @@ function loadConfig (playbook, configSource = './antora-assembler.yml') {
|
|
|
29
29
|
if (!('doctype' in asciidocAttrs)) asciidocAttrs.doctype = 'book'
|
|
30
30
|
if (!('revdate' in asciidocAttrs)) asciidocAttrs.revdate = getLocalDate()
|
|
31
31
|
asciidocAttrs['page-partial'] = null
|
|
32
|
+
asciidocAttrs['loader-assembler'] = ''
|
|
32
33
|
if (config.componentVersions == null) {
|
|
33
34
|
config.componentVersions = ['*']
|
|
34
35
|
} else if (typeof config.componentVersions === 'string') {
|
|
@@ -46,7 +47,7 @@ function loadConfig (playbook, configSource = './antora-assembler.yml') {
|
|
|
46
47
|
} else {
|
|
47
48
|
build.dir = expandPath(build.dir || './build/assembler', { dot: playbook.dir })
|
|
48
49
|
}
|
|
49
|
-
build.cwd = playbook.dir // use playbook.dir the purpose of finding and loading require scripts
|
|
50
|
+
build.cwd = playbook.dir // use playbook.dir for the purpose of finding and loading require scripts
|
|
50
51
|
if (!('clean' in build) && 'output' in playbook) build.clean = playbook.output.clean
|
|
51
52
|
if (!('publish' in build)) build.publish = true
|
|
52
53
|
if (!build.processLimit) {
|
|
@@ -62,7 +63,7 @@ function camelCaseKeys (o, stopPaths = [], p) {
|
|
|
62
63
|
const pathPrefix = p ? p + '.' : ''
|
|
63
64
|
const accum = {}
|
|
64
65
|
for (const [k, v] of Object.entries(o)) {
|
|
65
|
-
const camelKey = k.charAt() + k.
|
|
66
|
+
const camelKey = k.charAt() + k.slice(1).replace(/_([a-z])/g, (_, l) => l.toUpperCase())
|
|
66
67
|
accum[camelKey] = ~stopPaths.indexOf(pathPrefix + camelKey) ? v : camelCaseKeys(v, stopPaths, pathPrefix + camelKey)
|
|
67
68
|
}
|
|
68
69
|
return accum
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const File = require('vinyl')
|
|
4
4
|
const path = require('node:path/posix')
|
|
5
|
+
const sanitize = require('./util/sanitize')
|
|
5
6
|
|
|
6
7
|
function produceAggregateDocument (
|
|
7
8
|
loadAsciiDoc,
|
|
@@ -14,12 +15,19 @@ function produceAggregateDocument (
|
|
|
14
15
|
mutableAttributes,
|
|
15
16
|
sectionMergeStrategy = 'discrete'
|
|
16
17
|
) {
|
|
17
|
-
const pagesInOutline = selectPagesInOutline(
|
|
18
|
-
outline,
|
|
19
|
-
pages.filter((it) => it.out)
|
|
20
|
-
)
|
|
18
|
+
const pagesInOutline = selectPagesInOutline(outline, pages)
|
|
21
19
|
const navtitle = outline.content
|
|
22
|
-
const
|
|
20
|
+
const templateFile = contentCatalog.addFile({
|
|
21
|
+
src: {
|
|
22
|
+
component: componentVersion.name,
|
|
23
|
+
version: componentVersion.version,
|
|
24
|
+
module: 'ROOT',
|
|
25
|
+
family: 'page',
|
|
26
|
+
relative: generateSlug(navtitle),
|
|
27
|
+
},
|
|
28
|
+
})
|
|
29
|
+
const path_ = `${templateFile.out.path}.adoc`
|
|
30
|
+
contentCatalog.removeFile(templateFile)
|
|
23
31
|
const header = buildAsciiDocHeader(componentVersion, navtitle, doctype)
|
|
24
32
|
const body = aggregateAsciiDoc(
|
|
25
33
|
loadAsciiDoc,
|
|
@@ -32,24 +40,26 @@ function produceAggregateDocument (
|
|
|
32
40
|
mutableAttributes,
|
|
33
41
|
sectionMergeStrategy
|
|
34
42
|
)
|
|
35
|
-
const relativeSrcPath = `${stem}.adoc`
|
|
36
43
|
return new File({
|
|
44
|
+
aggregate: true,
|
|
37
45
|
asciidoc: asciidocConfig,
|
|
38
46
|
contents: Buffer.from([...header, ...body].join('\n') + '\n'),
|
|
39
47
|
mediaType: 'text/asciidoc',
|
|
40
|
-
path:
|
|
48
|
+
path: path_,
|
|
41
49
|
src: {
|
|
42
50
|
component: componentVersion.name,
|
|
43
51
|
version: componentVersion.version,
|
|
44
|
-
basename: path.basename(
|
|
45
|
-
stem,
|
|
52
|
+
basename: path.basename(path_),
|
|
53
|
+
stem: path.basename(path_, '.adoc'),
|
|
46
54
|
extname: '.adoc',
|
|
47
55
|
},
|
|
48
56
|
})
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
function buildAsciiDocHeader (componentVersion, navtitle, doctype = 'book') {
|
|
52
|
-
const
|
|
60
|
+
const [navtitlePlain, navtitleAsciiDoc] = sanitize(navtitle)
|
|
61
|
+
let doctitle = navtitleAsciiDoc
|
|
62
|
+
if (navtitlePlain !== componentVersion.title) doctitle = `${componentVersion.title}: ${doctitle}`
|
|
53
63
|
const version = componentVersion.version === 'master' ? '' : componentVersion.version
|
|
54
64
|
return [
|
|
55
65
|
`= ${doctitle}`,
|
|
@@ -88,16 +98,24 @@ function aggregateAsciiDoc (
|
|
|
88
98
|
asciidocConfig,
|
|
89
99
|
mutableAttributes,
|
|
90
100
|
sectionMergeStrategy,
|
|
101
|
+
lastComponentVersion = componentVersion,
|
|
91
102
|
level = 0
|
|
92
103
|
) {
|
|
93
104
|
const buffer = []
|
|
94
105
|
// TODO: we could try to be smart about it and make sure the page with fragment is included at least once
|
|
95
106
|
if (outlineEntry.hash) return buffer
|
|
96
107
|
const { content: navtitle, items, unresolved, urlType, url } = outlineEntry
|
|
108
|
+
const [navtitlePlain, navtitleAsciiDoc, navtitlePass] = sanitize(navtitle)
|
|
109
|
+
const siteUrl = ((val) => {
|
|
110
|
+
if (!val || val === '/') return ''
|
|
111
|
+
return val.charAt(val.length - 1) === '/' ? val.slice(0, val.length - 1) : val
|
|
112
|
+
})(asciidocConfig.attributes['site-url'])
|
|
97
113
|
// FIXME: ideally, resource ID would be stored in navigation so we can look up the page more efficiently
|
|
98
114
|
let page = urlType === 'internal' && !unresolved ? pagesInOutline.get(url) : undefined
|
|
115
|
+
if (page && pagesInOutline.aggregated?.includes(page)) page = undefined
|
|
99
116
|
if (page) {
|
|
100
117
|
let contents = page.src.contents
|
|
118
|
+
if (contents == null) return buffer
|
|
101
119
|
// NOTE: blank lines at top and bottom of document create mismatch when using line numbers to navigate source lines
|
|
102
120
|
// IMPORTANT: this must not leave behind lines the parser will drop!
|
|
103
121
|
// IDEA: another option is to capture initial lineno of reader and use as offset (but preseves those blank lines)
|
|
@@ -107,16 +125,36 @@ function aggregateAsciiDoc (
|
|
|
107
125
|
.replace(/^(?:[ \t]*\r\n?|[ \t]*\n)+/, '')
|
|
108
126
|
.trimRight()
|
|
109
127
|
)
|
|
128
|
+
;(pagesInOutline.aggregated ??= []).push(page)
|
|
110
129
|
page = new page.constructor(Object.assign({}, page, { contents, mediaType: 'text/asciidoc' }))
|
|
111
|
-
const { module: module_, relative, origin } = page.src
|
|
130
|
+
const { component, version, module: module_, relative, origin } = page.src
|
|
112
131
|
const doc = loadAsciiDoc(page, contentCatalog, asciidocConfig)
|
|
113
132
|
const refs = doc.getCatalog().refs
|
|
114
133
|
// NOTE: in Antora, docname is relative src path from module without file extension
|
|
115
134
|
const docname = doc.getAttribute('docname')
|
|
116
135
|
const docnameForId = docname.replace(/[/]/g, '::').replace(/[.]/g, '-')
|
|
117
|
-
const
|
|
136
|
+
const scopeId = component !== componentVersion.name
|
|
137
|
+
const idprefix =
|
|
138
|
+
(scopeId ? component + ':' : '') +
|
|
139
|
+
(module_ === 'ROOT' ? (scopeId ? ':' : '') : module_ + ':') +
|
|
140
|
+
docnameForId +
|
|
141
|
+
':::'
|
|
118
142
|
buffer.push('')
|
|
119
143
|
buffer.push(`:docname: ${docname}`)
|
|
144
|
+
if (component !== lastComponentVersion.name) {
|
|
145
|
+
const thisComponentVersion =
|
|
146
|
+
component === componentVersion.name && version === componentVersion.version
|
|
147
|
+
? componentVersion
|
|
148
|
+
: contentCatalog.getComponentVersion(component, version)
|
|
149
|
+
if (thisComponentVersion) {
|
|
150
|
+
buffer.push(`:page-component-name: ${thisComponentVersion.name}`)
|
|
151
|
+
buffer.push(`:page-component-version:${thisComponentVersion.version ? ' ' + thisComponentVersion.version : ''}`)
|
|
152
|
+
buffer.push(':page-version: {page-component-version}')
|
|
153
|
+
buffer.push(`:page-component-display-version: ${thisComponentVersion.displayVersion}`)
|
|
154
|
+
buffer.push(`:page-component-title: ${thisComponentVersion.title}`)
|
|
155
|
+
lastComponentVersion = thisComponentVersion
|
|
156
|
+
}
|
|
157
|
+
}
|
|
120
158
|
buffer.push(`:page-module: ${module_}`)
|
|
121
159
|
buffer.push(`:page-relative-src-path: ${relative}`)
|
|
122
160
|
//buffer.push(`:page-origin-type: ${origin.type}`)
|
|
@@ -128,7 +166,7 @@ function aggregateAsciiDoc (
|
|
|
128
166
|
let enclosed
|
|
129
167
|
// NOTE: if level is 0, doctitle has already been added and we're in the document header
|
|
130
168
|
if (level) {
|
|
131
|
-
if (level === 1 &&
|
|
169
|
+
if (level === 1 && navtitlePlain === componentVersion.title) {
|
|
132
170
|
level--
|
|
133
171
|
} else {
|
|
134
172
|
let hlevel = level + 1
|
|
@@ -138,7 +176,7 @@ function aggregateAsciiDoc (
|
|
|
138
176
|
} else {
|
|
139
177
|
buffer.push(`[#${idprefix}]`)
|
|
140
178
|
}
|
|
141
|
-
buffer.push(`${'='.repeat(hlevel)} ${
|
|
179
|
+
buffer.push(`${'='.repeat(hlevel)} ${navtitleAsciiDoc}`)
|
|
142
180
|
}
|
|
143
181
|
} else {
|
|
144
182
|
header.unshift(`[#${idprefix}]`)
|
|
@@ -170,10 +208,6 @@ function aggregateAsciiDoc (
|
|
|
170
208
|
buffer.push(`${'='.repeat(hlevel)} ${overviewTitle}`)
|
|
171
209
|
if (toggleSectids) buffer.push(':sectids:')
|
|
172
210
|
}
|
|
173
|
-
const siteUrl = ((val) => {
|
|
174
|
-
if (!val || val === '/') return ''
|
|
175
|
-
return val.charAt(val.length - 1) === '/' ? val.substr(0, val.length - 1) : val
|
|
176
|
-
})(doc.getAttribute('site-url'))
|
|
177
211
|
const lines = doc.getSourceLines()
|
|
178
212
|
const ignoreLines = []
|
|
179
213
|
// TODO: think more about when multipart is allowed; perhaps configurable
|
|
@@ -250,8 +284,8 @@ function aggregateAsciiDoc (
|
|
|
250
284
|
let pagePart, fragment, targetPage
|
|
251
285
|
const hashIdx = target.indexOf('#')
|
|
252
286
|
if (~hashIdx) {
|
|
253
|
-
pagePart = target.
|
|
254
|
-
fragment = target.
|
|
287
|
+
pagePart = target.slice(0, hashIdx)
|
|
288
|
+
fragment = target.slice(hashIdx + 1)
|
|
255
289
|
// TODO: for now, assume .adoc; in the future, consider other file extensions
|
|
256
290
|
if (pagePart && !pagePart.endsWith('.adoc')) pagePart += '.adoc'
|
|
257
291
|
} else if (target.endsWith('.adoc')) {
|
|
@@ -267,20 +301,24 @@ function aggregateAsciiDoc (
|
|
|
267
301
|
: `<<${idprefix}${fragment}${text ? ',' + text.replace(/\\]/g, ']') : ''}>>`
|
|
268
302
|
}
|
|
269
303
|
if (~pagePart.indexOf('@') || /:.*:/.test(pagePart)) {
|
|
304
|
+
if (siteUrl && (targetPage = contentCatalog.resolvePage(pagePart, page.src)) && targetPage.out) {
|
|
305
|
+
text ||= targetPage.asciidoc?.xreftext || target
|
|
306
|
+
return `${siteUrl}${targetPage.pub.url}${fragment && '#' + fragment}[${text}]`
|
|
307
|
+
}
|
|
270
308
|
// TODO: handle unresolved page better
|
|
271
|
-
return
|
|
272
|
-
? `${siteUrl}${targetPage.pub.url}${fragment && '#' + fragment}[${text}]`
|
|
273
|
-
: m
|
|
309
|
+
return m
|
|
274
310
|
} else if (pagePart.indexOf(':') < 0) {
|
|
275
311
|
if (module_ !== 'ROOT') pagePart = `${module_}:${pagePart}`
|
|
276
312
|
} else if (pagePart.startsWith('ROOT:')) {
|
|
277
|
-
pagePart = pagePart.
|
|
313
|
+
pagePart = pagePart.slice(5)
|
|
278
314
|
}
|
|
279
315
|
if (!(targetPage = pagesInOutline.get(pagePart))) {
|
|
316
|
+
if (siteUrl && (targetPage = contentCatalog.resolvePage(pagePart, page.src)) && targetPage.out) {
|
|
317
|
+
text ||= targetPage.asciidoc?.xreftext || target
|
|
318
|
+
return `${siteUrl}${targetPage.pub.url}${fragment && '#' + fragment}[${text}]`
|
|
319
|
+
}
|
|
280
320
|
// TODO: handle unresolved page better
|
|
281
|
-
return
|
|
282
|
-
? `${siteUrl}${targetPage.pub.url}${fragment && '#' + fragment}[${text}]`
|
|
283
|
-
: m
|
|
321
|
+
return m
|
|
284
322
|
}
|
|
285
323
|
pagePart = pagePart
|
|
286
324
|
.replace(/[/]/g, '::')
|
|
@@ -301,6 +339,7 @@ function aggregateAsciiDoc (
|
|
|
301
339
|
family: 'attachment',
|
|
302
340
|
relative,
|
|
303
341
|
})
|
|
342
|
+
// TODO: handle unresolved attachment page
|
|
304
343
|
return attachment && attachment.out
|
|
305
344
|
? `${siteUrl}${attachment.pub.url.replace(/_/g, '{underscore}')}[${text}]`
|
|
306
345
|
: m
|
|
@@ -353,14 +392,14 @@ function aggregateAsciiDoc (
|
|
|
353
392
|
let prefix = ''
|
|
354
393
|
// Q: can we use startsWith('image::') in certain cases?
|
|
355
394
|
let imageMacroOffset = (
|
|
356
|
-
lastImageMacroAt?.[0] === idx ? line.
|
|
395
|
+
lastImageMacroAt?.[0] === idx ? line.slice(0, lastImageMacroAt[1]) : line
|
|
357
396
|
).lastIndexOf('image::')
|
|
358
397
|
if (imageMacroOffset > 0) {
|
|
359
398
|
if (
|
|
360
399
|
block.getDocument().isNested() &&
|
|
361
|
-
(prefix = line.
|
|
400
|
+
(prefix = line.slice(0, imageMacroOffset)).trimRight().endsWith('|')
|
|
362
401
|
) {
|
|
363
|
-
line = line.
|
|
402
|
+
line = line.slice(prefix.length)
|
|
364
403
|
} else {
|
|
365
404
|
imageMacroOffset = -1
|
|
366
405
|
}
|
|
@@ -371,7 +410,7 @@ function aggregateAsciiDoc (
|
|
|
371
410
|
const image = contentCatalog.resolveResource(target, page.src, 'image', ['image'])
|
|
372
411
|
// FIXME: handle (or report) case when image is not resolved
|
|
373
412
|
if (image && image.out) {
|
|
374
|
-
const boxedAttrlist = line.
|
|
413
|
+
const boxedAttrlist = line.slice(line.indexOf('['))
|
|
375
414
|
image.out.assembled = true
|
|
376
415
|
lines[idx] = `${prefix}image::${image.out.path}${boxedAttrlist}`
|
|
377
416
|
}
|
|
@@ -398,7 +437,15 @@ function aggregateAsciiDoc (
|
|
|
398
437
|
} else if (val !== initialVal) {
|
|
399
438
|
accum.push(`:${name}:${initialVal ? ' ' + initialVal : ''}`)
|
|
400
439
|
}
|
|
401
|
-
} else if (
|
|
440
|
+
} else if (
|
|
441
|
+
!(
|
|
442
|
+
val == null ||
|
|
443
|
+
doc.isAttributeLocked(name) ||
|
|
444
|
+
name === 'doctype' ||
|
|
445
|
+
name === 'leveloffset' ||
|
|
446
|
+
name === 'underscore'
|
|
447
|
+
)
|
|
448
|
+
) {
|
|
402
449
|
accum.push(`:!${name}:`)
|
|
403
450
|
}
|
|
404
451
|
return accum
|
|
@@ -407,43 +454,54 @@ function aggregateAsciiDoc (
|
|
|
407
454
|
)
|
|
408
455
|
if (resolvedAttributeEntries.length > 1) buffer.push(...resolvedAttributeEntries)
|
|
409
456
|
}
|
|
410
|
-
} else {
|
|
411
|
-
if (level) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
457
|
+
} else if (level) {
|
|
458
|
+
if (level === 1 && navtitlePlain === componentVersion.title) {
|
|
459
|
+
level--
|
|
460
|
+
} else {
|
|
461
|
+
buffer.push('')
|
|
462
|
+
// NOTE: try to toggle sectids; otherwise, fallback to globally unique synthetic ID
|
|
463
|
+
// Q: should we unset docname, page-module, etc?
|
|
464
|
+
let toggleSectids, syntheticId
|
|
465
|
+
if (!('sectids' in asciidocConfig.attributes)) {
|
|
466
|
+
buffer.push(':!sectids:')
|
|
467
|
+
toggleSectids = true
|
|
468
|
+
} else if (typeof asciidocConfig.attributes.sectids === 'string') {
|
|
469
|
+
if ('sectids' in mutableAttributes) {
|
|
419
470
|
buffer.push(':!sectids:')
|
|
420
471
|
toggleSectids = true
|
|
421
|
-
} else
|
|
422
|
-
|
|
423
|
-
buffer.push(':!sectids:')
|
|
424
|
-
toggleSectids = true
|
|
425
|
-
} else {
|
|
426
|
-
syntheticId = `__object-id-${global.Opal.hash(outlineEntry).$object_id()}`
|
|
427
|
-
}
|
|
472
|
+
} else {
|
|
473
|
+
syntheticId = `__object-id-${global.Opal.hash(outlineEntry).$object_id()}`
|
|
428
474
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
475
|
+
}
|
|
476
|
+
let sectionTitle = navtitleAsciiDoc
|
|
477
|
+
if (urlType === 'external') {
|
|
478
|
+
sectionTitle = `${url}[${navtitlePass ? navtitleAsciiDoc : navtitleAsciiDoc.replace(/\]/g, '\\]')}]`
|
|
479
|
+
} else if (urlType === 'internal' && !unresolved && siteUrl) {
|
|
480
|
+
const resource = contentCatalog.getFiles().find((it) => it.pub.url === url)
|
|
481
|
+
if (resource?.out) {
|
|
482
|
+
sectionTitle = navtitlePass ? navtitleAsciiDoc : navtitleAsciiDoc.replace(/\]/g, '\\]')
|
|
483
|
+
sectionTitle = `${siteUrl}${resource.pub.url}[${sectionTitle}]`
|
|
437
484
|
}
|
|
438
|
-
buffer.push(`${'='.repeat(hlevel)} ${sectionTitle}`)
|
|
439
|
-
if (toggleSectids) buffer.push(':sectids:')
|
|
440
485
|
}
|
|
486
|
+
let hlevel = level + 1
|
|
487
|
+
if (hlevel > 6) {
|
|
488
|
+
hlevel = 6
|
|
489
|
+
buffer.push(syntheticId ? `[discrete#${syntheticId}]` : '[discrete]')
|
|
490
|
+
} else if (syntheticId) {
|
|
491
|
+
buffer.push(`[#${syntheticId}]`)
|
|
492
|
+
}
|
|
493
|
+
buffer.push(`${'='.repeat(hlevel)} ${sectionTitle}`)
|
|
494
|
+
if (toggleSectids) buffer.push(':sectids:')
|
|
441
495
|
}
|
|
442
496
|
}
|
|
443
497
|
|
|
444
498
|
const nextLevel = level + 1
|
|
445
499
|
if (items) {
|
|
446
|
-
|
|
500
|
+
// NOTE: drop first child if same as parent; should we keep if content is different?
|
|
501
|
+
;(urlType === 'internal' && urlType === items[0].urlType && url === items[0].url && !items[0].items
|
|
502
|
+
? items.slice(1)
|
|
503
|
+
: items
|
|
504
|
+
).forEach((item) => {
|
|
447
505
|
buffer.push(
|
|
448
506
|
...aggregateAsciiDoc(
|
|
449
507
|
loadAsciiDoc,
|
|
@@ -455,6 +513,7 @@ function aggregateAsciiDoc (
|
|
|
455
513
|
asciidocConfig,
|
|
456
514
|
mutableAttributes,
|
|
457
515
|
sectionMergeStrategy,
|
|
516
|
+
lastComponentVersion,
|
|
458
517
|
nextLevel
|
|
459
518
|
)
|
|
460
519
|
)
|
|
@@ -463,19 +522,12 @@ function aggregateAsciiDoc (
|
|
|
463
522
|
return buffer
|
|
464
523
|
}
|
|
465
524
|
|
|
466
|
-
function
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
title
|
|
473
|
-
.toLowerCase()
|
|
474
|
-
.replace(/&.+?;|[^ \p{Alpha}0-9_\-.]/gu, '')
|
|
475
|
-
.replace(/[ _.]/g, '-')
|
|
476
|
-
.replace(/--+/g, '-')
|
|
477
|
-
)
|
|
478
|
-
return path.join(...segments)
|
|
525
|
+
function generateSlug (title) {
|
|
526
|
+
return title
|
|
527
|
+
.toLowerCase()
|
|
528
|
+
.replace(/&.+?;|[^ \p{Alpha}0-9_\-.]/gu, '')
|
|
529
|
+
.replace(/[ _.]/g, '-')
|
|
530
|
+
.replace(/--+/g, '-')
|
|
479
531
|
}
|
|
480
532
|
|
|
481
533
|
function fixSectionLevels (sections, multipart) {
|
|
@@ -512,10 +564,10 @@ function rewriteStyleAttribute (block, lines, idx, idprefix, replacementStyle =
|
|
|
512
564
|
let rawStyle
|
|
513
565
|
const commaIdx = prevLine.indexOf(',')
|
|
514
566
|
if (~commaIdx) {
|
|
515
|
-
rawStyle = prevLine.
|
|
567
|
+
rawStyle = prevLine.slice(1, commaIdx - 1)
|
|
516
568
|
if (~rawStyle.indexOf('=')) rawStyle = undefined
|
|
517
569
|
} else if (!~prevLine.indexOf('=')) {
|
|
518
|
-
rawStyle = prevLine.
|
|
570
|
+
rawStyle = prevLine.slice(1, prevLine.length - 2)
|
|
519
571
|
}
|
|
520
572
|
if (rawStyle) {
|
|
521
573
|
if (~rawStyle.indexOf('#')) {
|
|
@@ -524,10 +576,10 @@ function rewriteStyleAttribute (block, lines, idx, idprefix, replacementStyle =
|
|
|
524
576
|
} else {
|
|
525
577
|
prevLine = `[${
|
|
526
578
|
replacementStyle ? rawStyle.replace(/^[^.%]*/, replacementStyle) : rawStyle
|
|
527
|
-
}#${idprefix}${block.getId()}${prevLine.
|
|
579
|
+
}#${idprefix}${block.getId()}${prevLine.slice(rawStyle.length + 1)}`
|
|
528
580
|
}
|
|
529
581
|
} else {
|
|
530
|
-
prevLine = `[${replacementStyle}#${idprefix}${block.getId()}${rawStyle == null ? ',' : ''}${prevLine.
|
|
582
|
+
prevLine = `[${replacementStyle}#${idprefix}${block.getId()}${rawStyle == null ? ',' : ''}${prevLine.slice(1)}`
|
|
531
583
|
}
|
|
532
584
|
if (cellSpec) prevLine = `${cellSpec}|${prevLine}`
|
|
533
585
|
lines[idx - 1] = prevLine
|
|
@@ -5,6 +5,8 @@ const filterComponentVersions = require('./filter-component-versions')
|
|
|
5
5
|
const produceAggregateDocument = require('./produce-aggregate-document')
|
|
6
6
|
const selectMutableAttributes = require('./select-mutable-attributes')
|
|
7
7
|
|
|
8
|
+
const IMAGE_MACRO_RX = /^image::?(.+?)\[(.*?)\]$/
|
|
9
|
+
|
|
8
10
|
function produceAggregateDocuments (loadAsciiDoc, contentCatalog, assemblerConfig) {
|
|
9
11
|
const { insertStartPage, rootLevel, sectionMergeStrategy, asciidoc: assemblerAsciiDocConfig } = assemblerConfig
|
|
10
12
|
const assemblerAsciiDocAttributes = Object.assign({}, assemblerAsciiDocConfig.attributes)
|
|
@@ -20,26 +22,32 @@ function produceAggregateDocuments (loadAsciiDoc, contentCatalog, assemblerConfi
|
|
|
20
22
|
const mergedAsciiDocConfig = Object.assign({}, componentVersionAsciiDocConfig, {
|
|
21
23
|
attributes: Object.assign({ revdate }, componentVersionAsciiDocConfig.attributes, assemblerAsciiDocAttributes),
|
|
22
24
|
})
|
|
25
|
+
const mergedAsciiDocAttributes = mergedAsciiDocConfig.attributes
|
|
26
|
+
Object.entries(mergedAsciiDocAttributes).forEach(([name, val]) => {
|
|
27
|
+
const match = name.endsWith('-image') && val.startsWith('image:') && IMAGE_MACRO_RX.exec(val)
|
|
28
|
+
if (!(match && isResourceRef(match[1]))) return
|
|
29
|
+
// Q should we allow image to be resolved relative to component version?
|
|
30
|
+
const image = contentCatalog.resolveResource(match[1], undefined, 'image', ['image'])
|
|
31
|
+
if (!image?.out) return
|
|
32
|
+
mergedAsciiDocAttributes[name] = `image:${image.out.path}[${match[2]}]`
|
|
33
|
+
image.out.assembled = true
|
|
34
|
+
})
|
|
23
35
|
const rootEntry = { content: title }
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
startPageUrl = undefined
|
|
29
|
-
} else {
|
|
30
|
-
Object.assign(rootEntry, { url: startPageUrl, urlType: 'internal' })
|
|
36
|
+
let startPage = contentCatalog.getComponentVersionStartPage(componentName, version)
|
|
37
|
+
if (startPage && startPage.src.component === componentName && startPage.src.version === version) {
|
|
38
|
+
if (insertStartPage && !includedInNav(navigation, startPage.pub.url)) {
|
|
39
|
+
Object.assign(rootEntry, { url: startPage.pub.url, urlType: 'internal' })
|
|
31
40
|
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
startPage ??
|
|
36
|
-
createFile({
|
|
41
|
+
} else {
|
|
42
|
+
// Q: should we always use a reference page as startPage for computing mutableAttributes?
|
|
43
|
+
startPage = createFile({
|
|
37
44
|
component: componentVersion.name,
|
|
38
45
|
version: componentVersion.version,
|
|
39
46
|
relative: '.reference-page.adoc',
|
|
40
47
|
origin: (componentVersion.origins || [])[0],
|
|
41
48
|
})
|
|
42
|
-
|
|
49
|
+
}
|
|
50
|
+
const mutableAttributes = selectMutableAttributes(loadAsciiDoc, contentCatalog, startPage, mergedAsciiDocConfig)
|
|
43
51
|
delete mutableAttributes.doctype
|
|
44
52
|
accum = accum.concat(
|
|
45
53
|
prepareOutlines(navigation, rootEntry, rootLevel).map((outline) =>
|
|
@@ -56,10 +64,10 @@ function produceAggregateDocuments (loadAsciiDoc, contentCatalog, assemblerConfi
|
|
|
56
64
|
)
|
|
57
65
|
)
|
|
58
66
|
)
|
|
59
|
-
|
|
67
|
+
mergedAsciiDocAttributes.doctype = doctype
|
|
60
68
|
sourceHighlighter
|
|
61
|
-
? (
|
|
62
|
-
: delete
|
|
69
|
+
? (mergedAsciiDocAttributes['source-highlighter'] = sourceHighlighter)
|
|
70
|
+
: delete mergedAsciiDocAttributes['source-highlighter']
|
|
63
71
|
return accum
|
|
64
72
|
},
|
|
65
73
|
[]
|
|
@@ -113,6 +121,10 @@ function includedInNav (items, url) {
|
|
|
113
121
|
return items.find((it) => it.url === url || includedInNav(it.items || [], url))
|
|
114
122
|
}
|
|
115
123
|
|
|
124
|
+
function isResourceRef (target) {
|
|
125
|
+
return ~target.indexOf(':') && !(~target.indexOf('://') || (target.startsWith('data:') && ~target.indexOf(',')))
|
|
126
|
+
}
|
|
127
|
+
|
|
116
128
|
// when root level is 0, merge the navigation into the rootEntry
|
|
117
129
|
// when root level is 1, create navigation per navigation menu
|
|
118
130
|
// in this case, if there's only a single navigation menu with no title, promote each top-level item to a menu
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const XML_TAG_RX = /<[^>]+>/g
|
|
4
|
+
const XML_SPECIAL_CHARS = { '<': '<', '>': '>', '&': '&' }
|
|
5
|
+
const XML_SPECIAL_CHARS_RX = /&(?:[lg]t|amp);/g
|
|
6
|
+
|
|
7
|
+
function sanitize (str) {
|
|
8
|
+
return ~str.indexOf('<')
|
|
9
|
+
? [str.replace(XML_TAG_RX, '').replace(XML_SPECIAL_CHARS_RX, (m) => XML_SPECIAL_CHARS[m]), `+++${str}+++`, true]
|
|
10
|
+
: ~str.indexOf('&')
|
|
11
|
+
? Array(2).fill(str.replace(XML_SPECIAL_CHARS_RX, (m) => XML_SPECIAL_CHARS[m]))
|
|
12
|
+
: [str, str]
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = sanitize
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antora/assembler",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.8",
|
|
4
4
|
"description": "An extension library for Antora that assembles content from multiple pages into a single AsciiDoc file to converted and publish.",
|
|
5
5
|
"license": "MPL-2.0",
|
|
6
6
|
"author": "OpenDevise Inc. (https://opendevise.com)",
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "_mocha test",
|
|
18
|
-
"prepublishOnly": "
|
|
19
|
-
"postpublish": "
|
|
18
|
+
"prepublishOnly": "npx -y downdoc --prepublish",
|
|
19
|
+
"postpublish": "npx -y downdoc --postpublish"
|
|
20
20
|
},
|
|
21
21
|
"main": "lib/index.js",
|
|
22
22
|
"exports": {
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"js-yaml": "~4.1"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@antora/asciidoc-loader": "3.
|
|
40
|
-
"@antora/site-publisher": "3.
|
|
39
|
+
"@antora/asciidoc-loader": "~3.1",
|
|
40
|
+
"@antora/site-publisher": "~3.1"
|
|
41
41
|
},
|
|
42
42
|
"engines": {
|
|
43
43
|
"node": ">=16.0.0"
|