Skip to content

Commit 29d35f5

Browse files
authored
Merge pull request #339 from wpengine/feat-recomendations
feat: Implement recommendations for docs
2 parents 0364966 + 834c76f commit 29d35f5

24 files changed

+461
-239
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
"@faustwp/cli": "^3.2.3",
2626
"@faustwp/core": "^3.2.3",
2727
"@headlessui/react": "^2.2.4",
28-
"@heroicons/react": "^2.2.0",
29-
"@icons-pack/react-simple-icons": "^13.1.0",
3028
"@jsdevtools/rehype-url-inspector": "^2.0.2",
3129
"@next/third-parties": "^15.3.4",
3230
"@octokit/core": "^7.0.2",
@@ -44,6 +42,8 @@
4442
"next-sitemap": "^4.2.3",
4543
"react": "^19.1.0",
4644
"react-dom": "^19.1.0",
45+
"react-icons": "^5.5.0",
46+
"react-intersection-observer": "^9.16.0",
4747
"rehype-callouts": "^2.1.0",
4848
"rehype-external-links": "^3.0.0",
4949
"rehype-pretty-code": "^0.14.1",

pnpm-lock.yaml

Lines changed: 30 additions & 24 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

scripts/smart-search.mjs

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { hash } from "node:crypto";
21
import { env, exit } from "node:process";
32
import { getTextContentFromMd } from "../src/lib/remark-parsing.mjs";
43
import {
54
getAllDocMeta,
65
getRawDocContent,
76
getDocUriFromPath,
7+
generateDocIdFromUri,
88
} from "../src/lib/remote-mdx-files.mjs";
9+
import { smartSearchConfig } from "../src/lib/smart-search.mjs";
910

