@feelpp/antora-extensions 1.0.0-rc.1 → 1.0.0-rc.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.
- package/data/css/listing.css +79 -0
- package/data/helpers/image-spec.js +17 -0
- package/data/helpers/listing-get-sections.js +43 -0
- package/data/helpers/resolve-resource-url.js +13 -0
- package/data/partials/listing-card.hbs +13 -0
- package/data/partials/listing.hbs +16 -0
- package/package.json +1 -1
- package/src/index.js +5 -3
- package/src/lazy-readable.js +19 -0
- package/src/listing.js +108 -0
- package/src/template.js +3 -0
- package/src/toolbox-navigation.js +5 -3
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
.listing .listing-container {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-wrap: wrap;
|
|
4
|
+
justify-content: flex-start;
|
|
5
|
+
margin-top: 5em;
|
|
6
|
+
margin-bottom: 5em;
|
|
7
|
+
gap: clamp(1.5em, 2vw, 5em);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.listing .listing-container .listing-card-container {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex: 1 calc(33.33% - 5em);
|
|
13
|
+
max-width: 500px;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Media query for small screens */
|
|
17
|
+
@media (max-width: 767px) {
|
|
18
|
+
.listing .listing-container .listing-card-container {
|
|
19
|
+
flex: 1 250px;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Media query for large screens */
|
|
24
|
+
@media (min-width: 768px) {
|
|
25
|
+
.listing .listing-container .listing-card-container {
|
|
26
|
+
min-width: 250px;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.listing .listing-card:hover,
|
|
31
|
+
.listing .listing-card:focus {
|
|
32
|
+
box-shadow: 0 6px 12px 1px #104d92ad;
|
|
33
|
+
text-decoration: none;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.listing .listing-card {
|
|
37
|
+
box-shadow: 0 3px 6px 1px #e0e0e0;
|
|
38
|
+
padding: 1.5em 1em 1em;
|
|
39
|
+
display: flex;
|
|
40
|
+
flex-direction: column;
|
|
41
|
+
color: initial;
|
|
42
|
+
border-radius: 0.5em;
|
|
43
|
+
flex: 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.listing .listing-card-illustration {
|
|
47
|
+
height: 150px;
|
|
48
|
+
object-fit: contain;
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: center;
|
|
52
|
+
padding-left: 1em;
|
|
53
|
+
padding-right: 1em;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.listing .listing-card-illustration > img {
|
|
57
|
+
max-width: 100%;
|
|
58
|
+
height: 100%;
|
|
59
|
+
object-fit: cover;
|
|
60
|
+
border-radius: 0.25em;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.listing .listing-card .listing-card-body {
|
|
64
|
+
border-top: 1px solid #e1e1e1;
|
|
65
|
+
margin-top: 1rem;
|
|
66
|
+
padding-bottom: 0.75rem;
|
|
67
|
+
margin-left: 0.75rem;
|
|
68
|
+
margin-right: 0.75rem;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.listing .listing-card .listing-card-body p {
|
|
72
|
+
font-size: 0.9em;
|
|
73
|
+
color: #56595c;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.listing .listing-card .listing-card-body h4 {
|
|
77
|
+
font-weight: 600;
|
|
78
|
+
margin-bottom: 0.5rem;
|
|
79
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Adds image$ family if missing
|
|
5
|
+
* @param {string} spec
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
module.exports = (spec) => {
|
|
9
|
+
if (!spec || spec.includes('image$') || spec.startsWith('http://') || spec.startsWith('https://')) {
|
|
10
|
+
return spec
|
|
11
|
+
}
|
|
12
|
+
const lastColon = spec.lastIndexOf(':')
|
|
13
|
+
if (lastColon > 0 && lastColon < spec.length - 1) {
|
|
14
|
+
return spec.slice(0, lastColon + 1) + 'image$' + spec.slice(lastColon + 1)
|
|
15
|
+
}
|
|
16
|
+
return 'image$' + spec
|
|
17
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const listingConfig = ${listingConfig}
|
|
4
|
+
|
|
5
|
+
module.exports = (configName, page, {data: {root}}) => {
|
|
6
|
+
const {contentCatalog} = root
|
|
7
|
+
if (configName in listingConfig) {
|
|
8
|
+
const config = listingConfig[configName]
|
|
9
|
+
if (config) {
|
|
10
|
+
return config.map((section) => {
|
|
11
|
+
const selector = {
|
|
12
|
+
tag: section.tag,
|
|
13
|
+
withinParentModule: section.withinParentModule
|
|
14
|
+
}
|
|
15
|
+
const pages = getPages(contentCatalog, page, selector)
|
|
16
|
+
return {
|
|
17
|
+
title: section.sectionTitle,
|
|
18
|
+
pages,
|
|
19
|
+
}
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return []
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const getPages = function (contentCatalog, page, {tag, withinParentModule}) {
|
|
27
|
+
const pages = contentCatalog.getPages(({asciidoc, out, src}) => {
|
|
28
|
+
if (!out || !asciidoc) return
|
|
29
|
+
if (src.component !== page.componentVersion.name ||
|
|
30
|
+
(withinParentModule && src.module !== page.module) ||
|
|
31
|
+
src.version !== page.componentVersion.version) return
|
|
32
|
+
const pageTags = asciidoc.attributes['page-tags']
|
|
33
|
+
return pageTags && pageTags.split(',').map((v) => v.trim()).includes(tag)
|
|
34
|
+
}).sort((a, b) => (a.title || '').localeCompare((b.title || '')))
|
|
35
|
+
if (pages && pages.length > 0) {
|
|
36
|
+
while (pages.length % 3 !== 0) {
|
|
37
|
+
pages.push({
|
|
38
|
+
empty: true,
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return pages
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
module.exports = (spec, { data, hash: context }) => {
|
|
4
|
+
if (!spec) return
|
|
5
|
+
const { contentCatalog, page } = data.root
|
|
6
|
+
if (page.component) {
|
|
7
|
+
context = Object.assign({ component: page.component.name, version: page.version, module: page.module }, context)
|
|
8
|
+
}
|
|
9
|
+
const file = contentCatalog.resolveResource(spec, context, 'page', ['page', 'attachment', 'image'])
|
|
10
|
+
if (file) {
|
|
11
|
+
return file.pub.url
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{{#if empty}}
|
|
2
|
+
<div class="empty"></div>
|
|
3
|
+
{{else}}
|
|
4
|
+
<a class="listing-card" href="{{{relativize pub.url}}}">
|
|
5
|
+
<div class="listing-card-illustration">
|
|
6
|
+
<img alt="" src="{{relativize (resolve-resource-url (image-spec asciidoc.attributes.page-illustration))}}"/>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="listing-card-body">
|
|
9
|
+
<h4>{{title}}</h4>
|
|
10
|
+
<p class="listing-card-description">{{{asciidoc.attributes.description}}}</p>
|
|
11
|
+
</div>
|
|
12
|
+
</a>
|
|
13
|
+
{{/if}}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<div class="listing">
|
|
2
|
+
{{#with (listing-get-sections configName @root.page)}}
|
|
3
|
+
{{#if this.length}}
|
|
4
|
+
{{#each this}}
|
|
5
|
+
{{#if this.pages.length}}
|
|
6
|
+
<h2>{{{this.title}}}</h2>
|
|
7
|
+
<div class="listing-container">
|
|
8
|
+
{{#each this.pages}}
|
|
9
|
+
<div class="listing-card-container">{{> listing-card this}}</div>
|
|
10
|
+
{{/each}}
|
|
11
|
+
</div>
|
|
12
|
+
{{/if}}
|
|
13
|
+
{{/each}}
|
|
14
|
+
{{/if}}
|
|
15
|
+
{{/with}}
|
|
16
|
+
</div>
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {register: registerTooboxNavigation} = require('./toolbox-navigation.js')
|
|
2
|
+
const {register: registerListing} = require('./listing.js')
|
|
2
3
|
|
|
3
|
-
module.exports.register = (
|
|
4
|
-
registerTooboxNavigation(
|
|
4
|
+
module.exports.register = (thisContext, args) => {
|
|
5
|
+
registerTooboxNavigation.call(thisContext, args)
|
|
6
|
+
registerListing.call(thisContext, args)
|
|
5
7
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { PassThrough } = require('stream')
|
|
4
|
+
|
|
5
|
+
// adapted from https://github.com/jpommerening/node-lazystream/blob/master/lib/lazystream.js | license: MIT
|
|
6
|
+
class LazyReadable extends PassThrough {
|
|
7
|
+
constructor (fn, options) {
|
|
8
|
+
super(options)
|
|
9
|
+
this._read = function () {
|
|
10
|
+
delete this._read // restores original method
|
|
11
|
+
fn.call(this, options).on('error', this.emit.bind(this, 'error')).pipe(this)
|
|
12
|
+
return this._read.apply(this, arguments)
|
|
13
|
+
}
|
|
14
|
+
this.emit('readable')
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = LazyReadable
|
|
19
|
+
|
package/src/listing.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
// The name of the package in order to give the Antora logger a useful name
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const ospath = require('path')
|
|
6
|
+
const { promises: fsp } = fs
|
|
7
|
+
const LazyReadable = require('./lazy-readable')
|
|
8
|
+
const template = require('./template')
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @module listing-extension
|
|
12
|
+
*/
|
|
13
|
+
function register ({ config: { listing, ...unknownOptions } }) {
|
|
14
|
+
const packageName = "@feelpp/antora-listing-extension"
|
|
15
|
+
const logger = this.getLogger(packageName)
|
|
16
|
+
|
|
17
|
+
if (Object.keys(unknownOptions).length) {
|
|
18
|
+
const keys = Object.keys(unknownOptions)
|
|
19
|
+
throw new Error(`Unrecognized option${keys.length > 1 ? 's' : ''} specified for ${packageName}: ${keys.join(', ')}`)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
this.on('uiLoaded', async ({playbook, uiCatalog}) => {
|
|
23
|
+
playbook.env.SITE_LISTING_EXTENSION_ENABLED = 'true'
|
|
24
|
+
const uiOutputDir = playbook.ui.outputDir
|
|
25
|
+
assetFile(uiCatalog, logger, uiOutputDir, 'css', 'listing.css')
|
|
26
|
+
const helperPath = 'helpers/listing-get-sections.js'
|
|
27
|
+
if (uiCatalog.findByType('helper').some(({ path }) => path === helperPath)) {
|
|
28
|
+
// ignore
|
|
29
|
+
} else {
|
|
30
|
+
const helperFilePath = ospath.join(__dirname, '../data', helperPath)
|
|
31
|
+
uiCatalog.addFile({
|
|
32
|
+
contents: Buffer.from(template(await fsp.readFile(helperFilePath, 'utf8'), {listingConfig: JSON.stringify(listing)})),
|
|
33
|
+
path: helperPath,
|
|
34
|
+
stem: ospath.parse(helperPath).name,
|
|
35
|
+
type: 'helper',
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
await helperFile(uiCatalog, 'helpers/image-spec.js')
|
|
39
|
+
await helperFile(uiCatalog, 'helpers/resolve-resource-url.js')
|
|
40
|
+
await partialFile(uiCatalog, 'partials/listing.hbs')
|
|
41
|
+
await partialFile(uiCatalog, 'partials/listing-card.hbs')
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
this.on('beforePublish', ({ playbook }) => {
|
|
45
|
+
delete playbook.env.SITE_LISTING_EXTENSION_ENABLED
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async function partialFile(uiCatalog, partialPath) {
|
|
50
|
+
if (uiCatalog.findByType('partial').some(({ path }) => path === partialPath)) {
|
|
51
|
+
// ignore
|
|
52
|
+
} else {
|
|
53
|
+
const partialFilePath = ospath.join(__dirname, '../data', partialPath)
|
|
54
|
+
uiCatalog.addFile({
|
|
55
|
+
contents: Buffer.from(await fsp.readFile(partialFilePath, 'utf8')),
|
|
56
|
+
path: partialPath,
|
|
57
|
+
stem: ospath.parse(partialPath).name,
|
|
58
|
+
type: 'partial',
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function helperFile(uiCatalog, helperPath) {
|
|
64
|
+
if (uiCatalog.findByType('helper').some(({ path }) => path === helperPath)) {
|
|
65
|
+
// ignore
|
|
66
|
+
} else {
|
|
67
|
+
const helperFilePath = ospath.join(__dirname, '../data', helperPath)
|
|
68
|
+
uiCatalog.addFile({
|
|
69
|
+
contents: Buffer.from(await fsp.readFile(helperFilePath, 'utf8')),
|
|
70
|
+
path: helperPath,
|
|
71
|
+
stem: ospath.parse(helperPath).name,
|
|
72
|
+
type: 'helper',
|
|
73
|
+
})
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function assetFile (
|
|
78
|
+
uiCatalog,
|
|
79
|
+
logger,
|
|
80
|
+
uiOutputDir,
|
|
81
|
+
assetDir,
|
|
82
|
+
basename,
|
|
83
|
+
assetPath = assetDir + '/' + basename,
|
|
84
|
+
contents = new LazyReadable(() => fs.createReadStream(ospath.join(__dirname, '../data', assetPath))),
|
|
85
|
+
overwrite = false
|
|
86
|
+
) {
|
|
87
|
+
const outputDir = uiOutputDir + '/' + assetDir
|
|
88
|
+
const existingFile = uiCatalog.findByType('asset').some(({path}) => path === assetPath)
|
|
89
|
+
if (existingFile) {
|
|
90
|
+
if (overwrite) {
|
|
91
|
+
logger.warn(`Please remove the following file from your UI since it is managed by ${packageName}: ${assetPath}`)
|
|
92
|
+
existingFile.contents = contents
|
|
93
|
+
delete existingFile.stat
|
|
94
|
+
} else {
|
|
95
|
+
logger.info(`The following file already exists in your UI: ${assetPath}, skipping`)
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
uiCatalog.addFile({
|
|
99
|
+
contents,
|
|
100
|
+
type: 'asset',
|
|
101
|
+
path: assetPath,
|
|
102
|
+
out: {dirname: outputDir, path: outputDir + '/' + basename, basename},
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports.register = register
|
package/src/template.js
ADDED
|
@@ -16,9 +16,9 @@ function getToolboxPageModules(contentCatalog, version, tag) {
|
|
|
16
16
|
}, {})
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
const logger =
|
|
21
|
-
|
|
19
|
+
function register() {
|
|
20
|
+
const logger = this.getLogger('toolbox-navigation')
|
|
21
|
+
this.on('navigationBuilt', ({contentCatalog, navigationCatalog}) => {
|
|
22
22
|
const toolboxComponent = contentCatalog.getComponent('toolboxes')
|
|
23
23
|
if (toolboxComponent) {
|
|
24
24
|
const componentVersions = toolboxComponent.versions
|
|
@@ -91,3 +91,5 @@ module.exports.register = (context) => {
|
|
|
91
91
|
}
|
|
92
92
|
})
|
|
93
93
|
}
|
|
94
|
+
|
|
95
|
+
module.exports.register = register
|