Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
96ac609
feat: implement remoendations API and rough UI for recomended posts
moonmeister Jun 24, 2025
925783a
refactor: remove prev/next links infavor of recommendations and rewor…
moonmeister Jun 25, 2025
697e794
refactor: search and recommendations to share code and return consist…
moonmeister Jun 25, 2025
afdb9ae
refactor: move mdx doc id generation into shared file
moonmeister Jun 25, 2025
0f5f518
feat: add mdx doc id generation to docs
moonmeister Jun 25, 2025
7fd17fa
refactor: cleanup recommendations/search endpoints
moonmeister Jun 25, 2025
d05d029
refactor: extract doc type tag in search for use elsewhere and make i…
moonmeister Jun 25, 2025
23b0e12
feat: fully implement recommendations UI and load on scroll
moonmeister Jun 25, 2025
d218c66
refactor: better collors
moonmeister Jun 25, 2025
73b83af
fix: build error
moonmeister Jun 25, 2025
e689155
style: cleanup some styles, change to link component
moonmeister Jun 26, 2025
2e2d0e0
refactor: switch to single icon package and move existing imports, ad…
moonmeister Jun 26, 2025
e6687d0
Merge branch 'main' into feat-recomendations
moonmeister Jul 1, 2025
2945671
refactor: cleanup console logs
moonmeister Jul 1, 2025
506388a
feat: add GA event to recommendations box
moonmeister Jul 1, 2025
706e91f
refactor: switch to more semantic aside
moonmeister Jul 1, 2025
ddd0fc4
refactor: non-fatal error handling
moonmeister Jul 1, 2025
d07f3cc
fix: add key to recommendations component to trigger rerender on navi…
moonmeister Jul 2, 2025
a538646
Merge branch 'main' into feat-recomendations
moonmeister Jul 2, 2025
a1f31bd
feat: enable hybrid search in search bar
moonmeister Jul 2, 2025
8a076a8
Merge branch 'main' into feat-recomendations
moonmeister Jul 8, 2025
fa648e4
wip: updating search to be more resilient
moonmeister Jul 10, 2025
f101c16
Merge branch 'main' into feat-recomendations
moonmeister Jul 10, 2025
d871f0f
fix: references to old field names
moonmeister Jul 10, 2025
68e208c
fix: remove extra console log
moonmeister Jul 10, 2025
870ef76
fix: inconrect post_type field name
moonmeister Jul 10, 2025
834c76f
fix: incorectly named variable
moonmeister Jul 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
"@faustwp/cli": "^3.2.3",
"@faustwp/core": "^3.2.3",
"@headlessui/react": "^2.2.4",
"@heroicons/react": "^2.2.0",
"@icons-pack/react-simple-icons": "^13.1.0",
"@jsdevtools/rehype-url-inspector": "^2.0.2",
"@next/third-parties": "^15.3.4",
"@octokit/core": "^7.0.2",
Expand All @@ -44,6 +42,8 @@
"next-sitemap": "^4.2.3",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"react-icons": "^5.5.0",
"react-intersection-observer": "^9.16.0",
"rehype-callouts": "^2.1.0",
"rehype-external-links": "^3.0.0",
"rehype-pretty-code": "^0.14.1",
Expand Down
54 changes: 30 additions & 24 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 45 additions & 11 deletions scripts/smart-search.mjs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { hash } from "node:crypto";
import { env, exit } from "node:process";
import { getTextContentFromMd } from "../src/lib/remark-parsing.mjs";
import {
getAllDocMeta,
getRawDocContent,
getDocUriFromPath,
generateDocIdFromUri,
} from "../src/lib/remote-mdx-files.mjs";
import { smartSearchConfig } from "../src/lib/smart-search.mjs";

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

await deleteOldDocs();

await setSearchConfig();

