@conduction/docusaurus-preset 3.19.0 → 3.21.0
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/package.json +1 -1
- package/src/components/FeaturesPage/FeaturesPage.jsx +57 -0
- package/src/components/SetupSteps/SetupSteps.jsx +81 -0
- package/src/components/SetupSteps/SetupSteps.module.css +82 -0
- package/src/components/index.js +2 -0
- package/src/index.js +11 -0
- package/src/plugins/features-page.js +89 -0
package/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <FeaturesPage />
|
|
3
|
+
*
|
|
4
|
+
* Route component used by the `conduction-features-page` plugin. Renders
|
|
5
|
+
* the list of implemented/reviewed capabilities extracted from
|
|
6
|
+
* `openspec/specs/` into `docs/features.json` (regenerated by the
|
|
7
|
+
* org-wide Features Extract workflow stage).
|
|
8
|
+
*
|
|
9
|
+
* Reuses the brand `<FeatureGrid>` for the actual list — every entry maps
|
|
10
|
+
* to a single `<FeatureItem>` with status `stable` (the extractor only
|
|
11
|
+
* emits specs whose frontmatter declares `implemented` or `reviewed`,
|
|
12
|
+
* both of which are stable by definition).
|
|
13
|
+
*
|
|
14
|
+
* Receives one prop module from the plugin via `addRoute({modules})`:
|
|
15
|
+
*
|
|
16
|
+
* data: {
|
|
17
|
+
* features: Array<{slug, title, summary, docsUrl}>,
|
|
18
|
+
* title: string,
|
|
19
|
+
* intro: string | null,
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* Sites that want a different page chrome can swizzle the plugin route
|
|
23
|
+
* to register their own component; this default is intentionally minimal
|
|
24
|
+
* (one heading, one optional intro paragraph, the grid).
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import React from 'react';
|
|
28
|
+
import Layout from '@theme/Layout';
|
|
29
|
+
import FeatureGrid from '../FeatureGrid/FeatureGrid.jsx';
|
|
30
|
+
|
|
31
|
+
export default function FeaturesPage({data}) {
|
|
32
|
+
const {features = [], title = 'Features', intro = null} = data || {};
|
|
33
|
+
|
|
34
|
+
const items = features.map((f) => ({
|
|
35
|
+
label: f.title || f.slug,
|
|
36
|
+
tip: f.summary || '',
|
|
37
|
+
status: 'stable',
|
|
38
|
+
href: f.docsUrl || undefined,
|
|
39
|
+
}));
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Layout
|
|
43
|
+
title={title}
|
|
44
|
+
description={intro || `Capabilities shipped in this app, sourced from openspec/specs/.`}
|
|
45
|
+
>
|
|
46
|
+
<main className="container margin-vert--lg">
|
|
47
|
+
<h1>{title}</h1>
|
|
48
|
+
{intro && <p>{intro}</p>}
|
|
49
|
+
{items.length === 0 ? (
|
|
50
|
+
<p>No features documented yet.</p>
|
|
51
|
+
) : (
|
|
52
|
+
<FeatureGrid items={items} legend />
|
|
53
|
+
)}
|
|
54
|
+
</main>
|
|
55
|
+
</Layout>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <SetupSteps /> + <SetupStep />
|
|
3
|
+
*
|
|
4
|
+
* Vertical numbered-step list for docs setup/install/walkthrough
|
|
5
|
+
* sections and editorial action prompts. A small orange hex with a
|
|
6
|
+
* white number sits on the left of each row; bold title + one short
|
|
7
|
+
* body paragraph sit on the right.
|
|
8
|
+
*
|
|
9
|
+
* Distinct from <HowSteps />, which renders a 3-up card row for
|
|
10
|
+
* marketing surfaces (How install works, How to get help, …).
|
|
11
|
+
* SetupSteps lives in body prose where steps are sequential and
|
|
12
|
+
* detailed, and the list reads top to bottom.
|
|
13
|
+
*
|
|
14
|
+
* Numbers are auto-assigned in document order. Pass `start` to
|
|
15
|
+
* resume after a break (e.g. a continued procedure split across
|
|
16
|
+
* sections).
|
|
17
|
+
*
|
|
18
|
+
* Usage:
|
|
19
|
+
*
|
|
20
|
+
* <SetupSteps title="Setup" lede="Numbered steps you read top to bottom.">
|
|
21
|
+
* <SetupStep title="Add an xWiki source">
|
|
22
|
+
* Open *Integrations → Sources → New source*, paste the base URL.
|
|
23
|
+
* </SetupStep>
|
|
24
|
+
* <SetupStep title="Pick the spaces to index">
|
|
25
|
+
* Per source, choose one or more xWiki spaces.
|
|
26
|
+
* </SetupStep>
|
|
27
|
+
* <SetupStep title="Verify">
|
|
28
|
+
* Open the app's chat sidebar and ask a matching question.
|
|
29
|
+
* </SetupStep>
|
|
30
|
+
* </SetupSteps>
|
|
31
|
+
*
|
|
32
|
+
* // Editorial / action-prompt use, no h2 above the list:
|
|
33
|
+
* <SetupSteps title={null}>
|
|
34
|
+
* <SetupStep title="Call Nextcloud by its proper name.">
|
|
35
|
+
* In tenders, in policy documents, in architecture diagrams.
|
|
36
|
+
* </SetupStep>
|
|
37
|
+
* ...
|
|
38
|
+
* </SetupSteps>
|
|
39
|
+
*
|
|
40
|
+
* Mirrors preview/components/setup-steps.html.
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
import React, {Children, cloneElement, isValidElement} from 'react';
|
|
44
|
+
import styles from './SetupSteps.module.css';
|
|
45
|
+
|
|
46
|
+
export function SetupStep({number, title, children, className}) {
|
|
47
|
+
const composed = [styles.row, className].filter(Boolean).join(' ');
|
|
48
|
+
return (
|
|
49
|
+
<div className={composed}>
|
|
50
|
+
{number != null && <div className={styles.num}>{number}</div>}
|
|
51
|
+
{/* div not p: MDX may wrap inline children in its own <p>,
|
|
52
|
+
which would be invalid nested inside another <p>. */}
|
|
53
|
+
<div className={styles.body}>
|
|
54
|
+
{title && <span className={styles.bodyTitle}>{title}</span>}
|
|
55
|
+
{children}
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default function SetupSteps({
|
|
62
|
+
title,
|
|
63
|
+
lede,
|
|
64
|
+
start = 1,
|
|
65
|
+
children,
|
|
66
|
+
className,
|
|
67
|
+
}) {
|
|
68
|
+
const numbered = Children.map(children, (child, i) => {
|
|
69
|
+
if (!isValidElement(child)) return child;
|
|
70
|
+
if (child.props.number != null) return child;
|
|
71
|
+
return cloneElement(child, {number: start + i});
|
|
72
|
+
});
|
|
73
|
+
const composed = [styles.wrap, className].filter(Boolean).join(' ');
|
|
74
|
+
return (
|
|
75
|
+
<section className={composed}>
|
|
76
|
+
{title && <h2 className={styles.title}>{title}</h2>}
|
|
77
|
+
{lede && <p className={styles.lede}>{lede}</p>}
|
|
78
|
+
{numbered}
|
|
79
|
+
</section>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <SetupSteps /> + <SetupStep /> styles. Vertical numbered-row
|
|
3
|
+
* pattern for docs setup/install/walkthrough sections and editorial
|
|
4
|
+
* action lists. Small orange hex with a white number on the left,
|
|
5
|
+
* bold title + one short body paragraph on the right.
|
|
6
|
+
*
|
|
7
|
+
* Distinct from <HowSteps /> which is a 3-up card row for marketing
|
|
8
|
+
* surfaces. SetupSteps lives inside body prose, so steps stack
|
|
9
|
+
* vertically and read like sentences in a list.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors preview/components/setup-steps.html.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
.wrap {
|
|
15
|
+
margin: var(--space-7) 0;
|
|
16
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.title {
|
|
20
|
+
font-size: 28px;
|
|
21
|
+
font-weight: 700;
|
|
22
|
+
letter-spacing: -0.01em;
|
|
23
|
+
color: var(--c-cobalt-900);
|
|
24
|
+
margin: 0 0 var(--space-3);
|
|
25
|
+
line-height: 1.2;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.lede {
|
|
29
|
+
font-size: 16px;
|
|
30
|
+
line-height: 1.6;
|
|
31
|
+
color: var(--c-cobalt-700);
|
|
32
|
+
margin: 0 0 var(--space-5);
|
|
33
|
+
max-width: 60ch;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.row {
|
|
37
|
+
display: flex;
|
|
38
|
+
align-items: flex-start;
|
|
39
|
+
gap: var(--space-4);
|
|
40
|
+
margin: var(--space-4) 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.num {
|
|
44
|
+
width: 36px;
|
|
45
|
+
height: 42px;
|
|
46
|
+
clip-path: var(--hex-pointy-top);
|
|
47
|
+
background: var(--c-orange-knvb);
|
|
48
|
+
color: white;
|
|
49
|
+
display: flex;
|
|
50
|
+
align-items: center;
|
|
51
|
+
justify-content: center;
|
|
52
|
+
font-weight: 600;
|
|
53
|
+
flex-shrink: 0;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.body {
|
|
57
|
+
font-size: 14px;
|
|
58
|
+
line-height: 1.55;
|
|
59
|
+
color: var(--c-cobalt-700);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.body strong,
|
|
63
|
+
.bodyTitle {
|
|
64
|
+
display: block;
|
|
65
|
+
margin-bottom: 4px;
|
|
66
|
+
color: var(--c-cobalt-900);
|
|
67
|
+
font-size: 17px;
|
|
68
|
+
font-weight: 600;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.body p {
|
|
72
|
+
margin: 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.body code {
|
|
76
|
+
background: var(--c-cobalt-50);
|
|
77
|
+
padding: 1px 6px;
|
|
78
|
+
border-radius: 3px;
|
|
79
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
80
|
+
font-size: 13px;
|
|
81
|
+
color: var(--c-cobalt-900);
|
|
82
|
+
}
|
package/src/components/index.js
CHANGED
|
@@ -48,6 +48,7 @@ export {default as ReferenceCard, ReferenceGrid} from './ReferenceCard/Reference
|
|
|
48
48
|
export {default as PairCard, PairRow} from './PairCard/PairCard.jsx';
|
|
49
49
|
export {default as FeatureList, FeatureItem} from './FeatureList/FeatureList.jsx';
|
|
50
50
|
export {default as HowSteps, HowStep} from './HowSteps/HowSteps.jsx';
|
|
51
|
+
export {default as SetupSteps, SetupStep} from './SetupSteps/SetupSteps.jsx';
|
|
51
52
|
export {default as FAQ, FAQItem} from './FAQ/FAQ.jsx';
|
|
52
53
|
export {default as EmployeeCard, TeamGrid} from './EmployeeCard/EmployeeCard.jsx';
|
|
53
54
|
export {default as DetailHero} from './DetailHero/DetailHero.jsx';
|
|
@@ -88,6 +89,7 @@ export {default as McpToolShelf} from './McpToolShelf/McpToolShelf.jsx';
|
|
|
88
89
|
export {default as ExternalAppShelf} from './ExternalAppShelf/ExternalAppShelf.jsx';
|
|
89
90
|
export {default as WidgetShelf} from './WidgetShelf/WidgetShelf.jsx';
|
|
90
91
|
export {default as FeatureGrid, FeatureGridGroup, FeatureItem as FeatureGridItem} from './FeatureGrid/FeatureGrid.jsx';
|
|
92
|
+
export {default as FeaturesPage} from './FeaturesPage/FeaturesPage.jsx';
|
|
91
93
|
|
|
92
94
|
/* Academy components (Batch 4). Card-and-chrome patterns for
|
|
93
95
|
academy.conduction.nl: a single feed of blogs, guides, case studies,
|
package/src/index.js
CHANGED
|
@@ -683,6 +683,17 @@ function createConfig(opts) {
|
|
|
683
683
|
require.resolve('./plugins/indexnow.js'),
|
|
684
684
|
opts.indexnow || {},
|
|
685
685
|
],
|
|
686
|
+
/* The Features Page plugin registers a `/features` route backed by
|
|
687
|
+
the consuming app's `docs/features.json` (regenerated from
|
|
688
|
+
openspec/specs/ by the org-wide Features Extract workflow stage).
|
|
689
|
+
No-ops when features.json is absent, so it's safe to enable
|
|
690
|
+
across the fleet ahead of full adoption. Sites opt out via
|
|
691
|
+
opts.featuresPage = { disable: true } or override the page
|
|
692
|
+
title / intro / route path. */
|
|
693
|
+
[
|
|
694
|
+
require.resolve('./plugins/features-page.js'),
|
|
695
|
+
opts.featuresPage || {},
|
|
696
|
+
],
|
|
686
697
|
...(opts.plugins || []),
|
|
687
698
|
],
|
|
688
699
|
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @conduction/docusaurus-preset/plugins/features-page
|
|
3
|
+
*
|
|
4
|
+
* Adds a `/features` route to every Conduction docs site, backed by the
|
|
5
|
+
* `docs/features.json` artefact regenerated from `openspec/specs/` by the
|
|
6
|
+
* org-wide Features Extract workflow stage
|
|
7
|
+
* (https://github.com/ConductionNL/.github/blob/main/.github/workflows/quality.yml).
|
|
8
|
+
*
|
|
9
|
+
* The route renders the brand `<FeaturesPage />` component, which in turn
|
|
10
|
+
* maps each entry to a `<FeatureItem>` inside `<FeatureGrid>`. Every entry
|
|
11
|
+
* is rendered with status `stable` (the extractor only emits specs whose
|
|
12
|
+
* frontmatter declares `status: implemented` or `status: reviewed`, both
|
|
13
|
+
* of which map to the mint hex bullet).
|
|
14
|
+
*
|
|
15
|
+
* Options:
|
|
16
|
+
* path string route path (default '/features')
|
|
17
|
+
* featuresFile string path to features.json (default 'features.json',
|
|
18
|
+
* resolved against `siteDir` — i.e. the directory
|
|
19
|
+
* holding `docusaurus.config.js`)
|
|
20
|
+
* title string page title (default 'Features')
|
|
21
|
+
* intro string optional intro paragraph rendered above the grid
|
|
22
|
+
* disable boolean skip the plugin entirely (the consuming site can
|
|
23
|
+
* keep the entry in `createConfig({featuresPage:
|
|
24
|
+
* {disable: true}})` instead of yanking the plugin)
|
|
25
|
+
*
|
|
26
|
+
* No-op when the resolved `featuresFile` doesn't exist on disk. This
|
|
27
|
+
* keeps the plugin safe to enable across the fleet before every app has
|
|
28
|
+
* adopted the workflow stage that produces the file.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
|
|
34
|
+
function loadFeatures(absPath) {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(absPath)) return null;
|
|
37
|
+
const raw = fs.readFileSync(absPath, 'utf8');
|
|
38
|
+
const parsed = JSON.parse(raw);
|
|
39
|
+
if (!Array.isArray(parsed)) return null;
|
|
40
|
+
return parsed;
|
|
41
|
+
} catch (e) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function featuresPagePlugin(context, options = {}) {
|
|
47
|
+
if (options.disable === true) return {name: 'conduction-features-page'};
|
|
48
|
+
|
|
49
|
+
const featuresFile = options.featuresFile || 'features.json';
|
|
50
|
+
const absPath = path.isAbsolute(featuresFile)
|
|
51
|
+
? featuresFile
|
|
52
|
+
: path.resolve(context.siteDir, featuresFile);
|
|
53
|
+
|
|
54
|
+
const features = loadFeatures(absPath);
|
|
55
|
+
if (features === null) {
|
|
56
|
+
/* Plugin is enabled but the consuming site hasn't produced
|
|
57
|
+
features.json yet — most likely the Features Extract workflow
|
|
58
|
+
stage hasn't run, or the app has no openspec/specs/ entries with
|
|
59
|
+
a publishable status. Stay silent rather than build-fail. */
|
|
60
|
+
return {name: 'conduction-features-page'};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const routePath = options.path || '/features';
|
|
64
|
+
const title = options.title || 'Features';
|
|
65
|
+
const intro = options.intro || null;
|
|
66
|
+
|
|
67
|
+
return {
|
|
68
|
+
name: 'conduction-features-page',
|
|
69
|
+
|
|
70
|
+
async loadContent() {
|
|
71
|
+
return {features, title, intro};
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
async contentLoaded({content, actions}) {
|
|
75
|
+
const dataPath = await actions.createData(
|
|
76
|
+
'conduction-features-page.json',
|
|
77
|
+
JSON.stringify(content)
|
|
78
|
+
);
|
|
79
|
+
actions.addRoute({
|
|
80
|
+
path: routePath,
|
|
81
|
+
component: require.resolve('../components/FeaturesPage/FeaturesPage.jsx'),
|
|
82
|
+
exact: true,
|
|
83
|
+
modules: {data: dataPath},
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
module.exports = featuresPagePlugin;
|