Files
unicorn-utterances/src/utils/get-suggested-articles.ts
2023-07-24 12:15:14 -07:00

157 lines
4.3 KiB
TypeScript

import { PostInfo, Languages, ExtendedPostInfo } from "types/index";
import { getPostsByLang } from "./api";
/**
* Get 3 similar articles to suggest in sidebar.
* Base off of article series,and similar tags.
* If neither apply, simply grab latest articles
*
* However, they should take the precedence. If there are
* series articles, they should suggest higher than
* matching tags
*
* We check exactly how similar tags are in general. For example, given one
* post with 4 tags that match, and another post with only 2, the one with
* 4 tags will show above the one with 2.
*
* For suggested articles, get the articles only within
* 1 series order of each other.
*
* So, if we got "2", we could get:
* 1, 3, 4
*
* But not:
* 1, 3, 5
*
* Or, alternatively, if we got "3", we could get:
* 1, 2, 4
*
* But not:
* 1, 4, 5
*/
const howManySimilarBetween = <T>(arr1: T[], arr2: T[]): number => {
let match = 0;
for (const item of arr1) {
if (arr2.includes(item)) match++;
}
return match;
};
const getOrderRange = (arr: PostInfo[]) => {
return arr.reduce(
(prev, curr) => {
if (prev.smallest === null || prev.largest === null) {
return {
largest: curr,
smallest: curr,
};
}
if (curr.order! < prev.smallest.order!) {
prev.smallest = curr;
}
if (curr.order! > prev.largest.order!) {
prev.largest = curr;
}
return prev;
},
{
largest: null as PostInfo,
smallest: null as PostInfo,
},
);
};
export const getSuggestedArticles = (
postNode: ExtendedPostInfo,
lang: Languages,
) => {
const suggestedPosts = getPostsByLang(lang);
const extraSuggestedArticles: PostInfo[] = [];
const suggestedArticles: PostInfo[] = [];
const similarTags: Array<{
post: PostInfo;
howManyTagsSimilar: number;
}> = [];
for (const post of suggestedPosts) {
// Early "return" for value
if (suggestedArticles.length >= 3) break;
// Don't return the same article
if (post.slug === postNode.slug) continue;
if (!!post.collection && post.collection === postNode.collection) {
const { largest, smallest } =
getOrderRange([...suggestedArticles, postNode]) || {};
let newArticlePushed = false;
if (
largest &&
smallest &&
(post.order === smallest.order! - 1 ||
post.order === largest.order! + 1)
) {
suggestedArticles.push(post);
newArticlePushed = false;
}
/**
* Because we've just updated the `largest` and `smallest`, it's possible
* there's another match in our list of suggested articles. Go check
*
* This may seem bad to do a while loop here, but I promise that we'll
* never have a series longer than even, like, 20 articles. This is a massive
* improvement over looping through the entire list of articles.
*/
while (newArticlePushed) {
if (suggestedArticles.length >= 3) break;
if (extraSuggestedArticles.length === 0) break;
const { largest, smallest } = getOrderRange(suggestedArticles) || {};
for (const suggestedPost of extraSuggestedArticles) {
if (
suggestedPost.order === smallest.order! - 1 ||
suggestedPost.order === largest.order! + 1
) {
suggestedArticles.push(suggestedPost);
}
}
}
if (suggestedArticles.length >= 3) break;
extraSuggestedArticles.push(post);
}
const howManyTagsSimilar = howManySimilarBetween(
post.tags,
postNode.tags || [],
);
if (howManyTagsSimilar > 0) {
similarTags.push({ post, howManyTagsSimilar });
}
}
// Check to see if there are at least three suggested articles.
// If not, fill it with another array of suggested articles.
const fillSuggestionArrayWith = (otherArr: PostInfo[]) => {
if (suggestedArticles.length < 3) {
let sizeToPush = 3 - suggestedArticles.length;
for (const item of otherArr) {
// Handle non-blog content, like about page
if (!item?.published) continue;
// Don't suggest itself
if (item.slug === postNode.slug) continue;
// No duplicates, please!
if (suggestedArticles.includes(item)) continue;
suggestedArticles.push(item);
sizeToPush--;
if (sizeToPush <= 0) return;
}
}
};
const tagSimilaritySorted = similarTags
.sort((a, b) => b.howManyTagsSimilar - a.howManyTagsSimilar)
.map(({ post }) => post);
fillSuggestionArrayWith(tagSimilaritySorted);
fillSuggestionArrayWith(suggestedPosts);
return suggestedArticles;
};