Skip to content

Commit 939d603

Browse files
committed
feat: migrating content from docs.coveo.com to typedoc
1 parent f219d75 commit 939d603

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+4753
-90
lines changed

.cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"metadatas",
8888
"Modernizr",
8989
"myapp",
90+
"coveoua",
9091
"mycoveocloudorganizationg",
9192
"mycoveoorganization",
9293
"mycoveoorganizationg",

packages/documentation/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# @coveo/documentation
2+
3+
This is a typedoc plugin. It ensure that the documentation generated adheres to our styling guidelines.
4+
5+
It is used by
6+
- `@coveo/headless`
7+
8+
## Navigation
9+
This plugin also deviates from the standard typedoc navigation patterns. It allows for top level documents that are ungrouped and it removes the standard
10+
`Documents` tab for ungrouped documents. It also allows for groups to have a mixed of documents and folders based on Category, as opposed to forcing
11+
each document in a group that has categories to _be_ categorized or otherwise lumped together into an `Others` folder.
12+
13+
The sorting of the navigation be specified, both for the top level and optionally by for groups.
14+
15+
NOTE: when specifying the sorting for groups, the key must be in lowercase not the actual casing of the document title.

packages/documentation/lib/formatTypeDocToolbar.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function formatTypeDocToolbar() {
1+
export const formatTypeDocToolbar = () => {
22
document.addEventListener('DOMContentLoaded', () => {
33
const header = document.getElementsByTagName('header')[0] as HTMLElement;
44
if (header) {
@@ -12,4 +12,4 @@ export function formatTypeDocToolbar() {
1212
typedocThemeSelector.style.display = 'none';
1313
}
1414
});
15-
}
15+
};
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import {normalize} from './normalize.js';
2+
import type {TNavNode} from './types.js';
3+
4+
/**
5+
* Hoists any child whose title equals `fallbackCategory` so its children are promoted
6+
* to the *parent* level, at the exact position where the bucket appeared.
7+
* Runs depth-first so descendants are processed as well.
8+
*/
9+
export const hoistOtherCategoryInNav = (
10+
root: TNavNode,
11+
fallbackCategory: string
12+
) => {
13+
if (!root) return;
14+
15+
const stack: TNavNode[] = [root];
16+
while (stack.length) {
17+
const node = stack.pop()!;
18+
const kids = node.children;
19+
if (!Array.isArray(kids) || kids.length === 0) continue;
20+
21+
const nextChildren: TNavNode[] = [];
22+
for (const child of kids) {
23+
const title = typeof child.text === 'string' ? child.text : undefined;
24+
if (title && normalize(title) === normalize(fallbackCategory)) {
25+
if (Array.isArray(child.children) && child.children.length) {
26+
// Promote grandchildren to the parent's level at this position
27+
nextChildren.push(...child.children);
28+
}
29+
// Drop the bucket itself
30+
} else {
31+
nextChildren.push(child);
32+
}
33+
}
34+
node.children = nextChildren;
35+
36+
// Recurse
37+
for (const c of node.children) stack.push(c);
38+
}
39+
};
40+
41+
/**
42+
* Top-level helper for themes that return navigation as an array of nodes.
43+
* If an element named like the fallback group exists at the root, its children are
44+
* spliced into the root array at the same index (i.e., promoted to the top level),
45+
* and the bucket is removed. Also applies recursive hoisting within all nodes.
46+
*/
47+
export const hoistOtherCategoryInArray = (
48+
rootItems: TNavNode[],
49+
fallbackCategory: string,
50+
topLevelGroup: string
51+
) => {
52+
if (!Array.isArray(rootItems) || rootItems.length === 0) return;
53+
54+
// First pass: recursively hoist 'Other' within each item
55+
for (const item of rootItems) {
56+
hoistOtherCategoryInNav(item, fallbackCategory);
57+
}
58+
59+
// Second pass: hoist any top-level bucket matching either fallbackCategory ('Other')
60+
// or the requested top-level group (e.g., 'Documents').
61+
let i = 0;
62+
while (i < rootItems.length) {
63+
const item = rootItems[i];
64+
const title = typeof item.text === 'string' ? item.text : undefined;
65+
if (
66+
title &&
67+
(normalize(title) === normalize(fallbackCategory) ||
68+
normalize(title) === normalize(topLevelGroup))
69+
) {
70+
const replacement = Array.isArray(item.children) ? item.children : [];
71+
// Replace the bucket node with its children (promote to top level)
72+
rootItems.splice(i, 1, ...replacement);
73+
// Continue at same index to handle multiple merges
74+
continue;
75+
}
76+
i++;
77+
}
78+
};

packages/documentation/lib/index.tsx

Lines changed: 165 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,168 @@
11
import {cpSync} from 'node:fs';
22
import {dirname, resolve} from 'node:path';
33
import {fileURLToPath} from 'node:url';
4-
// following docs https://typedoc.org/guides/development/#plugins
5-
// eslint-disable-next-line n/no-unpublished-import
6-
import {type Application, Converter, JSX, RendererEvent} from 'typedoc';
4+
import {
5+
type Application,
6+
Converter,
7+
type DefaultTheme,
8+
type DocumentReflection,
9+
JSX,
10+
KindRouter,
11+
type Models,
12+
type NavigationElement,
13+
ParameterType,
14+
type ProjectReflection,
15+
RendererEvent,
16+
} from 'typedoc';
717
import {formatTypeDocToolbar} from './formatTypeDocToolbar.js';
18+
import {hoistOtherCategoryInArray, hoistOtherCategoryInNav} from './hoist.js';
819
import {insertAtomicSearchBox} from './insertAtomicSearchBox.js';
920
import {insertBetaNote} from './insertBetaNote.js';
1021
import {insertCustomComments} from './insertCustomComments.js';
1122
import {insertMetaTags} from './insertMetaTags.js';
1223
import {insertSiteHeaderBar} from './insertSiteHeaderBar.js';
24+
import {applyTopLevelRenameArray} from './renaming.js';
25+
import {
26+
applyNestedOrderingArray,
27+
applyNestedOrderingNode,
28+
applyTopLevelOrderingArray,
29+
applyTopLevelOrderingNode,
30+
} from './sortNodes.js';
31+
import type {TFrontMatter, TNavNode} from './types.js';
32+
33+
class KebabRouter extends KindRouter {
34+
// Optional: keep .html (default) or change if you want
35+
extension = '.html';
36+
37+
protected getIdealBaseName(refl: Models.Reflection): string {
38+
const name = refl.getFullName?.() ?? refl.name ?? '';
39+
if (!(refl as DocumentReflection)?.frontmatter?.slug)
40+
return this.getUrlSafeName(name);
41+
const {slug} = (refl as DocumentReflection).frontmatter as TFrontMatter;
42+
43+
return `documents/${slug}`;
44+
}
45+
}
1346

1447
const __dirname = dirname(fileURLToPath(import.meta.url));
1548

1649
/**
1750
* Called by TypeDoc when loaded as a plugin.
1851
*/
19-
export function load(app: Application) {
52+
export const load = (app: Application) => {
53+
app.options.addDeclaration({
54+
name: 'hoistOther.fallbackCategory',
55+
help: "Name of the fallback category to hoist (defaults to defaultCategory or 'Other').",
56+
type: ParameterType.String,
57+
});
58+
59+
app.options.addDeclaration({
60+
name: 'hoistOther.topLevelGroup',
61+
help: "Name of the top-level group whose children should be promoted to root (default 'Documents').",
62+
type: ParameterType.String,
63+
});
64+
65+
app.options.addDeclaration({
66+
name: 'hoistOther.topLevelOrder',
67+
help: 'An array to sort the top level nav by.',
68+
type: ParameterType.Array,
69+
});
70+
71+
app.options.addDeclaration({
72+
name: 'hoistOther.nestedOrder',
73+
help: "Object mapping parent title -> ordering array for its children. Use '*' for a default. If omitted, children are sorted alphabetically.",
74+
type: ParameterType.Mixed,
75+
});
76+
77+
app.options.addDeclaration({
78+
name: 'hoistOther.renameModulesTo',
79+
help: "If set, rename any top-level group titled 'Modules' to this string.",
80+
type: ParameterType.String,
81+
});
82+
83+
const originalMethodName = 'getNavigation';
84+
let originalMethod: (
85+
project: ProjectReflection
86+
) => NavigationElement[] | null = null;
87+
app.renderer.on('beginRender', () => {
88+
const theme = app.renderer.theme as DefaultTheme | undefined;
89+
if (!theme) return;
90+
91+
originalMethod = theme.getNavigation;
92+
93+
if (!originalMethod) return;
94+
95+
const opts = app.options;
96+
const fallback =
97+
(opts.getValue('hoistOther.fallbackCategory') as string) ||
98+
(opts.getValue('defaultCategory') as string) ||
99+
'Other';
100+
101+
const topLevelGroup =
102+
(opts.getValue('hoistOther.topLevelGroup') as string) || 'Documents';
103+
104+
const topLevelOrder =
105+
(opts.getValue('hoistOther.topLevelOrder') as string[] | undefined) ||
106+
undefined;
107+
108+
let nestedOrder = opts.getValue('hoistOther.nestedOrder') as
109+
| Record<string, string[]>
110+
| string
111+
| undefined;
112+
if (typeof nestedOrder === 'string') {
113+
try {
114+
nestedOrder = JSON.parse(nestedOrder);
115+
} catch {}
116+
}
117+
118+
const renameModulesTo =
119+
(opts.getValue('hoistOther.renameModulesTo') as string | undefined) ||
120+
undefined;
121+
122+
const typedNestedOrder = nestedOrder as Record<string, string[]>;
123+
124+
theme.getNavigation = function wrappedNavigation(
125+
this: unknown,
126+
...args: unknown[]
127+
) {
128+
const nav = originalMethod!.apply(this, args);
129+
130+
// The nav shape can be an array of nodes or a single root with children
131+
if (Array.isArray(nav)) {
132+
if (renameModulesTo?.trim()) {
133+
applyTopLevelRenameArray(nav, 'Modules', renameModulesTo.trim());
134+
}
135+
136+
hoistOtherCategoryInArray(nav as TNavNode[], fallback, topLevelGroup);
137+
138+
if (topLevelOrder?.length) {
139+
applyTopLevelOrderingArray(nav as TNavNode[], topLevelOrder);
140+
}
141+
142+
applyNestedOrderingArray(nav as TNavNode[], typedNestedOrder);
143+
} else if (nav && typeof nav === 'object') {
144+
if (renameModulesTo?.trim() && Array.isArray(nav.children)) {
145+
applyTopLevelRenameArray(
146+
nav.children,
147+
'Modules',
148+
renameModulesTo.trim()
149+
);
150+
}
151+
152+
hoistOtherCategoryInNav(nav as TNavNode, fallback);
153+
if (
154+
(nav as TNavNode).children &&
155+
topLevelOrder &&
156+
topLevelOrder.length
157+
) {
158+
applyTopLevelOrderingNode(nav as TNavNode, topLevelOrder);
159+
}
160+
applyNestedOrderingNode(nav as TNavNode, typedNestedOrder);
161+
}
162+
return nav;
163+
};
164+
});
165+
20166
// Need the Meta Tags to be inserted first, or it causes issues with the navigation sidebar
21167
app.renderer.hooks.on('head.begin', () => (
22168
<>
@@ -119,14 +265,8 @@ export function load(app: Application) {
119265
</>
120266
));
121267

122-
const baseAssetsPath = '../../documentation/assets';
123-
124-
const createFileCopyEntry = (sourcePath: string) => ({
125-
from: resolve(__dirname, `${baseAssetsPath}/${sourcePath}`),
126-
to: resolve(app.options.getValue('out'), `assets/${sourcePath}`),
127-
});
128-
129-
const onRenderEnd = () => {
268+
app.renderer.on(RendererEvent.END, () => {
269+
const baseAssetsPath = '../../documentation/assets';
130270
const filesToCopy = [
131271
'css/docs-style.css',
132272
'css/main-new.css',
@@ -145,19 +285,28 @@ export function load(app: Application) {
145285
];
146286

147287
filesToCopy.forEach((filePath) => {
148-
const file = createFileCopyEntry(filePath);
288+
const file = {
289+
from: resolve(__dirname, `${baseAssetsPath}/${filePath}`),
290+
to: resolve(app.options.getValue('out'), `assets/${filePath}`),
291+
};
149292
cpSync(file.from, file.to);
150293
});
151294

152295
const darkModeJs = {
153296
from: resolve(__dirname, '../../documentation/dist/dark-mode.js'),
154297
to: resolve(app.options.getValue('out'), 'assets/vars/dark-mode.js'),
155298
};
299+
// Restore original to avoid side effects
300+
const theme = app.renderer.theme as DefaultTheme | undefined;
301+
if (theme && originalMethodName && originalMethod) {
302+
theme[originalMethodName] = originalMethod;
303+
}
304+
originalMethod = null;
156305

157306
cpSync(darkModeJs.from, darkModeJs.to);
158-
};
307+
});
159308

160-
app.renderer.on(RendererEvent.END, onRenderEnd);
309+
app.renderer.defineRouter('kebab', KebabRouter);
161310

162311
app.converter.on(Converter.EVENT_CREATE_DECLARATION, insertCustomComments);
163-
}
312+
};

packages/documentation/lib/insertAtomicSearchBox.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ declare global {
1313
}
1414
}
1515

16-
export function insertAtomicSearchBox() {
16+
export const insertAtomicSearchBox = () => {
1717
const areFunctionalCookiesEnabled = (): boolean => {
1818
return document.cookie
1919
.split('; ')
@@ -54,4 +54,4 @@ export function insertAtomicSearchBox() {
5454
})();
5555
}
5656
});
57-
}
57+
};

