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(css-tricks): add new routes for css-tricks #18066

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion lib/routes/30secondsofcode/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ async function processItem({ link: articleLink, date }) {
category: tags,
image: `${rootUrl}${image}`,
banner: `${rootUrl}${image}`,
language: 'en-us',
} as DataItem;
});
}
48 changes: 48 additions & 0 deletions lib/routes/css-tricks/articles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Data, Route, ViewType } from '@/types';
import { load } from 'cheerio';
import ofetch from '@/utils/ofetch';
import { processWithWp } from './utils';
export const route: Route = {
path: '/articles',
view: ViewType.Articles,
categories: ['programming'],
example: '/articles',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
example: '/articles',
example: '/css-tricks/articles',

features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['css-tricks.com/category/articles/'],
target: '/articles',
},
],
name: 'Articles',
maintainers: ['Rjnishant530'],
handler,
};

async function handler() {
// const category = ctx.req.param('category') ?? '';
// const subCategory = ctx.req.param('subCategory') ?? '';

const rootUrl = 'https://css-tricks.com';
const currentUrl = `${rootUrl}/category/articles/`;
const response = await ofetch(currentUrl);
const $ = load(response);
const articleCards = $('article.article-card').toArray();
const items = await processWithWp(articleCards);
return {
title: 'Articles - CSS-Tricks',
description: 'Latest Articles - CSS-Tricks',
link: currentUrl,
item: items,
language: 'en',
logo: `${rootUrl}/favicon.ico`,
icon: `${rootUrl}/favicon.ico`,
} as Data;
}
40 changes: 40 additions & 0 deletions lib/routes/css-tricks/fresh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Data, Route, ViewType } from '@/types';
import { extractMiniCards, processCards, rootUrl } from './utils';
export const route: Route = {
path: '/fresh',
view: ViewType.Articles,
categories: ['programming'],
example: '/fresh',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
example: '/fresh',
example: '/css-tricks/fresh',

features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['css-tricks.com'],
target: '/fresh',
},
],
name: 'Fresh From the Almanac',
maintainers: ['Rjnishant530'],
handler,
};

async function handler() {
const popularCards = await extractMiniCards('body > div.page-wrap > section.post-sliders > div:nth-child(4) article.mini-card.module.module-article');
// Can't use wordPress API, these post Id's aren't available in the response
const items = await processCards(popularCards, true);
return {
title: 'Fresh From the Almanac',
description: 'Properties, selectors, rules, and functions!',
link: rootUrl,
item: items,
language: 'en',
logo: `${rootUrl}/favicon.ico`,
icon: `${rootUrl}/favicon.ico`,
} as Data;
}
39 changes: 39 additions & 0 deletions lib/routes/css-tricks/guides.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Data, Route, ViewType } from '@/types';
import { extractMiniCards, processWithWp, rootUrl } from './utils';
export const route: Route = {
path: '/guides',
view: ViewType.Articles,
categories: ['programming'],
example: '/guides',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
example: '/guides',
example: '/css-tricks/guides',

features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['css-tricks.com'],
target: '/guides',
},
],
name: 'CSS Guides',
maintainers: ['Rjnishant530'],
handler,
};

async function handler() {
const guideCards = await extractMiniCards('body > div.page-wrap > section.post-sliders > div:nth-child(3) article.mini-card.module.module-article');
const items = await processWithWp(guideCards, true);
return {
title: 'Latest CSS Guides',
description: 'Dive deep into CSS features and concepts',
link: rootUrl,
item: items,
language: 'en',
logo: `${rootUrl}/favicon.ico`,
icon: `${rootUrl}/favicon.ico`,
} as Data;
}
8 changes: 8 additions & 0 deletions lib/routes/css-tricks/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: 'CSS-Tricks',
url: 'css-tricks.com',
categories: ['programming'],
lang: 'en',
};
39 changes: 39 additions & 0 deletions lib/routes/css-tricks/popular.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Data, Route, ViewType } from '@/types';
import { extractMiniCards, processWithWp, rootUrl } from './utils';
export const route: Route = {
path: '/popular',
view: ViewType.Articles,
categories: ['programming'],
example: '/popular',
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
example: '/popular',
example: '/css-tricks/popular',

features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: false,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
radar: [
{
source: ['css-tricks.com'],
target: '/popular',
},
],
name: 'Popular this month',
maintainers: ['Rjnishant530'],
handler,
};