1011
const {
1112
NEXT_PUBLIC_SEARCH_ENDPOINT: endpoint,
@@ -24,6 +25,9 @@ async function main() {
2425
console.log("Docs Pages collected for indexing:", pages.length);
2526

2627
await deleteOldDocs();
28+
29+
await setSearchConfig();
30+
2731
await sendPagesToEndpoint(pages);
2832
} catch (error) {
2933
console.error("Error in smartSearchPlugin:", error);
@@ -36,10 +40,10 @@ async function main() {
3640
* @typedef {object} Page
3741
* @property {string} id //The unique identifier of the document.
3842
* @property {object} data //The data to be indexed.
39-
* @property {string} data.title //The title of the document.
40-
* @property {string} data.content //The text content of the document.
41-
* @property {string} data.path //A relative path to the document on the internet.
42-
* @property {string} data.content_type // The type of content. Always "mdx_doc".
43+
* @property {string} data.post_title //The title of the document.
44+
* @property {string} data.post_content //The text content of the document.
45+
* @property {string} data.post_url //A relative path to the document on the internet.
46+
* @property {string} data.post_type // The type of content. Always "mdx_doc".
4347
* @returns Page[]
4448
*/
4549
async function collectPages() {
@@ -53,16 +57,16 @@ async function collectPages() {
5357

5458
const cleanedPath = getDocUriFromPath(entry.path);
5559

56-
const id = hash("sha-1", `mdx:${cleanedPath}`);
60+
const id = generateDocIdFromUri(cleanedPath);
5761

5862
pages.push({
5963
id,
6064
data: {
61-
title: parsedContent.data.matter.title,
65+
post_title: parsedContent.data.matter.title,
6266
description: parsedContent.data.matter.description,
63-
content: parsedContent.value,
64-
path: cleanedPath,
65-
content_type: "mdx_doc",
67+
post_content: parsedContent.value,
68+
post_url: cleanedPath,
69+
post_type: "mdx_doc",
6670
},
6771
});
6872
}
@@ -126,7 +130,7 @@ async function deleteOldDocs() {
126130
const response = await graphql({
127131
query: queryDocuments,
128132
variables: {
129-
query: 'content_type:"mdx_doc"',
133+
query: 'post_type:"mdx_doc"',
130134
limit: 10,
131135
offset: totalCollected,
132136
},
@@ -208,4 +212,34 @@ async function sendPagesToEndpoint(pages) {
208212
}
209213
}
210214

215+
const searchConfigMutation = `
216+
mutation setSemanticSearchConfiguration($fields: [String!]!, $chunking: ChunkingConfig!) {
217+
config {
218+
semanticSearch(fields: $fields, chunking: $chunking) {
219+
type
220+
fields
221+
chunking {
222+
enabled
223+
}
224+
}
225+
}
226+
}`;
227+
228+
async function setSearchConfig() {
229+
const variables = {
230+
fields: smartSearchConfig.fields,
231+
chunking: smartSearchConfig.chunking,
232+
};
233+
234+
try {
235+
const response = await graphql({ query: searchConfigMutation, variables });
236+
console.log(
237+
"Search configuration updated successfully.",
238+
JSON.stringify(response.data.config.semanticSearch, undefined, 2),
239+
);
240+
} catch (error) {
241+
console.error("Error updating search configuration:", error);
242+
}
243+
}
244+
211245
await main();

src/components/blog-breadcrumbs.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChevronRightIcon } from "@heroicons/react/24/outline";
1+
import { HiOutlineChevronRight } from "react-icons/hi2";
22
import Link from "@/components/link";
33

44
export default function BlogBreadcrumbs({ currentPostTitle }) {
@@ -13,7 +13,7 @@ export default function BlogBreadcrumbs({ currentPostTitle }) {
1313
>
1414
<span>Blog</span>
1515
<span>
16-
<ChevronRightIcon className="inline h-5 w-10 pl-4 align-text-top" />
16+
<HiOutlineChevronRight className="inline h-5 w-10 pl-4 align-text-top" />
1717
</span>
1818
</Link>
1919

src/components/doc-type-tag.jsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { classNames } from "@/utils/strings";
2+
3+
export default function DocTypeTag(type) {
4+
if (!type || !type.type) {
5+
return;
6+
}
7+
8+
const theType = type.type || type;
9+
10+
const config = {
11+
name: "Ext",
12+
className: "bg-gray-500",
13+
};
14+
15+
if (theType === "mdx_doc") {
16+
config.name = "Doc";
17+
config.className = "bg-teal-800";
18+
} else if (theType === "post" || theType === "page") {
19+
config.name = "Blog";
20+
config.className = "bg-purple-600";
21+
}
22+
23+
return (
24+
<span
25+
className={classNames(
26+
"mr-2 inline-block rounded px-2 py-1 text-xs font-semibold text-gray-200 uppercase",
27+
config.className,
28+
)}
29+
>
30+
{config.name}
31+
</span>
32+
);
33+
}

src/components/docs-breadcrumbs.jsx

Lines changed: 38 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import { ChevronRightIcon } from "@heroicons/react/24/outline";
21
import { useRouter } from "next/router";
2+
import { HiOutlineChevronRight } from "react-icons/hi2";
33
import Link from "@/components/link";
44
import { sendSelectItemEvent } from "@/lib/analytics.mjs";
5-
import { normalizeHref } from "@/utils/strings";
5+
import { classNames, normalizeHref } from "@/utils/strings";
66

7-
export default function DocsBreadcrumbs({ routes }) {
7+
export default function DocsBreadcrumbs({ routes, className }) {
88
const generateBreadcrumbs = (breadcrumbRoutes, currentRoute) => {
99
const breadcrumbs = [];
1010

@@ -48,37 +48,40 @@ export default function DocsBreadcrumbs({ routes }) {
4848
}
4949

5050
return (
51-
<div className="mt-4 mb-7 md:mt-2 md:mb-10">
52-
<div className="flex flex-wrap items-center gap-2 text-sm">
53-
{breadcrumbLinks.map((breadcrumb, index) =>
54-
normalizeHref(breadcrumb.route) === normalizeHref(currentPath) ? (
55-
<span key={index}>{breadcrumb.title}</span>
56-
) : (
57-
<Link
58-
key={index}
59-
className="font-normal text-gray-400 no-underline transition duration-200 ease-in hover:text-white focus:text-white"
60-
href={breadcrumb.route}
61-
noDefaultStyles
62-
aria-label={breadcrumb.title}
63-
onClick={() => {
64-
sendSelectItemEvent({
65-
list: { id: "docs_breadcrumbs", name: "Docs Breadcrumbs" },
66-
item: {
67-
item_id: breadcrumb.route,
68-
item_name: breadcrumb.title,
69-
item_category: "mdx_doc",
70-
},
71-
});
72-
}}
73-
>
74-
<span>{breadcrumb.title}</span>
75-
<span>
76-
<ChevronRightIcon className="inline h-5 w-10 pl-4 align-text-top" />
77-
</span>
78-
</Link>
79-
),
80-
)}
81-
</div>
82-
</div>
51+
<nav
52+
className={classNames(
53+
"flex flex-wrap items-center gap-2 text-sm",
54+
className,
55+
)}
56+
>
57+
{breadcrumbLinks.map((breadcrumb, index) =>
58+
normalizeHref(breadcrumb.route) === normalizeHref(currentPath) ? (
59+
<span key={index}>{breadcrumb.title}</span>
60+
) : (
61+
<Link
62+
key={index}
63+
className="font-normal text-gray-400 no-underline transition duration-200 ease-in hover:text-white focus:text-white"
64+
href={breadcrumb.route}
65+
noDefaultStyles
66+
aria-label={breadcrumb.title}
67+
onClick={() => {
68+
sendSelectItemEvent({
69+
list: { id: "docs_breadcrumbs", name: "Docs Breadcrumbs" },
70+
item: {
71+
item_id: breadcrumb.route,
72+
item_name: breadcrumb.title,
73+
item_category: "mdx_doc",
74+
},
75+
});
76+
}}
77+
>
78+
<span>{breadcrumb.title}</span>
79+
<span>
80+
<HiOutlineChevronRight className="inline h-5 w-10 pl-4 align-text-top" />
81+
</span>
82+
</Link>
83+
),
84+
)}
85+
</nav>
8386
);
8487
}

0 commit comments

Comments
 (0)