From e7e8d9c17160032c335b22c19e8ca2af61fef539 Mon Sep 17 00:00:00 2001 From: Rjnishant530 Date: Wed, 8 Jan 2025 12:24:25 +0530 Subject: [PATCH 1/5] init --- lib/routes/joshwcomeau/latest.ts | 59 +++++++++++++++++++++++++++ lib/routes/joshwcomeau/namespace.ts | 8 ++++ lib/routes/joshwcomeau/popular.ts | 49 ++++++++++++++++++++++ lib/routes/joshwcomeau/utils.ts | 63 +++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 lib/routes/joshwcomeau/latest.ts create mode 100644 lib/routes/joshwcomeau/namespace.ts create mode 100644 lib/routes/joshwcomeau/popular.ts create mode 100644 lib/routes/joshwcomeau/utils.ts diff --git a/lib/routes/joshwcomeau/latest.ts b/lib/routes/joshwcomeau/latest.ts new file mode 100644 index 00000000000000..b4047d0093ba77 --- /dev/null +++ b/lib/routes/joshwcomeau/latest.ts @@ -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: '/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; +} diff --git a/lib/routes/joshwcomeau/namespace.ts b/lib/routes/joshwcomeau/namespace.ts new file mode 100644 index 00000000000000..f0125e4c9b2b0a --- /dev/null +++ b/lib/routes/joshwcomeau/namespace.ts @@ -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', +}; diff --git a/lib/routes/joshwcomeau/popular.ts b/lib/routes/joshwcomeau/popular.ts new file mode 100644 index 00000000000000..064dbddacdfdaf --- /dev/null +++ b/lib/routes/joshwcomeau/popular.ts @@ -0,0 +1,49 @@ +import { Data, Route } from '@/types'; +import { getRelativeUrlList, processList, rootUrl } from './utils'; + +export const route: Route = { + path: '/popular/:dateSort?', + categories: ['programming'], + example: '/popular/false', + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + parameters: { + dateSort: { + description: 'Sort posts by publication date instead of popularity', + default: 'true', + options: [ + { value: 'false', label: 'False' }, + { value: 'true', label: 'True' }, + ], + }, + }, + radar: [ + { + source: ['joshwcomeau.com/'], + target: '/popular', + }, + ], + name: 'Popular Content', + maintainers: ['Rjnishant530'], + handler, +}; + +async function handler(ctx) { + const dateSort = ctx.req.param('dateSort') ? JSON.parse(ctx.req.param('dateSort')) : true; + const { urls } = await getRelativeUrlList(rootUrl, 'section > ol > li > a'); + const items = await processList(urls, { dateSort }); + 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; +} diff --git a/lib/routes/joshwcomeau/utils.ts b/lib/routes/joshwcomeau/utils.ts new file mode 100644 index 00000000000000..b3dba2926a11bf --- /dev/null +++ b/lib/routes/joshwcomeau/utils.ts @@ -0,0 +1,63 @@ +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, { dateSort = true } = {}) { + const listPromise = await Promise.allSettled( + list.map(async (item) => await cache.tryGet(`joshwcomeau:${item.url}`, async () => await getPostContent(item, dateSort))) + ); + 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 }, dateSort: boolean) { + 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: dateSort ? processDate(pubDate) : '', + updateDate: dateSort ? processDate(updateDate) : '', + 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'); +} From 25ab97810d45896be29098b972f26949c9b9bf67 Mon Sep 17 00:00:00 2001 From: Nishant Singh <57475999+Rjnishant530@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:13:00 +0530 Subject: [PATCH 2/5] Update lib/routes/joshwcomeau/latest.ts Co-authored-by: Tony --- lib/routes/joshwcomeau/latest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/joshwcomeau/latest.ts b/lib/routes/joshwcomeau/latest.ts index b4047d0093ba77..2483403361b989 100644 --- a/lib/routes/joshwcomeau/latest.ts +++ b/lib/routes/joshwcomeau/latest.ts @@ -4,7 +4,7 @@ import { getRelativeUrlList, processList, rootUrl } from './utils'; export const route: Route = { path: '/latest/:category?', categories: ['programming'], - example: '/latest/css', + example: '/joshwcomeau/latest/css', features: { requireConfig: false, requirePuppeteer: false, From 6b60313b35253c745dc18df4ad2bbd36ba5a3941 Mon Sep 17 00:00:00 2001 From: Nishant Singh <57475999+Rjnishant530@users.noreply.github.com> Date: Mon, 13 Jan 2025 09:13:06 +0530 Subject: [PATCH 3/5] Update lib/routes/joshwcomeau/popular.ts Co-authored-by: Tony --- lib/routes/joshwcomeau/popular.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/joshwcomeau/popular.ts b/lib/routes/joshwcomeau/popular.ts index 064dbddacdfdaf..16bfd96dfe89b2 100644 --- a/lib/routes/joshwcomeau/popular.ts +++ b/lib/routes/joshwcomeau/popular.ts @@ -4,7 +4,7 @@ import { getRelativeUrlList, processList, rootUrl } from './utils'; export const route: Route = { path: '/popular/:dateSort?', categories: ['programming'], - example: '/popular/false', + example: '/joshwcomeau/popular/false', features: { requireConfig: false, requirePuppeteer: false, From d80e2fa14e5b455ac21f05eb53ff2286b23af8e2 Mon Sep 17 00:00:00 2001 From: Rjnishant530 Date: Sat, 18 Jan 2025 10:22:30 +0530 Subject: [PATCH 4/5] remove date sorting --- lib/routes/joshwcomeau/popular.ts | 19 ++++--------------- lib/routes/joshwcomeau/utils.ts | 12 +++++------- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/lib/routes/joshwcomeau/popular.ts b/lib/routes/joshwcomeau/popular.ts index 16bfd96dfe89b2..dd5c352ce0e929 100644 --- a/lib/routes/joshwcomeau/popular.ts +++ b/lib/routes/joshwcomeau/popular.ts @@ -2,9 +2,9 @@ import { Data, Route } from '@/types'; import { getRelativeUrlList, processList, rootUrl } from './utils'; export const route: Route = { - path: '/popular/:dateSort?', + path: '/popular', categories: ['programming'], - example: '/joshwcomeau/popular/false', + example: '/joshwcomeau/popular', features: { requireConfig: false, requirePuppeteer: false, @@ -13,16 +13,6 @@ export const route: Route = { supportPodcast: false, supportScihub: false, }, - parameters: { - dateSort: { - description: 'Sort posts by publication date instead of popularity', - default: 'true', - options: [ - { value: 'false', label: 'False' }, - { value: 'true', label: 'True' }, - ], - }, - }, radar: [ { source: ['joshwcomeau.com/'], @@ -34,10 +24,9 @@ export const route: Route = { handler, }; -async function handler(ctx) { - const dateSort = ctx.req.param('dateSort') ? JSON.parse(ctx.req.param('dateSort')) : true; +async function handler() { const { urls } = await getRelativeUrlList(rootUrl, 'section > ol > li > a'); - const items = await processList(urls, { dateSort }); + const items = await processList(urls); return { title: 'Popular Content | Josh W. Comeau', description: 'Friendly tutorials for developers. Focus on React, CSS, Animation, and more!', diff --git a/lib/routes/joshwcomeau/utils.ts b/lib/routes/joshwcomeau/utils.ts index b3dba2926a11bf..8fe95efd1bb938 100644 --- a/lib/routes/joshwcomeau/utils.ts +++ b/lib/routes/joshwcomeau/utils.ts @@ -20,14 +20,12 @@ export async function getRelativeUrlList(url, selector) { return { heading, urls }; } -export async function processList(list, { dateSort = true } = {}) { - const listPromise = await Promise.allSettled( - list.map(async (item) => await cache.tryGet(`joshwcomeau:${item.url}`, async () => await getPostContent(item, dateSort))) - ); +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 }, dateSort: boolean) { +export async function getPostContent({ url, cardTitle }) { if (url.startsWith('https')) { return { title: cardTitle ?? 'External Content', @@ -49,8 +47,8 @@ export async function getPostContent({ url, cardTitle }, dateSort: boolean) { title, description, author, - pubDate: dateSort ? processDate(pubDate) : '', - updateDate: dateSort ? processDate(updateDate) : '', + pubDate: processDate(pubDate), + updateDate: processDate(updateDate), link: `${rootUrl}${url}`, content: { html: description, text: summary }, category: [tag], From 24731733d0683f534b6a6bb5a7c0a7f717aa137f Mon Sep 17 00:00:00 2001 From: Rjnishant530 Date: Sat, 18 Jan 2025 23:41:49 +0530 Subject: [PATCH 5/5] update date --- lib/routes/joshwcomeau/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes/joshwcomeau/utils.ts b/lib/routes/joshwcomeau/utils.ts index 8fe95efd1bb938..90a71a73edec75 100644 --- a/lib/routes/joshwcomeau/utils.ts +++ b/lib/routes/joshwcomeau/utils.ts @@ -48,7 +48,7 @@ export async function getPostContent({ url, cardTitle }) { description, author, pubDate: processDate(pubDate), - updateDate: processDate(updateDate), + updated: processDate(updateDate), link: `${rootUrl}${url}`, content: { html: description, text: summary }, category: [tag],