如何将 Dev.to 博客嵌入到您的个人网站
API
集成到 React 应用中
结果
我最近将我的博客从 Medium 转到了 DEV,主要原因是我希望能够轻松地在我的个人网站以及我正在使用的平台上显示我的完整博客。
Medium 急于盈利,因此将博客嵌入到自己的网站变得异常困难。GitHub 上有几个项目试图实现这一点,但效果似乎都不太好,而且样式方面也略显不足。
DEV 拥有开放的 API,让您可以更轻松地将博客嵌入到自己的网站中,并提供完全自定义的标记和样式,让您轻松打造理想的外观。在本文中,我们将介绍如何轻松设置。
我们将使用 TypeScript,但如果您不熟悉它或者只是没有使用它,您仍然可以继续,只是排除 TypeScript 接口。
API
我将使用Superagent作为我的 HTTP 客户端,因为我喜欢它的函数式和声明式 API。不过,你也可以轻松地将其替换为其他 API,例如 Axios 或浏览器的 Fetch API。DEV 的完整 API 文档可以在这里找到。
让我们首先设置一些界面来输入我们的响应(如果您使用的是原始 JavaScript,则可以跳过此步骤)。
dev-to-article-meta.ts
export default interface DevToArticleMeta {
type_of: string;
id: number;
title: string;
description: string;
readable_publish_date: string;
slug: string;
path: string;
url: string;
comments_count: string;
public_reactions_count: string;
collection_id?: number;
published_timestamp: string;
positive_reactions_count: string;
cover_image: string;
social_image: string;
tag_list: Array<string>;
}
dev-to-article.ts
import DevToArticleMeta from "interfaces/dev-to-article-meta";
export default interface DevToArticle extends DevToArticleMeta {
body_html: string;
body_markdown: string;
}
现在让我们设置一个服务来处理数据的获取。我们需要两个方法:
async fetchArticles(): Promise<Array<DevToArticleMeta>>
async getArticle(slug: string): Promise<DevToArticle>
dev-to-service.ts
import DevToArticle from "interfaces/dev-to-article";
import DevToArticleMeta from "interfaces/dev-to-article-meta";
import superagent from "superagent";
// setup API endpoints and queries
const DEV_TO_USERNAME = "matjones"; // swap this for your username
const ARTICLES_API = "https://dev.to/api/articles";
// helper method to type responses
const parseResponse = <T>(response: any): T =>
(typeof response === "string" ? JSON.parse(response) : response) as T;
const fetchArticles = async () => {
// GET the endpoint
const response = await superagent.get(ARTICLES_API)
// and add the username query parameter
.query({ username: DEV_TO_USERNAME });
return parseResponse(response.body);
};
const getArticle = async (slug: string) => {
// build the API endpoint URL, `slug` is the `slug`
//property of one of your articles,
// e.g. "protecting-your-privacy-online-3bmc"
const endpoint = `${ARTICLES_API}/${DEV_TO_USERNAME}/${slug}`;
const response = await superagent.get(endpoint);
return parseResponse<DevToArticle>(response.body);
};
export const DevToService = {
fetchArticles,
getArticle,
};
我们的 API 层基本就是这样了。简洁又漂亮。😎
该fetchArticles()
函数将获取您所有已发布的文章。如果您想实现分页或限制最大结果数量,可以通过chained off of 方法添加page
和per_page
查询参数。.query()
superagent.get()
集成到 React 应用中
虽然从技术上讲,这就是您获取呈现文章所需的数据的全部内容,但我们可以通过添加一些自定义 React 钩子,使其在 React 应用程序中更容易使用。
停一下:如果你不熟悉 React Hooks,先去了解一下 Hooks,然后再回来。我等你。
我们将为每个服务方法创建一个非常简单的钩子。由于useEffect
回调不能是async
自身,我们将使用IIFE,但请注意,您也可以使用可靠的旧Promise.then()
语法。
use-dev-to-articles.ts
import DevToArticleMeta from "interfaces/dev-to-article-meta";
import { useEffect, useState } from "react";
import { DevToService } from "utils/dev-to-service";
/**
* Fetch all of my published dev.to articles
* @param onError a callback which is invoked if the request fails
*/
export default function useDevToArticles(onError?: () => void) {
const [loading, setLoading] = useState(true);
const [articles, setArticles] = useState<Array<DevToArticleMeta>>([]);
useEffect(() => {
(async () => {
try {
setArticles(await DevToService.fetchArticles());
} catch (e) {
onError?.();
}
setLoading(false);
})();
}, [onError]);
// return the array of articles, and the loading indicator
return { articles, loading };
}
use-dev-to-article.ts
import DevToArticle from "interfaces/dev-to-article";
import { useState, useEffect } from "react";
import { DevToService } from "utils/dev-to-service";
/**
* Get a specific article given the article's slug.
* @param slug the slug of the article to retrieve.
* @param onError a callback which is invoked if the request fails
*/
export default function useDevToArticle(slug: string, onError?: () => void) {
const [loading, setLoading] = useState(true);
const [article, setArticle] = useState<DevToArticle>();
useEffect(() => {
// this bit may not be necessary for you; I needed
// it because I'm using Next.js server side rendering
// so slug is `undefined` on the initial render
// since I'm getting it from a route parameter
// e.g. /blog/:slug
if (slug == null || slug.length === 0) {
return;
}
(async () => {
try {
setArticle(await DevToService.getArticle(slug));
} catch (e) {
onError?.();
}
setLoading(false);
})();
}, [onError, slug]);
// return the article, and the loading indicator
return { article, loading };
}
渲染文章正文
渲染文章正文有两种方式:使用body_html
属性或body_markdown
属性。如果您的文章不包含任何 HTML 元素(您只使用 Markdown 格式)且不包含任何 DEV Liquid 标签,则可以使用body_markdown
属性并使用类似 的方法react-markdown
来渲染它。
但是,如果您使用的是 Liquid 标签,或者您的 Markdown 中包含 HTML 元素(例如,我经常使用该<figcaption>
元素为图片和代码片段添加标题),则需要 render 该body_html
属性。在 React 中,可以像这样完成:
<div dangerouslySetInnerHTML={{ __html: article.body_html }}/>
请注意dangerouslySetInnerHTML
语法;它故意设计得丑陋不堪,而且难以快速输入,因为这样做可能会让你的应用面临跨站脚本攻击的风险。通常情况下,你应该只dangerouslySetInnerHTML
在可信输入(例如,你控制的输入,而不是未知的最终用户的输入)的情况下使用。
结果
通过这种方法,我可以轻松地将我的开发博客嵌入到我的个人网站中。点击评论或点赞图标即可打开 dev.to 上的文章,以便添加回复或评论。