Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(joshwcomeau): add new Routes josh w comeau #18071

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
59 changes: 59 additions & 0 deletions lib/routes/joshwcomeau/latest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Data, Route } from '@/types';
import { getRelativeUrlList, processList, rootUrl } from './utils';

export const route: Route = {
path: '/latest/:category?',
categories: ['programming'],
example: '/joshwcomeau/latest/css',
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
parameters: {
category: {
description: 'Category',
options: [
{ value: 'css', label: 'CSS' },
{ value: 'react', label: 'React' },
{ value: 'animation', label: 'Animation' },
{ value: 'javascript', label: 'JavaScript' },
{ value: 'career', label: 'Career' },
{ value: 'blog', label: 'Blog' },
],
},
},
radar: [
{
source: ['joshwcomeau.com/'],
target: '/latest',
},
{
source: ['joshwcomeau.com/:category'],
target: '/latest/:category',
},
],
name: 'Articles and Tutorials',
maintainers: ['Rjnishant530'],
handler,
};

async function handler(ctx) {
const category: string = ctx.req.param('category') || '';
const currentUrl = category ? `${rootUrl}/${category}` : rootUrl;
const selector = category ? 'div > article > a:first-child' : 'article[data-include-enter-animation="false"] > a:first-child';
const { heading, urls } = await getRelativeUrlList(currentUrl, selector);
const items = await processList(urls);
const title = category ? `${heading} | ` : '';
return {
title: `${title}Articles and Tutorials | Josh W. Comeau`,
description: `Friendly tutorials for developers. Focus on ${category ? title : 'React, CSS, Animation, and more!'}`,
link: currentUrl,
item: items,
icon: `${rootUrl}/favicon.png`,
logo: `${rootUrl}/favicon.png`,
} as Data;
}
8 changes: 8 additions & 0 deletions lib/routes/joshwcomeau/namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { Namespace } from '@/types';

export const namespace: Namespace = {
name: 'Josh W Comeau',
url: 'www.joshwcomeau.com',
categories: ['programming'],
lang: 'en',
};
38 changes: 38 additions & 0 deletions lib/routes/joshwcomeau/popular.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Data, Route } from '@/types';
import { getRelativeUrlList, processList, rootUrl } from './utils';

export const route: Route = {
path: '/popular',
categories: ['programming'],
example: '/joshwcomeau/popular',
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['joshwcomeau.com/'],
target: '/popular',
},
],
name: 'Popular Content',
maintainers: ['Rjnishant530'],
handler,
};

async function handler() {
const { urls } = await getRelativeUrlList(rootUrl, 'section > ol > li > a');
const items = await processList(urls);
return {
title: 'Popular Content | Josh W. Comeau',
description: 'Friendly tutorials for developers. Focus on React, CSS, Animation, and more!',
link: rootUrl,
item: items,
icon: `${rootUrl}/favicon.png`,
logo: `${rootUrl}/favicon.png`,
} as Data;
}
61 changes: 61 additions & 0 deletions lib/routes/joshwcomeau/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { DataItem } from '@/types';
import { load } from 'cheerio';
import ofetch from '@/utils/ofetch';
import { parseDate } from '@/utils/parse-date';
import cache from '@/utils/cache';

export const rootUrl = 'https://www.joshwcomeau.com';

export async function getRelativeUrlList(url, selector) {
const response = await ofetch(url);
const $ = load(response);
const heading = $('header>h1').text();
const urls = $(selector)
.toArray()
.map((element) => {
const itemRelativeUrl = $(element).attr('href');
const cardTitle = $(element).find('span').text();
return { url: itemRelativeUrl as string, cardTitle };
});
return { heading, urls };
}

export async function processList(list) {
const listPromise = await Promise.allSettled(list.map(async (item) => await cache.tryGet(`joshwcomeau:${item.url}`, async () => await getPostContent(item))));
return listPromise.map((item, index) => (item.status === 'fulfilled' ? item.value : ({ title: 'Error Reading Item', link: `${rootUrl}${list[index]?.url}` } as DataItem)));
}

export async function getPostContent({ url, cardTitle }) {
if (url.startsWith('https')) {
return {
title: cardTitle ?? 'External Content',
description: 'Read it on external Site',
link: url,
} as DataItem;
}
const response = await ofetch(`${rootUrl}${url}`);
const $ = load(response);
const title = $('meta[property="og:title"]').attr('content')?.replace('• Josh W. Comeau', '');
const summary = $('meta[property="og:description"]').attr('content');
const author = $('meta[name="author"]').attr('content');
const dateDiv = $('div[data-parent-layout]');
const tag = dateDiv.find('dl:first-child > dd > a').text();
const pubDate = dateDiv.find('dl:first-child > dd:has(span):not(:last-child)').text();
const updateDate = dateDiv.find('dl:last-child > dd:has(span):not(:last-child)').text();
const description = $('main > article').html();
return {
title,
description,
author,
pubDate: processDate(pubDate),
updateDate: processDate(updateDate),
Rjnishant530 marked this conversation as resolved.
Show resolved Hide resolved
link: `${rootUrl}${url}`,
content: { html: description, text: summary },
category: [tag],
} as DataItem;
}

function processDate(date: string) {
const dateWithSlash = date.trim().replaceAll(' ', '/').replace(',', '');
return parseDate(dateWithSlash, 'MMMM/Do/YYYY', 'en');
}
Loading