反馈
接收用户反馈
概览
🌐 Overview
反馈对于了解读者的想法至关重要,并能帮助你进一步改进文档内容。
🌐 Feedback is crucial for knowing what your reader thinks, and help you to further improve documentation content.
你可以将一个简单的反馈系统与 Fumadocs 集成。
🌐 You can integrate a simple feedback system with Fumadocs.
安装
🌐 Installation
使用 Fumadocs CLI 安装它。
🌐 Install it using Fumadocs CLI.
npx @fumadocs/cli@latest add feedback页面反馈
🌐 Page Feedback
要创建页面级反馈界面,请将 <Feedback /> 组件添加到你的文档页面中:
🌐 To create a page-level feedback UI, add the <Feedback /> component to your docs page:
import { DocsPage } from 'fumadocs-ui/layout/docs/page';
import { Feedback } from '@/components/feedback/client';
import posthog from 'posthog-js';
export default async function Page() {
return (
<DocsPage>
<Feedback
onSendAction={async (feedback) => {
'use server';
await posthog.capture('on_rate_docs', feedback);
}}
/>
</DocsPage>
);
}onSendAction:当用户提交反馈时触发。
你可以指定一个服务器动作,或任何函数(在客户端组件中)来处理用户反馈。例如,将用户反馈作为 PostHog 上的 on_rate_docs 事件上报。
🌐 You can specify a server action, or any function (in client component) to handle the user feedback.
For example, to report user feedback as a on_rate_docs event on PostHog.
反馈模块
🌐 Feedback Block
你还可以配置块级反馈(例如,为每个段落设置反馈弹出框)。
🌐 You can also configure block-level feedback (e.g. a feedback popover for each paragraph).
添加 remark-feedback-block Remark 插件:
🌐 Add the remark-feedback-block Remark plugin:
import { remarkFeedbackBlock } from 'fumadocs-core/mdx-plugins/remark-feedback-block';
import { defineConfig } from 'fumadocs-mdx/config';
export default defineConfig({
mdxOptions: {
remarkPlugins: [remarkFeedbackBlock],
},
});然后,定义 FeedbackBlock MDX 组件:
🌐 Then, define the FeedbackBlock MDX component:
import { FeedbackBlock } from '@/components/feedback/client';
import defaultComponents from 'fumadocs-ui/mdx';
import type { MDXComponents } from 'mdx/types';
export function getMDXComponents(components?: MDXComponents): MDXComponents {
return {
...defaultComponents,
FeedbackBlock: (props) => (
<FeedbackBlock {...props} onSendAction={onBlockFeedbackAction}>
{children}
</FeedbackBlock>
),
...components,
};
}onSendAction:当用户提交反馈时触发。
知道了
remark-feedback-block 根据内容和页面中的顺序生成块 ID,因此也可以从第三方服务跟踪这些块。
与 GitHub 讨论集成
🌐 Integrating with GitHub Discussion
要将你的反馈报告给 GitHub 讨论,你可以将此文件复制作为起点:
🌐 To report your feedback to GitHub Discussion, you can copy this file as a starting point:
import { App, Octokit } from 'octokit';
import {
blockFeedback,
BlockFeedback,
pageFeedback,
type ActionResponse,
type PageFeedback,
} from '@/components/feedback/schema';
export const repo = 'fumadocs';
export const owner = 'fuma-nama';
export const DocsCategory = 'Docs Feedback';
let instance: Octokit | undefined;
async function getOctokit(): Promise<Octokit> {
if (instance) return instance;
const appId = process.env.GITHUB_APP_ID;
const privateKey = process.env.GITHUB_APP_PRIVATE_KEY;
if (!appId || !privateKey) {
throw new Error('No GitHub keys provided for Github app, docs feedback feature will not work.');
}
const app = new App({
appId,
privateKey,
});
const { data } = await app.octokit.request('GET /repos/{owner}/{repo}/installation', {
owner,
repo,
headers: {
'X-GitHub-Api-Version': '2022-11-28',
},
});
instance = await app.getInstallationOctokit(data.id);
return instance;
}
interface RepositoryInfo {
id: string;
discussionCategories: {
nodes: {
id: string;
name: string;
}[];
};
}
let cachedDestination: RepositoryInfo | undefined;
async function getFeedbackDestination() {
if (cachedDestination) return cachedDestination;
const octokit = await getOctokit();
const {
repository,
}: {
repository: RepositoryInfo;
} = await octokit.graphql(`
query {
repository(owner: "${owner}", name: "${repo}") {
id
discussionCategories(first: 25) {
nodes { id name }
}
}
}
`);
return (cachedDestination = repository);
}
export async function onPageFeedbackAction(feedback: PageFeedback): Promise<ActionResponse> {
'use server';
feedback = pageFeedback.parse(feedback);
return createDiscussionThread(
feedback.url,
`[${feedback.opinion}] ${feedback.message}\n\n> Forwarded from user feedback.`,
);
}
export async function onBlockFeedbackAction(feedback: BlockFeedback): Promise<ActionResponse> {
'use server';
feedback = blockFeedback.parse(feedback);
return createDiscussionThread(
feedback.url,
`> ${feedback.blockBody ?? feedback.blockId}\n\n${feedback.message}\n\n> Forwarded from user feedback.`,
);
}
async function createDiscussionThread(pageId: string, body: string) {
const octokit = await getOctokit();
const destination = await getFeedbackDestination();
const category = destination.discussionCategories.nodes.find(
(category) => category.name === DocsCategory,
);
if (!category) throw new Error(`Please create a "${DocsCategory}" category in GitHub Discussion`);
const title = `Feedback for ${pageId}`;
const {
search: {
nodes: [discussion],
},
}: {
search: {
nodes: { id: string; url: string }[];
};
} = await octokit.graphql(`
query {
search(type: DISCUSSION, query: ${JSON.stringify(`${title} in:title repo:${owner}/${repo} author:@me`)}, first: 1) {
nodes {
... on Discussion { id, url }
}
}
}`);
if (discussion) {
const result: {
addDiscussionComment: {
comment: { id: string; url: string };
};
} = await octokit.graphql(`
mutation {
addDiscussionComment(input: { body: ${JSON.stringify(body)}, discussionId: "${discussion.id}" }) {
comment { id, url }
}
}`);
return {
githubUrl: result.addDiscussionComment.comment.url,
};
} else {
const result: {
discussion: { id: string; url: string };
} = await octokit.graphql(`
mutation {
createDiscussion(input: { repositoryId: "${destination.id}", categoryId: "${category.id}", body: ${JSON.stringify(body)}, title: ${JSON.stringify(title)} }) {
discussion { id, url }
}
}`);
return {
githubUrl: result.discussion.url,
};
}
}- 创建你自己的 GitHub 应用并获取其应用 ID 和私钥。
- 填写必需的环境变量。
- 替换类似
owner、repo和DocsCategory的常量。 - 在你的反馈组件中使用
onPageFeedbackAction和onBlockFeedbackAction。
Last updated on
