by Shaun Persad
通過Shaun Persad
如何構建自己的免費無服務器評論框 (How you can build your own free, serverless comment box)
Contentful’s flexible content modeling goes far beyond blog posts. Here’s how you can leverage Contentful and Netlify to create a nested commenting system that’s easy to moderate and deploy.
Contentful靈活的內容建模遠遠超出了博客文章。 這是如何利用Contentful和Netlify創建易于審核和部署的嵌套評論系統。
動機 (The motivation)
I find most commenting systems out there to be…lacking. Disqus can often be slow to render, and their user tracking behavior doesn’t have the best reputation. Meanwhile, Facebook’s comments plugin is quite nice, but of course is limited to Facebook users.
我發現那里的大多數評論系統都……不足。 Disqus的呈現速度通常很慢,并且它們的用戶跟蹤行為也不是最好的聲譽。 同時,Facebook的評論插件相當不錯,但當然僅限Facebook用戶。
What I really wanted was the native speed and approach to nested commenting and moderation taken by sites like Hacker News and Indie Hackers, but I needed a solution that would be portable to multiple projects.
我真正想要的是像Hacker News和Indie Hackers這樣的網站采用固有的速度和方法來進行嵌套評論和主持人,但是我需要一個可移植到多個項目的解決方案。
There just didn’t seem to be a great fit out there, so I decided to build my own, with my wish list of features:
似乎沒有什么合適的選擇,所以我決定建立我自己的功能清單,并提供以下功能:
Free
自由
Low barrier to entry — minimal steps required to submit a comment
進入門檻低 -提交評論所需的最少步驟
Low maintenance — serverless, to not worry about hosting or scaling
低維護 —無服務器,不用擔心托管或擴展
Easy moderation — use a dashboard to perform CRUD on comments
輕松審核 -使用儀表板對評論執行CRUD
Peformant — super-fast to appear on the page
Peformant-超快速出現在頁面上
Flexible — users should be able to log in via multiple platforms
靈活 -用戶應該能夠通過多個平臺登錄
Powerful — comments should have smart formatting features
強大 -注釋應具有智能格式設置功能
High comment quality — users can upvote and downvote comments
高評論質量 -用戶可以對評論進行投票和降級
Subscriptions — users can receive notifications when their comments are replied to
訂閱 -回復他們的評論時,用戶可以收到通知
Over the course of this series, we will build out a commenting system that incorporates each of the above aspects.
在本系列的整個過程中,我們將構建一個包含上述各個方面的評論系統。
計劃 (The plan)
Our stack will initially include:
我們的堆棧最初將包括:
Contentful as a database and moderation dashboard
內容豐富,可作為數據庫和審核儀表板
AWS Lambda via Netlify as our back-end
通過Netlify的 AWS Lambda作為我們的后端
React on the front-end
在前端做出React
We will create a React component to serve as our comment box, and supply it with the ability to make an API call to Contentful to fetch comments as necessary. It will also be able to make an API call to our Lambda function to post a comment to Contentful.
我們將創建一個React組件作為我們的注釋框,并為它提供對Contentful進行API調用以根據需要獲取注釋的功能。 它還將能夠對我們的Lambda函數進行API調用,以對Contentful發表評論。
Project-wise, our Lambda function will live along-side our front-end code. Both the front-end and back-end will be set up to be continuously deployed via Netlify.
在項目方面,我們的Lambda函數將與我們的前端代碼一起使用。 前端和后端都將設置為通過Netlify進行連續部署。
By the way, the above stack is all free! Well, mostly. Unless you’re going to be doing over 10,000 comments, it’s free. Also, I’m not affiliated with any of these companies…I just love their stuff :)
順便說一下,以上堆棧都是免費的! 好吧,主要是。 除非您要進行超過10,000條評論,否則它是免費的。 另外,我與這些公司都不隸屬...我只是喜歡他們的東西:)
在10秒內滿意 (Contentful in 10 seconds)
If you’re not already familiar with Contentful and how it works, it’s a “headless” (API-driven) CMS. You’re able to model your content with different fields and field types, and then you create content based on those models. You can build your front-end however you like, and query for your data using their API. It’s super flexible, and their dashboard is quite nice to use. It’s basically the best thing to happen to CMS’s since, well, ever?
如果您還不熟悉Contentful及其工作原理 ,那么它就是“無頭”(由API驅動)的CMS。 您可以使用不同的字段和字段類型對內容建模,然后根據這些模型創建內容。 您可以根據需要構建自己的前端,并使用其API查詢數據。 它非常靈活,他們的儀表板非常好用。 從根本上說,這是CMS發生的最好的事情了?
I was already using Contentful for my blog posts, so I wondered, could it be viable to host comments as well? I’m happy to report that the answer is yes! However, a few of the items on my wishlist don’t quite work out using just Contentful. But don’t worry, we’ll get there…in the subsequent posts of this series.
我已經在博客文章中使用了Contentful,所以我想知道是否也可以托管評論? 我很高興地報告答案是肯定的! 但是,我的愿望清單上的某些項目僅使用Contentful不太可行。 但是請放心,我們將會到達……在本系列的后續文章中。
We’ll be using Contentful because:
我們將使用Contentful是因為:
- flexible data modeling 靈活的數據建模
- convenient API 方便的API
- moderation via a dashboard 通過儀表板進行審核
- you may already be using it for your website/blog that needs comments 您可能已經將其用于需要評論的網站/博客
在10秒內完成Netlify (Netlify in 10 seconds)
I think Netlify has by far the most enjoyable deployment experience for front-end apps. It links to your GitHub repo and sets you up to continuously deploy a static site to CDN-backed hosting. They also have Netlify Functions, which let you deploy to AWS Lambda without any of the pain of messing around in AWS.
我認為Netlify迄今為止具有最令人滿意的前端應用程序部署體驗。 它鏈接到您的GitHub存儲庫,并設置為將靜態站點連續部署到CDN支持的托管。 它們還具有Netlify Functions ,可讓您部署到AWS Lambda,而不會在AWS上亂成一團 。
You can get started at their docs, but honestly, their dashboard is so easy to use and understand, I recommend just logging in and poking around.
您可以從他們的文檔開始,但老實說,他們的儀表板非常易于使用和理解,我建議您先登錄并四處瀏覽。
We’ll be using Netlify because:
我們將使用Netlify是因為:
- painless AWS Lambda integration 無痛的AWS Lambda集成
- you may already be using it for your website/blog that needs comments 您可能已經將其用于需要評論的網站/博客
- If you’re not already using it, you can still deploy the Lambda functions we create to AWS itself 如果尚未使用它,您仍然可以將我們創建的Lambda函數部署到AWS本身
等等,沒有“ 10秒鐘后React”嗎? (Wait, no “React in 10 seconds”?)
I don’t know if 10 seconds is enough to do React justice. If you haven’t yet learned it, you should! But skip the Redux and Flux stuff. Chances are you don’t need any of that (but that’s another topic for another time).
我不知道10秒鐘是否足以完成React正義。 如果您還沒有學過,那應該! 但是跳過Redux和Flux的內容。 您可能不需要其中任何一個(但這是另一回事了)。
Contentful中的內容建模 (Content modeling in Contentful)
Now down to business.
現在正事。
There are two different approaches we could take regarding how we handle our users: authless and logged-in commenting:
關于我們如何處理用戶,我們可以采取兩種不同的方法: authless和登錄評論:
- Authless — anyone can leave a comment simply by supplying their name 無需身份驗證-任何人都可以通過提供姓名來發表評論
- Logged-in — only users who are authenticated in some auth system can comment 登錄-只有在某些身份驗證系統中經過身份驗證的用戶才能評論
I prefer logged-in commenting, because in my opinion, the conversations tend to be more civilized. Plus, you tend to avoid spam altogether. On the flipside, the barrier to create a comment is slightly higher.
我更喜歡登錄評論,因為我認為對話通常更加文明。 另外,您傾向于完全避免垃圾郵件。 另一方面,創建評論的障礙稍高。
However, we will start off with authless commenting, because it’s simpler to implement. Once we get our feet wet, we’ll jump into logged-in commenting in Part 2.
但是,我們將從無認證注釋開始,因為它更易于實現。 一旦弄濕了,我們將在第2部分中進入登錄評論。
Regardless, we’re going to first need to create a content model to represent our comments.
無論如何,我們首先需要創建一個內容模型來表示我們的評論。
For both authless and logged-in approaches, our Comment content model will remain mostly the same as well, though there will be some later changes to the Author field, as noted below.
對于無認證方法和登錄方法,我們的評論內容模型也將基本保持不變,盡管稍后會在“ 作者”字段中進行一些更改,如下所述。
評論內容模型 (The Comment content model)
This is the model at the heart of our commenting system. Comments should have four fields:
這是我們評論系統的核心模型。 注釋應包含四個字段:
Body
身體
- The actual body of the comment 評論的實際內容
- Mark this one as the entry title 將此標記為條目標題
- Feel free to also set a maximum and/or minimum value on its length 還可以設置其長度的最大值和/或最小值
Author
作者
- A unique identifier representing the user who posted this comment. 代表發布此評論的用戶的唯一標識符。
- For authless commenting, you’d use short text and fill in the author’s name in this field 對于無認證評論,您將使用短文本并在此字段中填寫作者的姓名
- For logged-in commenting, this field will become a reference to the upcoming CommentAuthor model 對于已登錄的評論,此字段將成為對即將到來的CommentAuthor模型的引用
Subject
學科
- The unique ID of the blog post (or equivalent) that these comments belong to 這些評論所屬的博客文章的唯一ID(或等效名稱)
- It can also be the URL of the page 也可以是頁面的URL
- For maximum flexibility, I chose not to assume that you’re storing your blog posts in Contentful, or else this would be a reference field instead of short text 為了獲得最大的靈活性,我選擇不假定您將博客文章存儲在Contentful中,否則這將是一個參考字段,而不是短文本
ParentComment
家長評論
- If this comment is a reply to another comment, we’ll reference that comment here 如果此評論是對其他評論的回復,我們將在此處引用該評論
- This field is what enables us to create nested comments 該字段使我們能夠創建嵌套注釋
實施無認證評論 (Implementing authless commenting)
For this implementation, we want the user to enter their name before they are able to post a comment. I recommend doing an initial read-through of the following steps, and then check out the final demo project at the end to see how it all comes together.
對于此實現,我們希望用戶在發表評論之前輸入他們的姓名。 我建議對以下步驟進行初步閱讀,然后在最后查看最終的演示項目,以了解所有內容如何組合在一起。
前端 (Front-end)
Now that our Comment model is done, it’s time to create our comment box. The good news is that I’ve already made a generic “comment box” React component. It’s designed as a low-order component, where you wrap a higher-order component around it to handle fetching and creating Contentful comments, and other application-specific business logic.
現在,我們的評論模型已經完成,是時候創建我們的評論框了。 好消息是我已經制作了一個通用的“注釋框” React組件。 它被設計為一個低階組件,你環繞它高階組件來處理獲取和創造Contentful意見,以及其他應用程序特定的業務邏輯。
You can install it and the other required packages via npm:
您可以通過npm安裝它和其他必需的軟件包:
npm install react-commentbox contentful contentful-management --save
The GitHub repo has a list of every prop you can pass to it, but minimally, we’ll be implementing and passing these:
GitHub存儲庫列出了您可以傳遞給它的每個道具,但是最少,我們將實現并傳遞這些道具:
getComments
: a function that returns a promise that resolves to an array of comments, ordered from oldest to newestgetComments
:一個函數,該函數返回一個promise,該promise解析為從最舊到最新的注釋數組normalizeComment
: a function that maps your array of comments to objects that the component understandsnormalizeComment
:將注釋數組映射到組件可以理解的對象的函數comment
: a function that makes an API call to create a comment, and returns a promisecomment
:進行API調用以創建評論并返回promise的函數disabled
: set to true when commenting should be disableddisabled
:應禁用評論時設置為truedisabledComponent
: the component to show when commenting is disableddisabledComponent
:禁用評論時顯示的組件
Let’s create our higher-level component:
讓我們創建更高級別的組件:
import React from 'react';import CommentBox from 'react-commentbox';
class MyCommentBox extends React.Component {
state = { authorName: '', authorNameIsSet: false };
onChangeAuthorName = (e) => this.setState({ authorName: e.currentTarget.value });
onSubmitAuthorName = (e) => {
e.preventDefault(); this.setState({ authorNameIsSet: true }); };}
Notice that the component is in charge of setting the author’s name.
注意,該組件負責設置作者的姓名。
By the way, we’re using the transform-class-properties Babel plugin to avoid tedious constructor setup and function bindings. You don’t need to use it, but it’s quite handy.
順便說一句,我們正在使用transform-class-properties Babel插件來避免繁瑣的構造函數設置和函數綁定。 您不需要使用它,但是非常方便。
Now we need to implement the business-logic props that react-commentbox
needs.
現在,我們需要實現react-commentbox
所需的業務邏輯道具。
We’ll start off by fetching comments from Contentful, and normalizing them:
我們將從獲取Contentful的注釋并對其進行規范化開始:
// fetch our comments from ContentfulgetComments = () => {
return this.props.contentfulClient.getEntries({ 'order': 'sys.createdAt', 'content_type': 'comment', 'fields.subject': this.props.subjectId, }).then( response => {
return response.items;
}).catch(console.error);};
// turn Contentful entries to objects that react-commentbox expects.normalizeComment = (comment) => {
const { id, createdAt } = comment.sys; const { body, author, parentComment } = comment.fields;
return { id, bodyDisplay: body, userNameDisplay: author, timestampDisplay: createdAt.split('T')[0], belongsToAuthor: false, parentCommentId: parentComment ? parentComment.sys.id : null };};
Next, we need to make the API call to create comments:
接下來,我們需要進行API調用以創建注釋:
// make an API call to post a commentcomment = (body, parentCommentId = null) => {
return this.props.postData('/create-comment', { body, parentCommentId, authorName: this.state.authorName, subjectId: this.props.subjectId });};
We also need to ask the user for their name before they can comment:
我們還需要先詢問用戶的姓名,然后他們才能發表評論:
// will be shown when the comment box is initially disableddisabledComponent = (props) => {
return ( <form className="author-name" onSubmit{ this.onSubmitAuthorName } > <input type="text" placeholder="Enter your name to post a comment" value={ this.state.authorName } onChange={ this.onChangeAuthorName } /> <button type="submit">Submit</button> </form> );};
Then, bring it all together in render
, by passing the appropriate props to react-commentbox
:
然后,通過將適當的道具傳遞給react-commentbox
,將其全部合并到render
:
render() {
return ( <div> <h4>Comments</h4> <CommentBox disabled={ !this.state.authorNameIsSet } getComments={ this.getComments } normalizeComment={ this.normalizeComment } comment={ this.comment } disabledComponent={ this.disabledComponent } /> </div> );};
We’ve also set the disabled
prop to true
while the author's name is not set. This disables the textarea
, and shows the disabledComponent
form we made to get the author's name.
在未設置作者姓名的情況下,我們還將disabled
prop設置為true
。 這將禁用textarea
,并顯示我們為獲得作者姓名而創建的disabledComponent
形式。
You can view the complete component here.
您可以在此處查看完整的組件。
You may have noticed that our newly created MyCommentBox
also expects a few props itself: subjectId
, postData
, and contentfulClient
.
您可能已經注意到,我們的新創建的MyCommentBox
還預計一些道具本身: subjectId
, postData
和contentfulClient
。
The subjectId
is simply some unique ID or URL of the blog post (or equivalent entity) that these comments are for.
subjectId
只是這些評論所針對的博客文章(或等效實體)的唯一ID或URL。
postData
is a function that makes POST ajax calls. Using fetch
, it could look like this:
postData
是進行POST ajax調用的函數。 使用fetch
,它看起來可能像這樣:
function postData(url, data) {
return fetch(`.netlify/functions${url}`, { body: JSON.stringify(data), headers: { 'content-type': 'application/json' }, method: 'POST', mode: 'cors' // if your endpoints are on a different domain }).then(response => response.json());}
contentfulClient
is an instance of the client you get when using the contentful npm package (so make sure you've installed it):
contentfulClient
是使用有內容的 npm軟件包時獲得的客戶端的實例(因此請確保已安裝它):
import { createClient } from 'contentful';const contentfulClient = createClient({ space: 'my-space-id', accessToken: 'my-access-token'});
You can get your space ID in the Contentful dashboard under “Space settings” > “General settings”.
您可以在內容豐富的信息中心的“空間設置”>“常規設置”下獲取空間ID。
You can get your access token from “Space settings” > “API keys” > “Content delivery/preview tokens” > “Add API Key”.
您可以從“空間設置”>“ API密鑰”>“內容交付/預覽令牌”>“添加API密鑰”獲取訪問令牌。
You can then pass in your props when creating MyCommentBox
, as shown here.
然后,您可以通過在你的道具制作時MyCommentBox
,如圖所示這里 。
后端 (Back-end)
We will implement our /create-comment
endpoint as an AWS Lambda function.
我們將/create-comment
端點實現為AWS Lambda函數。
先決條件 (Prerequisites)
To be able to build, preview, and eventually deploy these functions, we’re going to use the handy netlify-lambda npm package. It lets you write your Lambda functions as regular ES6 functions in a particular source directory, and then it builds them in a Lambda-friendly way and puts them in a destination directory, ready for deployment. Even better, it also allows us to preview these functions by deploying them locally.
為了能夠構建,預覽并最終部署這些功能,我們將使用方便的netlify-lambda npm軟件包。 它允許您在特定的源目錄中將Lambda函數編寫為常規的ES6函數,然后以對Lambda友好的方式構建它們,并將它們放置在目標目錄中,以備部署。 更好的是,它還允許我們通過在本地部署這些功能來預覽這些功能。
So, you’ll need to create a particular source directory to store your function (e.g. src/lambda
), then create a netlify.toml
file in your root directory. Minimally, that file should look like this:
因此,您需要創建一個特定的源目錄來存儲您的函數(例如src/lambda
),然后在根目錄中創建一個netlify.toml
文件。 至少,該文件應如下所示:
[build] Functions = "lambda"
The above tells netlify-lambda
which directory to put your built functions, meaning it will build the functions in src/lambda
and store them in ./lambda
. Also, when it comes time to deploy, Netlify will look in the ./lambda
directory to deploy to AWS.
上面的內容告訴netlify-lambda
將構建的函數放在哪個目錄中,這意味著它將在src/lambda
構建函數并將它們存儲在./lambda
。 另外,當需要進行部署時,Netlify將在./lambda
目錄中查找以部署到AWS。
To run your Lambda functions locally, use the following command:
要在本地運行Lambda函數,請使用以下命令:
netlify-lambda serve <source directory>
This will allow you to run your functions on http://localhost:9000/{function-name}
.
這將允許您在http://localhost:9000/{function-name}
上運行函數。
This is the default behavior, but it does not quite match what will happen in production, because it’s running our functions on a different domain from our front-end. In production, our functions will be available on the same domain as our front-end, via the URL {domain}/.netlify/functions/{function-name}
.
這是默認行為,但與生產中發生的情況完全不匹配,因為它在我們前端的不同域上運行我們的功能。 在生產中,我們的功能將通過URL {domain}/.netlify/functions/{function-name}
在與前端相同的域中提供。
To replicate this behavior locally, we need to proxy front-end calls from /.netlify/functions/{function-name}
to http://localhost:9000/{function-name}
.
要在本地復制此行為,我們需要將前端調用從/.netlify/functions/{function-name}
代理到http://localhost:9000/{function-name}
。
Accomplishing this differs based on your project setup. I will cover two popular setups:
完成此操作因您的項目設置而異。 我將介紹兩種流行的設置:
For create-react-app projects, add the following to your package.json
:
對于create-react-app項目,將以下內容添加到package.json
:
"proxy": { "/.netlify/functions": { "target": "http://localhost:9000", "pathRewrite": { "^/\\.netlify/functions": "" } }}
For Gatsby.js projects, add the following to your gatsby-config.js
:
對于Gatsby.js項目,將以下內容添加到您的gatsby-config.js
:
const proxy = require('http-proxy-middleware');...developMiddleware: app => { app.use( '/.netlify/functions/', proxy({ target: 'http://lambda:9000', pathRewrite: { '/.netlify/functions/': '', } }) );},
For most other projects, you can leverage webpack’s dev server, which has proxy support.
對于大多數其他項目,您可以利用webpack的具有代理支持的dev服務器。
編寫我們的功能 (Writing our function)
Before we get to writing Lambda-specific code, we will first create a generic function to handle most of our logic. This way, our code remains portable beyond Lambda.
在編寫特定于Lambda的代碼之前,我們將首先創建一個通用函數來處理大多數邏輯。 這樣,我們的代碼可移植到Lambda之外。
Let’s create a createComment
function:
讓我們創建一個createComment
函數:
const contentful = require('contentful-management');const client = contentful.createClient({ accessToken: process.env.CONTENTFUL_CONTENT_MANAGEMENT_ACCESS_TOKEN});
module.exports = function createComment( body, authorName, subjectId, parentCommentId = null) {
return client.getSpace('my-space-id') .then(space => space.getEnvironment('master')) .then(environment => environment.createEntry('comment', { fields: { body: { 'en-US': body }, author: { 'en-US': authorName }, subject: { 'en-US': subjectId }, parentComment: { 'en-US': { sys: { type: 'Link', linkType: 'Entry', id: parentCommentId } } } } })) .then(entry => entry.publish());};
You can put the above function someplace like a utils
directory. It uses the contentful-management
npm package to create and publish a new comment entry, and returns a promise. Notice we've specified our management API key as an environment variable. You definitely do not want to hard-code that one. When deploying to Netlify or anywhere else, be sure to check that your environment variables are set.
您可以將上述函數放在utils
目錄中。 它使用contentful-management
npm包來創建和發布新的評論條目,并返回承諾。 請注意,我們已將管理API密鑰指定為環境變量。 您絕對不想硬編碼那個。 部署到Netlify或其他任何地方時,請確保檢查是否已設置環境變量。
You can get your management access token from the Contentful dashboard at “Space settings” > “API keys” > “Content management tokens” > “Generate personal token”.
您可以從“內容”儀表板的“空間設置”>“ API密鑰”>“內容管理令牌”>“生成個人令牌”獲取管理訪問令牌。
Now, let’s create our Lambda-specific function:
現在,讓我們創建特定于Lambda的函數:
const createComment = require('../utils/createComment');
exports.handler = function (event, context, callback) {
const { body, authorName, subjectId, parentCommentId } = JSON.parse(event.body);
createComment(body, authorName, subjectId, parentCommentId) .then(entry => callback(null, { headers: { 'Content-Type': 'application/json' }, statusCode: 200, body: JSON.stringify({ message: 'OK' }) })) .catch(callback);};
Put this function in your Lambda source directory, and name the file with the path you’d want the URL to be, e.g. create-comment.js
. This will make your function available at the URL /.netlify/functions/create-comment
.
將此函數放在Lambda源目錄中,并用您想要URL的路徑命名文件,例如create-comment.js
。 這將使您的函數可以在URL /.netlify/functions/create-comment
。
大圖 (The big picture)
To illustrate our complete front-end and back-end setup thus far, I’ve created a create-react-app project that functions as a readily-deployable, fully-functional example.
為了說明到目前為止我們完整的前端和后端設置,我創建了一個create-react-app項目 ,該項目充當易于部署且功能齊全的示例。
Notice that in the example project’s netlify.toml
file, there’s a few more lines that you should add to your own file. Command
tells Netlify what commands to run to build the project. Publish
tells Netlify where to find the static assets ready for deployment once the build is complete. You can read more about this file in Netlify's documentation.
請注意,在示例項目的netlify.toml
文件中,應在自己的文件中添加幾行。 Command
告訴Netlify運行哪些命令來構建項目。 Publish
告訴Netlify,一旦構建完成,就可以在哪里找到準備部署的靜態資產。 您可以在Netlify的文檔中閱讀有關此文件的更多信息。
The example project is also easily cloneable and deployable to your own Netlify account via the convenient deploy button in the README.
通過自述文件中的便捷部署按鈕,示例項目也可以輕松克隆并部署到您自己的Netlify帳戶。
If you’ve been implementing this in your own project instead, head over to the Netlify dashboard and follow their straightforward instructions to set up your repo to deploy.
如果您一直在自己的項目中實施此操作,請轉到Netlify儀表板,并按照其簡單的說明設置要部署的存儲庫。
Once it’s up and running, you’ll be able to start commenting like a boss.
一旦啟動并運行,您就可以像老板一樣發表評論。
(Note: this is just a screenshot, so don’t try clicking on it ^_^)
(注意:這只是一個屏幕截圖,因此請勿嘗試單擊它^ _ ^)
直到下一次 (Until next time)
In Part 2, we’ll cover implementing logged-in commenting, as well as giving our comment box some super-cool text formatting functionality.
在第2部分中,我們將介紹如何實現登錄評論,以及為評論框提供一些超酷的文本格式設置功能。
Thanks for reading! — Shaun
謝謝閱讀! —肖恩
Originally published at shaunasaservice.com.
最初發布在shaunasaservice.com上 。
翻譯自: https://www.freecodecamp.org/news/how-you-can-build-your-own-free-serverless-comment-box-dc9d4f366d12/