packages/documentation/lib/insertBetaNote.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function insertBetaNote() {
1+
export const insertBetaNote = () => {
22
document.addEventListener('DOMContentLoaded', () => {
33
const breadcrumbs = document.querySelector('ul.tsd-breadcrumb');
44
if (breadcrumbs) {
@@ -14,4 +14,4 @@ export function insertBetaNote() {
1414
}
1515
}
1616
});
17-
}
17+
};

packages/documentation/lib/insertCoveoLogo.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function insertCoveoLogo(imagePath: string) {
1+
export const insertCoveoLogo = (imagePath: string) => {
22
document.addEventListener('DOMContentLoaded', () => {
33
const toolbarContents = document.getElementsByClassName(
44
'tsd-toolbar-contents'
@@ -24,4 +24,4 @@ export function insertCoveoLogo(imagePath: string) {
2424
faviconLink.rel = 'icon';
2525
faviconLink.href = `${imagePath}/favicon.ico`;
2626
document.head.appendChild(faviconLink);
27-
}
27+
};

packages/documentation/lib/insertCustomComments.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const comments = [
1717
},
1818
];
1919

20+
// NOTE: cannot be converted into an arrow function `this`
2021
export function insertCustomComments(
2122
this: undefined,
2223
_ctx: Context,
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export function insertMetaTags() {
1+
export const insertMetaTags = () => {
22
const head = document.getElementsByTagName('head')[0];
33
if (head) {
44
head.innerHTML += `
@@ -8,4 +8,4 @@ export function insertMetaTags() {
88
<meta name="docsSiteBaseUrl" content="/en">
99
`;
1010
}
11-
}
11+
};

0 commit comments

Comments
 (0)