async function handler() {
const popularCards = await extractMiniCards('div.popular-articles > div.mini-card-grid article.mini-card.module.module-article');
const items = await processWithWp(popularCards, true);
return {
title: 'Popular this month',
description: 'Popular CSS articles this month',
link: rootUrl,
item: items,
language: 'en',
logo: `${rootUrl}/favicon.ico`,
icon: `${rootUrl}/favicon.ico`,
} as Data;
}
141 changes: 141 additions & 0 deletions lib/routes/css-tricks/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { load } from 'cheerio';
import ofetch from '@/utils/ofetch';
import { DataItem } from '@/types';
import { parseDate } from '@/utils/parse-date';
import cache from '@/utils/cache';

export const rootUrl = 'https://css-tricks.com';
type Card = {
id: string;
title: string;
link: string;
thumbnail: string;
};

export async function extractMiniCards(selector) {
const response = await ofetch(rootUrl);
const $ = load(response);
return $(selector).toArray();
}

function extractCardsInfo(cards) {
return cards.map((card) => {
const $ = load(card);
const id = $(card).attr('id');
const thumbnail = $(card).find('div.article-thumbnail-wrap > a >img').attr('src');
const article = $('div.article-article');
const title = article.find('h2 > a').text();
const link = article.find('h2 > a').attr('href');
return {
id,
title,
link,
thumbnail,
} as Card;
});
}

function extractMiniCardsInfo(cards) {
return cards.map((card) => {
const $ = load(card);
const id = $(card).attr('id')?.replace('mini-', '');
const thumbnail = '';
const title = $('h3.mini-card-title').find('a:not(.aal_anchor)').text();
const link = $('h3.mini-card-title').find('a:not(.aal_anchor)').attr('href');
return {
id,
title,
link,
thumbnail,
} as Card;
});
}

export async function processWithWp(cards, mini: boolean = false) {
const cardsWithInfo = mini ? extractMiniCardsInfo(cards) : extractCardsInfo(cards);
const ids = cardsWithInfo.map((card: Card) => card.id.replace('post-', ''));
const allPosts = await ofetch(`${rootUrl}/wp-json/wp/v2/posts?include=${ids.join(',')}&_embed&per_page=${ids.length}`);
// To maintain the ID/post Order
const idMappedPost = Object.fromEntries(allPosts.map((post) => [post.id, post]));
return ids.map((id) => extractPostDetails(idMappedPost[id]));
}

export async function processCards(cards, mini: boolean = false) {
const cardsWithInfo = mini ? extractMiniCardsInfo(cards) : extractCardsInfo(cards);
const cardsPromise = await Promise.allSettled(cardsWithInfo.map(async (card: Card) => await fetchCardDetails(card)));
return cardsPromise.filter((card) => card.status === 'fulfilled').map((card) => card.value as DataItem);
}

export async function fetchCardDetails(card: Card) {
return await cache.tryGet(`css-tricks:${card.id}`, async () => {
const response = await ofetch(card.link);
const $ = load(response);
const tags = $('meta[property="article:tag"]')
?.toArray()
.map((tag) => $(tag).attr('content'));
const date = $('meta[property="article:published_time"]').attr('content') || '';
const updateDate = $('meta[property="article:modified_time"]').attr('content') || '';
const summary = $('meta[property="description"]').attr('content') || '';
const authorUrl = $('header.mega-header').find('div.author-row > a').attr('href');
const authorAvatar = $('header.mega-header').find('header.mega-header div.author-row > a >img').attr('src');
const authorName = $('header.mega-header').find('header.mega-header div.author-row > div > a.author-name').text();
const content = $('div.article-content').html() || '';
return {
title: card.title,
link: card.link,
description: content,
banner: card.thumbnail,
image: card.thumbnail,
pubDate: parseDate(date),
updated: parseDate(updateDate),
author: [
{
name: authorName || '',
url: authorUrl || '',
avatar: authorAvatar || '',
},
],
content: {
html: content,
text: summary,
},
category: tags,
} as DataItem;
});
}

function extractPostDetails(data) {
const title = data.title.rendered;
const link = data.link;
const content = data.content.rendered;
const summary = data.excerpt.rendered;
const date = data.date_gmt;
const updateDate = data.modified_gmt;
const author = data._embedded.author;
const authorName = author[0]?.name;
const authorUrl = author[0]?.link;
const authorAvatar = author[0]?.avatar_urls['48'];
const thumbnail = data._embedded['wp:featuredmedia']?.[0]?.source_url;
const tags = data._embedded['wp:term']?.[1]?.map((tag) => tag.name);
return {
title,
link,
description: content,
banner: thumbnail,
image: thumbnail,
pubDate: parseDate(date),
updated: parseDate(updateDate),
author: [
{
name: authorName || '',
url: authorUrl || '',
avatar: authorAvatar || '',
},
],
content: {
html: content,
text: summary,
},
category: tags,
} as DataItem;
}
Loading