@conduction/docusaurus-preset 1.5.1 → 1.7.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 +2 -1
- package/src/components/AppCrossLinks/AppCrossLinks.jsx +148 -0
- package/src/components/AppCrossLinks/AppCrossLinks.module.css +178 -0
- package/src/components/ContentTypeFilter/ContentTypeFilter.jsx +28 -4
- package/src/components/index.js +2 -0
- package/src/data/apps-registry.js +61 -0
- package/src/index.js +2 -2
- package/src/theme/DocItem/Footer/index.jsx +104 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@conduction/docusaurus-preset",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"prepack": "node scripts/prepack-bundle-css.js"
|
|
6
6
|
},
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
".": "./src/index.js",
|
|
11
11
|
"./components": "./src/components/index.js",
|
|
12
12
|
"./data": "./src/data/app-downloads.js",
|
|
13
|
+
"./data/apps-registry": "./src/data/apps-registry.js",
|
|
13
14
|
"./css/brand.css": "./src/css/brand.css",
|
|
14
15
|
"./css/tokens.css": "./src/css/tokens.css",
|
|
15
16
|
"./theme": "./src/theme.js",
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <AppCrossLinks />
|
|
3
|
+
*
|
|
4
|
+
* Cross-links between the three surfaces a visitor lands on while
|
|
5
|
+
* learning about a Conduction app:
|
|
6
|
+
*
|
|
7
|
+
* 1. /apps/<slug> product page
|
|
8
|
+
* 2. https://docs.conduction.nl/<slug> documentation
|
|
9
|
+
* 3. /academy?app=<slug> academy filtered
|
|
10
|
+
*
|
|
11
|
+
* URLs come from the shared apps-registry so all three surfaces stay
|
|
12
|
+
* in lockstep without per-page constants.
|
|
13
|
+
*
|
|
14
|
+
* Two variants:
|
|
15
|
+
* - rail sticky right-rail card. Used on /apps/<slug>.mdx, paired
|
|
16
|
+
* with a 2-col grid (see partners/<slug>.mdx for the same
|
|
17
|
+
* layout pattern).
|
|
18
|
+
* - inline page-bottom block, three link tiles in a row. Used at
|
|
19
|
+
* the bottom of an academy post and on each app's docs
|
|
20
|
+
* overview page (or auto-injected via the DocItemFooter
|
|
21
|
+
* swizzle when the docs site sets themeConfig.conduction
|
|
22
|
+
* .appId in docusaurus.config.js).
|
|
23
|
+
*
|
|
24
|
+
* `surface` tells the component which row to omit, so we never link
|
|
25
|
+
* the user to the page they're already on:
|
|
26
|
+
* - surface="product" hides the "Open product page" row
|
|
27
|
+
* - surface="academy" hides the "Read in the academy" row
|
|
28
|
+
* - surface="docs" hides the "Read the documentation" row
|
|
29
|
+
* - surface omitted all three rows render
|
|
30
|
+
*
|
|
31
|
+
* Usage:
|
|
32
|
+
*
|
|
33
|
+
* import {AppCrossLinks} from '@conduction/docusaurus-preset/components';
|
|
34
|
+
*
|
|
35
|
+
* <AppCrossLinks
|
|
36
|
+
* variant="rail"
|
|
37
|
+
* apps={['opencatalogi']}
|
|
38
|
+
* surface="product"
|
|
39
|
+
* />
|
|
40
|
+
*
|
|
41
|
+
* <AppCrossLinks
|
|
42
|
+
* variant="inline"
|
|
43
|
+
* apps={frontMatter.apps}
|
|
44
|
+
* surface="academy"
|
|
45
|
+
* />
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
import React from 'react';
|
|
49
|
+
import {APPS_REGISTRY} from '../../data/apps-registry';
|
|
50
|
+
import styles from './AppCrossLinks.module.css';
|
|
51
|
+
|
|
52
|
+
const ROWS = [
|
|
53
|
+
{key: 'product', surfaceKey: 'product', hrefKey: 'productHref', label: 'Open product page', hint: 'Capabilities, pricing, install link'},
|
|
54
|
+
{key: 'docs', surfaceKey: 'docs', hrefKey: 'docsHref', label: 'Read the documentation', hint: 'Reference, guides, API'},
|
|
55
|
+
{key: 'academy', surfaceKey: 'academy', hrefKey: 'academyHref', label: 'Read in the academy', hint: 'Tutorials, case studies, blog'},
|
|
56
|
+
];
|
|
57
|
+
|
|
58
|
+
const ICONS = {
|
|
59
|
+
product: (
|
|
60
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
61
|
+
<path d="M3 7l9-4 9 4-9 4-9-4z" />
|
|
62
|
+
<path d="M3 12l9 4 9-4" />
|
|
63
|
+
<path d="M3 17l9 4 9-4" />
|
|
64
|
+
</svg>
|
|
65
|
+
),
|
|
66
|
+
docs: (
|
|
67
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
68
|
+
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" />
|
|
69
|
+
<path d="M14 2v6h6" />
|
|
70
|
+
<path d="M9 13h6M9 17h6M9 9h2" />
|
|
71
|
+
</svg>
|
|
72
|
+
),
|
|
73
|
+
academy: (
|
|
74
|
+
<svg viewBox="0 0 24 24" aria-hidden="true">
|
|
75
|
+
<path d="M3 7l9-4 9 4-9 4-9-4z" />
|
|
76
|
+
<path d="M5 11v5a7 7 0 0 0 14 0v-5" />
|
|
77
|
+
<path d="M21 7v5" />
|
|
78
|
+
</svg>
|
|
79
|
+
),
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
function AppCard({app, surface, variant}) {
|
|
83
|
+
const reg = APPS_REGISTRY[app];
|
|
84
|
+
if (!reg) return null;
|
|
85
|
+
|
|
86
|
+
const rows = ROWS.filter((row) => row.surfaceKey !== surface);
|
|
87
|
+
const tagId = `app-cross-links-${reg.slug}`;
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<div className={styles.card} aria-labelledby={tagId}>
|
|
91
|
+
<div className={styles.head}>
|
|
92
|
+
<span className={styles.hex} aria-hidden="true" />
|
|
93
|
+
<div>
|
|
94
|
+
<p className={styles.eyebrow}>About this app</p>
|
|
95
|
+
<h4 className={styles.title} id={tagId}>{reg.name}</h4>
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
|
|
99
|
+
<ul className={styles.rows}>
|
|
100
|
+
{rows.map((row) => (
|
|
101
|
+
<li key={row.key} className={styles.row}>
|
|
102
|
+
<a
|
|
103
|
+
href={reg[row.hrefKey]}
|
|
104
|
+
className={styles.rowLink}
|
|
105
|
+
target={row.key === 'docs' ? '_blank' : undefined}
|
|
106
|
+
rel={row.key === 'docs' ? 'noopener noreferrer' : undefined}
|
|
107
|
+
>
|
|
108
|
+
<span className={styles.rowIcon} aria-hidden="true">{ICONS[row.key]}</span>
|
|
109
|
+
<span className={styles.rowText}>
|
|
110
|
+
<span className={styles.rowLabel}>{row.label}</span>
|
|
111
|
+
<span className={styles.rowHint}>{row.hint}</span>
|
|
112
|
+
</span>
|
|
113
|
+
<span className={styles.rowArrow} aria-hidden="true">→</span>
|
|
114
|
+
</a>
|
|
115
|
+
</li>
|
|
116
|
+
))}
|
|
117
|
+
</ul>
|
|
118
|
+
</div>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export default function AppCrossLinks({
|
|
123
|
+
apps = [],
|
|
124
|
+
variant = 'inline',
|
|
125
|
+
surface,
|
|
126
|
+
className,
|
|
127
|
+
heading,
|
|
128
|
+
}) {
|
|
129
|
+
const known = apps.filter((slug) => APPS_REGISTRY[slug]);
|
|
130
|
+
if (known.length === 0) return null;
|
|
131
|
+
|
|
132
|
+
const composed = [
|
|
133
|
+
styles.root,
|
|
134
|
+
styles[`variant-${variant}`],
|
|
135
|
+
className,
|
|
136
|
+
].filter(Boolean).join(' ');
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<aside className={composed} aria-label="Related app links">
|
|
140
|
+
{heading && <h3 className={styles.heading}>{heading}</h3>}
|
|
141
|
+
<div className={styles.list}>
|
|
142
|
+
{known.map((slug) => (
|
|
143
|
+
<AppCard key={slug} app={slug} surface={surface} variant={variant} />
|
|
144
|
+
))}
|
|
145
|
+
</div>
|
|
146
|
+
</aside>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <AppCrossLinks /> styles. Rail variant mirrors PartnerSidecard's
|
|
3
|
+
* card chrome (white card, cobalt-100 hairline, 1-shadow). Inline
|
|
4
|
+
* variant lays the same cards out horizontally as a page-bottom
|
|
5
|
+
* cross-link block on academy posts and docs overview pages.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.root {
|
|
9
|
+
font-family: var(--conduction-typography-font-family-body);
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
gap: var(--space-4);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.heading {
|
|
16
|
+
font-size: 22px;
|
|
17
|
+
font-weight: 700;
|
|
18
|
+
letter-spacing: -0.01em;
|
|
19
|
+
color: var(--c-cobalt-900);
|
|
20
|
+
margin: 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.list {
|
|
24
|
+
display: flex;
|
|
25
|
+
flex-direction: column;
|
|
26
|
+
gap: var(--space-4);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.variant-rail {
|
|
30
|
+
width: 100%;
|
|
31
|
+
max-width: 320px;
|
|
32
|
+
position: sticky;
|
|
33
|
+
top: var(--space-8);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.variant-inline .list {
|
|
37
|
+
display: grid;
|
|
38
|
+
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
39
|
+
gap: var(--space-4);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.card {
|
|
43
|
+
background: white;
|
|
44
|
+
border: 1px solid var(--c-cobalt-100);
|
|
45
|
+
border-radius: var(--radius-lg);
|
|
46
|
+
padding: var(--space-5);
|
|
47
|
+
box-shadow: var(--shadow-1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ---------- Card head ---------- */
|
|
51
|
+
|
|
52
|
+
.head {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
gap: var(--space-3);
|
|
56
|
+
margin-bottom: var(--space-4);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.hex {
|
|
60
|
+
width: 28px;
|
|
61
|
+
height: 32px;
|
|
62
|
+
clip-path: var(--hex-pointy-top);
|
|
63
|
+
background: var(--c-blue-cobalt);
|
|
64
|
+
flex-shrink: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.eyebrow {
|
|
68
|
+
font-family: var(--conduction-typography-font-family-code);
|
|
69
|
+
font-size: 10px;
|
|
70
|
+
letter-spacing: 0.12em;
|
|
71
|
+
text-transform: uppercase;
|
|
72
|
+
color: var(--c-cobalt-400);
|
|
73
|
+
margin: 0 0 2px;
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.title {
|
|
78
|
+
font-size: 17px;
|
|
79
|
+
font-weight: 700;
|
|
80
|
+
letter-spacing: -0.01em;
|
|
81
|
+
color: var(--c-cobalt-900);
|
|
82
|
+
margin: 0;
|
|
83
|
+
line-height: 1.2;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* ---------- Rows ---------- */
|
|
87
|
+
|
|
88
|
+
.rows {
|
|
89
|
+
list-style: none;
|
|
90
|
+
margin: 0;
|
|
91
|
+
padding: 0;
|
|
92
|
+
display: flex;
|
|
93
|
+
flex-direction: column;
|
|
94
|
+
gap: 2px;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.row {
|
|
98
|
+
margin: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.rowLink {
|
|
102
|
+
display: grid;
|
|
103
|
+
grid-template-columns: 24px 1fr auto;
|
|
104
|
+
align-items: center;
|
|
105
|
+
gap: var(--space-3);
|
|
106
|
+
padding: var(--space-3);
|
|
107
|
+
border-radius: var(--radius-md);
|
|
108
|
+
background: var(--c-cobalt-50);
|
|
109
|
+
color: var(--c-cobalt-900);
|
|
110
|
+
text-decoration: none;
|
|
111
|
+
transition: background 160ms ease, color 160ms ease, transform 160ms ease;
|
|
112
|
+
}
|
|
113
|
+
.rowLink:hover {
|
|
114
|
+
background: var(--c-blue-cobalt);
|
|
115
|
+
color: white;
|
|
116
|
+
text-decoration: none;
|
|
117
|
+
transform: translateX(2px);
|
|
118
|
+
}
|
|
119
|
+
.rowLink:hover .rowHint,
|
|
120
|
+
.rowLink:hover .rowArrow {
|
|
121
|
+
color: rgba(255, 255, 255, 0.85);
|
|
122
|
+
}
|
|
123
|
+
.rowLink:hover .rowIcon {
|
|
124
|
+
color: white;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.rowIcon {
|
|
128
|
+
width: 22px;
|
|
129
|
+
height: 22px;
|
|
130
|
+
display: inline-flex;
|
|
131
|
+
align-items: center;
|
|
132
|
+
justify-content: center;
|
|
133
|
+
color: var(--c-blue-cobalt);
|
|
134
|
+
}
|
|
135
|
+
.rowIcon svg {
|
|
136
|
+
width: 22px;
|
|
137
|
+
height: 22px;
|
|
138
|
+
stroke: currentColor;
|
|
139
|
+
stroke-width: 1.6;
|
|
140
|
+
fill: none;
|
|
141
|
+
stroke-linecap: round;
|
|
142
|
+
stroke-linejoin: round;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.rowText {
|
|
146
|
+
display: flex;
|
|
147
|
+
flex-direction: column;
|
|
148
|
+
gap: 1px;
|
|
149
|
+
min-width: 0;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.rowLabel {
|
|
153
|
+
font-size: 14px;
|
|
154
|
+
font-weight: 600;
|
|
155
|
+
line-height: 1.3;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.rowHint {
|
|
159
|
+
font-size: 12px;
|
|
160
|
+
color: var(--c-cobalt-400);
|
|
161
|
+
line-height: 1.3;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.rowArrow {
|
|
165
|
+
font-size: 16px;
|
|
166
|
+
color: var(--c-cobalt-400);
|
|
167
|
+
font-weight: 600;
|
|
168
|
+
flex-shrink: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* ---------- Responsive ---------- */
|
|
172
|
+
|
|
173
|
+
@media (max-width: 900px) {
|
|
174
|
+
.variant-rail {
|
|
175
|
+
max-width: 100%;
|
|
176
|
+
position: static;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* <ContentTypeFilter />
|
|
3
3
|
*
|
|
4
|
-
* Top-of-page chip row that filters
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Top-of-page chip row that filters a feed by a single key. Defaults
|
|
5
|
+
* to academy content types ("Blogs", "Guides", "Case studies", …) but
|
|
6
|
+
* is reused on the academy landing as the product filter row by
|
|
7
|
+
* passing a different `types` + `labels` map (see APP_LABELS in
|
|
8
|
+
* @conduction/docusaurus-preset/data/apps-registry).
|
|
9
|
+
*
|
|
10
|
+
* "All" is the default chip and is always present. Driven by a query
|
|
11
|
+
* parameter (`?type=` for content types, `?app=` for products) so the
|
|
12
|
+
* filter survives reload and copy-paste.
|
|
7
13
|
*
|
|
8
14
|
* Mirrors the chip row in preview/components/academy.html.
|
|
9
15
|
*
|
|
@@ -29,6 +35,19 @@
|
|
|
29
35
|
*
|
|
30
36
|
* const [type, setType] = useState(null);
|
|
31
37
|
* <ContentTypeFilter value={type} onChange={setType} />
|
|
38
|
+
*
|
|
39
|
+
* Usage as a product filter (reuses the same component):
|
|
40
|
+
*
|
|
41
|
+
* import {APP_LABELS, APP_SLUGS} from '@conduction/docusaurus-preset/data/apps-registry';
|
|
42
|
+
* <ContentTypeFilter
|
|
43
|
+
* value={app}
|
|
44
|
+
* onChange={setApp}
|
|
45
|
+
* types={APP_SLUGS}
|
|
46
|
+
* labels={APP_LABELS}
|
|
47
|
+
* counts={appCounts}
|
|
48
|
+
* allLabel="All apps"
|
|
49
|
+
* allCount={totalCount}
|
|
50
|
+
* />
|
|
32
51
|
*/
|
|
33
52
|
|
|
34
53
|
import React from 'react';
|
|
@@ -48,9 +67,14 @@ export default function ContentTypeFilter({
|
|
|
48
67
|
allLabel = 'Everything',
|
|
49
68
|
allCount,
|
|
50
69
|
types = CONTENT_TYPES,
|
|
70
|
+
labels,
|
|
51
71
|
className,
|
|
52
72
|
}) {
|
|
53
73
|
const isLinkMode = typeof hrefForType === 'function';
|
|
74
|
+
const labelFor = (key) => {
|
|
75
|
+
if (labels && labels[key]) return labels[key];
|
|
76
|
+
return CONTENT_TYPE_PLURAL_LABELS[key] || key;
|
|
77
|
+
};
|
|
54
78
|
|
|
55
79
|
const renderChip = (key, label, count) => {
|
|
56
80
|
const active = (key === ALL && value == null) || key === value;
|
|
@@ -94,7 +118,7 @@ export default function ContentTypeFilter({
|
|
|
94
118
|
<div className={[styles.row, className].filter(Boolean).join(' ')}>
|
|
95
119
|
{renderChip(ALL, allLabel, allCount)}
|
|
96
120
|
{types.map((t) =>
|
|
97
|
-
renderChip(t,
|
|
121
|
+
renderChip(t, labelFor(t), counts && counts[t])
|
|
98
122
|
)}
|
|
99
123
|
</div>
|
|
100
124
|
);
|
package/src/components/index.js
CHANGED
|
@@ -94,6 +94,8 @@ export {
|
|
|
94
94
|
export {default as NewsletterCta} from './NewsletterCta/NewsletterCta.jsx';
|
|
95
95
|
export {default as RelatedPosts} from './RelatedPosts/RelatedPosts.jsx';
|
|
96
96
|
export {default as ContentDetailHero} from './ContentDetailHero/ContentDetailHero.jsx';
|
|
97
|
+
export {default as AppCrossLinks} from './AppCrossLinks/AppCrossLinks.jsx';
|
|
98
|
+
export {APPS_REGISTRY, APP_SLUGS, APP_LABELS, getApp, getApps} from '../data/apps-registry';
|
|
97
99
|
|
|
98
100
|
/* Tutorial-body components. Drop-in replacements for the ad-hoc
|
|
99
101
|
"What you need", "Troubleshooting", and "Next steps" h2 + bullet
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @conduction/docusaurus-preset/data/apps-registry
|
|
3
|
+
*
|
|
4
|
+
* URL-only registry of every public Conduction app. Single source of
|
|
5
|
+
* truth for cross-linking between the three surfaces a visitor lands
|
|
6
|
+
* on while learning about an app:
|
|
7
|
+
*
|
|
8
|
+
* 1. /apps/<slug> product detail page
|
|
9
|
+
* 2. https://docs.conduction.nl/<slug> documentation site (lives
|
|
10
|
+
* in the app's own repo, served from a per-app subfolder under
|
|
11
|
+
* the central docs.conduction.nl Docusaurus install)
|
|
12
|
+
* 3. /academy?app=<slug> academy posts filtered by app
|
|
13
|
+
*
|
|
14
|
+
* The registry is consumed by:
|
|
15
|
+
* - <AppCrossLinks/> renders the three links per app.
|
|
16
|
+
* - <ProductFilter/> renders the chip row on /academy.
|
|
17
|
+
* - sites/www/src/data/apps-catalog.js the live Conduction.nl
|
|
18
|
+
* site adds icons, taglines, and category metadata on top of
|
|
19
|
+
* this registry, so display data and URLs stay in lockstep.
|
|
20
|
+
*
|
|
21
|
+
* Adding a new app: add an entry here. The url shape is conventional:
|
|
22
|
+
* - productHref: /apps/<slug>
|
|
23
|
+
* - docsHref: https://docs.conduction.nl/<slug>
|
|
24
|
+
* - academyHref: /academy?app=<slug>
|
|
25
|
+
* Override any of the three when an app deviates from the convention
|
|
26
|
+
* (none today; the convention holds).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export const APPS_REGISTRY = {
|
|
30
|
+
opencatalogi: {slug: 'opencatalogi', name: 'OpenCatalogi', productHref: '/apps/opencatalogi', docsHref: 'https://docs.conduction.nl/opencatalogi', academyHref: '/academy?app=opencatalogi'},
|
|
31
|
+
openregister: {slug: 'openregister', name: 'OpenRegister', productHref: '/apps/openregister', docsHref: 'https://docs.conduction.nl/openregister', academyHref: '/academy?app=openregister'},
|
|
32
|
+
openconnector: {slug: 'openconnector', name: 'OpenConnector', productHref: '/apps/openconnector', docsHref: 'https://docs.conduction.nl/openconnector', academyHref: '/academy?app=openconnector'},
|
|
33
|
+
docudesk: {slug: 'docudesk', name: 'DocuDesk', productHref: '/apps/docudesk', docsHref: 'https://docs.conduction.nl/docudesk', academyHref: '/academy?app=docudesk'},
|
|
34
|
+
mydash: {slug: 'mydash', name: 'MyDash', productHref: '/apps/mydash', docsHref: 'https://docs.conduction.nl/mydash', academyHref: '/academy?app=mydash'},
|
|
35
|
+
openwoo: {slug: 'openwoo', name: 'OpenWoo', productHref: '/apps/openwoo', docsHref: 'https://docs.conduction.nl/openwoo', academyHref: '/academy?app=openwoo'},
|
|
36
|
+
zaakafhandelapp: {slug: 'zaakafhandelapp', name: 'ZaakAfhandelApp', productHref: '/apps/zaakafhandelapp', docsHref: 'https://docs.conduction.nl/zaakafhandelapp', academyHref: '/academy?app=zaakafhandelapp'},
|
|
37
|
+
pipelinq: {slug: 'pipelinq', name: 'PipelinQ', productHref: '/apps/pipelinq', docsHref: 'https://docs.conduction.nl/pipelinq', academyHref: '/academy?app=pipelinq'},
|
|
38
|
+
procest: {slug: 'procest', name: 'Procest', productHref: '/apps/procest', docsHref: 'https://docs.conduction.nl/procest', academyHref: '/academy?app=procest'},
|
|
39
|
+
decidesk: {slug: 'decidesk', name: 'DeciDesk', productHref: '/apps/decidesk', docsHref: 'https://docs.conduction.nl/decidesk', academyHref: '/academy?app=decidesk'},
|
|
40
|
+
softwarecatalog: {slug: 'softwarecatalog', name: 'SoftwareCatalog', productHref: '/apps/softwarecatalog', docsHref: 'https://docs.conduction.nl/softwarecatalog', academyHref: '/academy?app=softwarecatalog'},
|
|
41
|
+
larpingapp: {slug: 'larpingapp', name: 'LarpingApp', productHref: '/apps/larpingapp', docsHref: 'https://docs.conduction.nl/larpingapp', academyHref: '/academy?app=larpingapp'},
|
|
42
|
+
nldesign: {slug: 'nldesign', name: 'NLDesign', productHref: '/apps/nldesign', docsHref: 'https://docs.conduction.nl/nldesign', academyHref: '/academy?app=nldesign'},
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const APP_SLUGS = Object.keys(APPS_REGISTRY);
|
|
46
|
+
|
|
47
|
+
/** Build a label map keyed by slug, suitable for <ContentTypeFilter labels=…/>. */
|
|
48
|
+
export const APP_LABELS = APP_SLUGS.reduce((acc, slug) => {
|
|
49
|
+
acc[slug] = APPS_REGISTRY[slug].name;
|
|
50
|
+
return acc;
|
|
51
|
+
}, {});
|
|
52
|
+
|
|
53
|
+
/** Resolve a slug to its registry entry; returns undefined for unknown slugs. */
|
|
54
|
+
export function getApp(slug) {
|
|
55
|
+
return APPS_REGISTRY[slug];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Resolve an array of slugs, dropping any that aren't in the registry. */
|
|
59
|
+
export function getApps(slugs = []) {
|
|
60
|
+
return slugs.map(getApp).filter(Boolean);
|
|
61
|
+
}
|
package/src/index.js
CHANGED
|
@@ -86,8 +86,8 @@ const baseFooterLinks = () => [
|
|
|
86
86
|
{
|
|
87
87
|
title: 'Documentatie',
|
|
88
88
|
items: [
|
|
89
|
-
{ label: 'Brand book', href: 'https://
|
|
90
|
-
{ label: 'Diagram set', href: 'https://
|
|
89
|
+
{ label: 'Brand book', href: 'https://identity.conduction.nl/' },
|
|
90
|
+
{ label: 'Diagram set', href: 'https://identity.conduction.nl/diagrams/' },
|
|
91
91
|
],
|
|
92
92
|
},
|
|
93
93
|
];
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Brand DocItem/Footer swizzle.
|
|
3
|
+
*
|
|
4
|
+
* Wraps Docusaurus's default doc-item footer (tags row + edit-meta
|
|
5
|
+
* row) and appends a Conduction <AppCrossLinks/> block so every
|
|
6
|
+
* documentation page on docs.conduction.nl/<app> finishes with a
|
|
7
|
+
* cross-link to the product page and the academy filter.
|
|
8
|
+
*
|
|
9
|
+
* Activation is per-site, opt-in: a docs site enables the block by
|
|
10
|
+
* adding to its docusaurus.config.js:
|
|
11
|
+
*
|
|
12
|
+
* themeConfig: {
|
|
13
|
+
* conduction: {
|
|
14
|
+
* appId: 'opencatalogi'
|
|
15
|
+
* },
|
|
16
|
+
* ...
|
|
17
|
+
* }
|
|
18
|
+
*
|
|
19
|
+
* Apps that don't set `themeConfig.conduction.appId` keep the default
|
|
20
|
+
* Docusaurus footer behaviour, untouched.
|
|
21
|
+
*
|
|
22
|
+
* Per-page opt-out: a doc page can suppress the block via frontmatter
|
|
23
|
+
*
|
|
24
|
+
* ---
|
|
25
|
+
* hide_app_cross_links: true
|
|
26
|
+
* ---
|
|
27
|
+
*
|
|
28
|
+
* which is useful for changelogs, license pages, or other docs that
|
|
29
|
+
* shouldn't end with a cross-link.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import React from 'react';
|
|
33
|
+
import clsx from 'clsx';
|
|
34
|
+
import {ThemeClassNames} from '@docusaurus/theme-common';
|
|
35
|
+
import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
|
|
36
|
+
import {useDoc} from '@docusaurus/plugin-content-docs/client';
|
|
37
|
+
import TagsListInline from '@theme/TagsListInline';
|
|
38
|
+
import EditMetaRow from '@theme/EditMetaRow';
|
|
39
|
+
import {AppCrossLinks} from '@conduction/docusaurus-preset/components';
|
|
40
|
+
import {APPS_REGISTRY} from '@conduction/docusaurus-preset/data/apps-registry';
|
|
41
|
+
|
|
42
|
+
export default function DocItemFooter() {
|
|
43
|
+
const {siteConfig} = useDocusaurusContext();
|
|
44
|
+
const {metadata, frontMatter} = useDoc();
|
|
45
|
+
const {editUrl, lastUpdatedAt, lastUpdatedBy, tags} = metadata;
|
|
46
|
+
|
|
47
|
+
/* Per-page opt-out and per-page override (`apps:` in frontmatter
|
|
48
|
+
wins over the site-wide appId). Lets a single doc page point at
|
|
49
|
+
a different app without touching siteConfig. */
|
|
50
|
+
const hide = !!frontMatter?.hide_app_cross_links;
|
|
51
|
+
const fmApps = Array.isArray(frontMatter?.apps) ? frontMatter.apps : null;
|
|
52
|
+
const siteAppId = siteConfig?.themeConfig?.conduction?.appId;
|
|
53
|
+
const apps = (fmApps && fmApps.length > 0)
|
|
54
|
+
? fmApps
|
|
55
|
+
: (siteAppId ? [siteAppId] : []);
|
|
56
|
+
const knownApps = apps.filter((slug) => APPS_REGISTRY[slug]);
|
|
57
|
+
|
|
58
|
+
const canDisplayTagsRow = tags.length > 0;
|
|
59
|
+
const canDisplayEditMetaRow = !!(editUrl || lastUpdatedAt || lastUpdatedBy);
|
|
60
|
+
const canDisplayCrossLinks = !hide && knownApps.length > 0;
|
|
61
|
+
|
|
62
|
+
const canDisplayFooter = canDisplayTagsRow || canDisplayEditMetaRow || canDisplayCrossLinks;
|
|
63
|
+
if (!canDisplayFooter) return null;
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<footer
|
|
67
|
+
className={clsx(ThemeClassNames.docs.docFooter, 'docusaurus-mt-lg')}>
|
|
68
|
+
{canDisplayTagsRow && (
|
|
69
|
+
<div
|
|
70
|
+
className={clsx(
|
|
71
|
+
'row margin-top--sm',
|
|
72
|
+
ThemeClassNames.docs.docFooterTagsRow,
|
|
73
|
+
)}>
|
|
74
|
+
<div className="col">
|
|
75
|
+
<TagsListInline tags={tags} />
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
{canDisplayEditMetaRow && (
|
|
80
|
+
<EditMetaRow
|
|
81
|
+
className={clsx(
|
|
82
|
+
'margin-top--sm',
|
|
83
|
+
ThemeClassNames.docs.docFooterEditMetaRow,
|
|
84
|
+
)}
|
|
85
|
+
editUrl={editUrl}
|
|
86
|
+
lastUpdatedAt={lastUpdatedAt}
|
|
87
|
+
lastUpdatedBy={lastUpdatedBy}
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
{canDisplayCrossLinks && (
|
|
91
|
+
<div className="margin-top--lg">
|
|
92
|
+
<AppCrossLinks
|
|
93
|
+
variant="inline"
|
|
94
|
+
apps={knownApps}
|
|
95
|
+
surface="docs"
|
|
96
|
+
heading={knownApps.length === 1
|
|
97
|
+
? 'About this app'
|
|
98
|
+
: 'About these apps'}
|
|
99
|
+
/>
|
|
100
|
+
</div>
|
|
101
|
+
)}
|
|
102
|
+
</footer>
|
|
103
|
+
);
|
|
104
|
+
}
|