_includes/relatedresources/RelatedResources.11ty.tsx (63 lines of code) (raw):
import { Resource } from "../../src/ResourceModels";
import ListingSection from "../pageelements/ListingSection.11ty";
import { Fragment } from "jsx-async-runtime/jsx-dev-runtime";
// The upper limit on the number of items to show
const ITEM_LIMIT = 3;
export type RelatedItemsProps = {
currentResource: Resource;
items: Resource[];
};
export type FilterItemsProps = {
currentResource: Resource;
items: Resource[];
};
type ScoredItem = {
item: Resource;
score: number;
};
export function filterResources({
currentResource,
items,
}: FilterItemsProps): Resource[] {
const currentTopics = currentResource.topics;
let scoredItems: ScoredItem[] = items
// Never include the current resource as a related resource
.filter((item) => item.url !== currentResource.url)
// Check channels
.filter((item) => {
if (!currentResource.channel) {
// This request didn't ask for a channel
return true;
} else {
if (item.channel === undefined) {
// Allow resources outside a channel
return true;
}
// Check if this item's channel matches this resource's channel
return item.channel === currentResource.channel;
}
})
// Now get a tuple of (intersectionCount, resource) to "score" the
// overlap between the tags
.map((resource) => {
// currentResource and/or this item might not have a topics field
let score: number = 0;
if (currentTopics && currentTopics.length) {
score = currentTopics.filter((x) => {
return resource.topics && resource.topics.includes(x);
}).length;
}
return { score, item: resource };
});
// Reverse sort the scored items
scoredItems.sort((a, b) => b.score - a.score);
// Unpack just the resource
return scoredItems.map((scoredItem) => scoredItem.item).slice(0, ITEM_LIMIT);
}
const RelatedResources = ({
currentResource,
items,
}: RelatedItemsProps): JSX.Element => {
const filteredResources = filterResources({ currentResource, items });
if (filteredResources.length) {
return (
<ListingSection
title={`Related Resources`}
resources={filteredResources}
includeCardFooter={false}
sectionExtraClass={"has-background-grey-lighter"}
/>
);
}
return <Fragment></Fragment>;
};
export default RelatedResources;