await sendPagesToEndpoint(pages);
} catch (error) {
console.error("Error in smartSearchPlugin:", error);
Expand All @@ -36,10 +40,10 @@ async function main() {
* @typedef {object} Page
* @property {string} id //The unique identifier of the document.
* @property {object} data //The data to be indexed.
* @property {string} data.title //The title of the document.
* @property {string} data.content //The text content of the document.
* @property {string} data.path //A relative path to the document on the internet.
* @property {string} data.content_type // The type of content. Always "mdx_doc".
* @property {string} data.post_title //The title of the document.
* @property {string} data.post_content //The text content of the document.
* @property {string} data.post_url //A relative path to the document on the internet.
* @property {string} data.post_type // The type of content. Always "mdx_doc".
* @returns Page[]
*/
async function collectPages() {
Expand All @@ -53,16 +57,16 @@ async function collectPages() {

const cleanedPath = getDocUriFromPath(entry.path);

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

pages.push({
id,
data: {
title: parsedContent.data.matter.title,
post_title: parsedContent.data.matter.title,
description: parsedContent.data.matter.description,
content: parsedContent.value,
path: cleanedPath,
content_type: "mdx_doc",
post_content: parsedContent.value,
post_url: cleanedPath,
post_type: "mdx_doc",
},
});
}
Expand Down Expand Up @@ -126,7 +130,7 @@ async function deleteOldDocs() {
const response = await graphql({
query: queryDocuments,
variables: {
query: 'content_type:"mdx_doc"',
query: 'post_type:"mdx_doc"',
limit: 10,
offset: totalCollected,
},
Expand Down Expand Up @@ -208,4 +212,34 @@ async function sendPagesToEndpoint(pages) {
}
}

const searchConfigMutation = `
mutation setSemanticSearchConfiguration($fields: [String!]!, $chunking: ChunkingConfig!) {
config {
semanticSearch(fields: $fields, chunking: $chunking) {
type
fields
chunking {
enabled
}
}
}
}`;

async function setSearchConfig() {
const variables = {
fields: smartSearchConfig.fields,
chunking: smartSearchConfig.chunking,
};

try {
const response = await graphql({ query: searchConfigMutation, variables });
console.log(
"Search configuration updated successfully.",
JSON.stringify(response.data.config.semanticSearch, undefined, 2),
);
} catch (error) {
console.error("Error updating search configuration:", error);
}
}

await main();
4 changes: 2 additions & 2 deletions src/components/blog-breadcrumbs.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ChevronRightIcon } from "@heroicons/react/24/outline";
import { HiOutlineChevronRight } from "react-icons/hi2";
import Link from "@/components/link";

export default function BlogBreadcrumbs({ currentPostTitle }) {
Expand All @@ -13,7 +13,7 @@ export default function BlogBreadcrumbs({ currentPostTitle }) {
>
<span>Blog</span>
<span>
<ChevronRightIcon className="inline h-5 w-10 pl-4 align-text-top" />
<HiOutlineChevronRight className="inline h-5 w-10 pl-4 align-text-top" />
</span>
</Link>

Expand Down
33 changes: 33 additions & 0 deletions src/components/doc-type-tag.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { classNames } from "@/utils/strings";

export default function DocTypeTag(type) {
if (!type || !type.type) {
return;
}

const theType = type.type || type;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems weird that the type could be nested one level (type.type) or it could just be the top-level type. Is there a way to make the format the same, or are they necessarily different?


const config = {
name: "Ext",
className: "bg-gray-500",
};

if (theType === "mdx_doc") {
config.name = "Doc";
config.className = "bg-teal-800";
} else if (theType === "post" || theType === "page") {
config.name = "Blog";
config.className = "bg-purple-600";
}

return (
<span
className={classNames(
"mr-2 inline-block rounded px-2 py-1 text-xs font-semibold text-gray-200 uppercase",
config.className,
)}
>
{config.name}
</span>
);
}
73 changes: 38 additions & 35 deletions src/components/docs-breadcrumbs.jsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { ChevronRightIcon } from "@heroicons/react/24/outline";
import { useRouter } from "next/router";
import { HiOutlineChevronRight } from "react-icons/hi2";
import Link from "@/components/link";
import { sendSelectItemEvent } from "@/lib/analytics.mjs";
import { normalizeHref } from "@/utils/strings";
import { classNames, normalizeHref } from "@/utils/strings";

export default function DocsBreadcrumbs({ routes }) {
export default function DocsBreadcrumbs({ routes, className }) {
const generateBreadcrumbs = (breadcrumbRoutes, currentRoute) => {
const breadcrumbs = [];

Expand Down Expand Up @@ -48,37 +48,40 @@ export default function DocsBreadcrumbs({ routes }) {
}

return (
<div className="mt-4 mb-7 md:mt-2 md:mb-10">
<div className="flex flex-wrap items-center gap-2 text-sm">
{breadcrumbLinks.map((breadcrumb, index) =>
normalizeHref(breadcrumb.route) === normalizeHref(currentPath) ? (
<span key={index}>{breadcrumb.title}</span>
) : (
<Link
key={index}
className="font-normal text-gray-400 no-underline transition duration-200 ease-in hover:text-white focus:text-white"
href={breadcrumb.route}
noDefaultStyles
aria-label={breadcrumb.title}
onClick={() => {
sendSelectItemEvent({
list: { id: "docs_breadcrumbs", name: "Docs Breadcrumbs" },
item: {
item_id: breadcrumb.route,
item_name: breadcrumb.title,
item_category: "mdx_doc",
},
});
}}
>
<span>{breadcrumb.title}</span>
<span>
<ChevronRightIcon className="inline h-5 w-10 pl-4 align-text-top" />
</span>
</Link>
),
)}
</div>
</div>
<nav
className={classNames(
"flex flex-wrap items-center gap-2 text-sm",
className,
)}
>
{breadcrumbLinks.map((breadcrumb, index) =>
normalizeHref(breadcrumb.route) === normalizeHref(currentPath) ? (
<span key={index}>{breadcrumb.title}</span>
) : (
<Link
key={index}
className="font-normal text-gray-400 no-underline transition duration-200 ease-in hover:text-white focus:text-white"
href={breadcrumb.route}
noDefaultStyles
aria-label={breadcrumb.title}
onClick={() => {
sendSelectItemEvent({
list: { id: "docs_breadcrumbs", name: "Docs Breadcrumbs" },
item: {
item_id: breadcrumb.route,
item_name: breadcrumb.title,
item_category: "mdx_doc",
},
});
}}
>
<span>{breadcrumb.title}</span>
<span>
<HiOutlineChevronRight className="inline h-5 w-10 pl-4 align-text-top" />
</span>
</Link>
),
)}
</nav>
);
}
Loading