mirror of
https://github.com/d0zingcat/NotionNext.git
synced 2026-05-13 23:16:47 +00:00
Merge pull request #949 from siygle/webmention
feat: Webmention comment system
This commit is contained in:
@@ -177,6 +177,21 @@ const BLOG = {
|
||||
COMMENT_WALINE_SERVER_URL: process.env.NEXT_PUBLIC_WALINE_SERVER_URL || '', // 请配置完整的Waline评论地址 例如 hhttps://preview-waline.tangly1024.com @see https://waline.js.org/guide/get-started.html
|
||||
COMMENT_WALINE_RECENT: process.env.NEXT_PUBLIC_WALINE_RECENT || false, // 最新评论
|
||||
|
||||
// 此评论系统基于WebMention,细节可参考https://webmention.io
|
||||
// 它是一个基于IndieWeb理念的开放式评论系统,下方COMMENT_WEBMENTION包含的属性皆需配置:
|
||||
// ENABLE: 是否开启
|
||||
// AUTH: Webmention使用的IndieLogin,可使用Twitter或Github个人页面连结
|
||||
// HOSTNAME: Webmention绑定之网域,通常即为本站网址
|
||||
// TWITTER_USERNAME: 评论显示区域需要的资讯
|
||||
// TOKEN: Webmention的API token
|
||||
COMMENT_WEBMENTION: {
|
||||
ENABLE: process.env.NEXT_PUBLIC_WEBMENTION_ENABLE || false,
|
||||
AUTH: process.env.NEXT_PUBLIC_WEBMENTION_AUTH || '',
|
||||
HOSTNAME: process.env.NEXT_PUBLIC_WEBMENTION_HOSTNAME || '',
|
||||
TWITTER_USERNAME: process.env.NEXT_PUBLIC_TWITTER_USERNAME || '',
|
||||
TOKEN: process.env.NEXT_PUBLIC_WEBMENTION_TOKEN || ''
|
||||
},
|
||||
|
||||
// <---- 评论插件
|
||||
|
||||
// ----> 站点统计
|
||||
|
||||
@@ -43,6 +43,12 @@ const GiscusComponent = dynamic(
|
||||
},
|
||||
{ ssr: false }
|
||||
)
|
||||
const WebMentionComponent = dynamic(
|
||||
() => {
|
||||
return import('@/components/WebMention')
|
||||
},
|
||||
{ ssr: false }
|
||||
)
|
||||
|
||||
const ValineComponent = dynamic(() => import('@/components/ValineComponent'), {
|
||||
ssr: false
|
||||
@@ -100,6 +106,10 @@ const Comment = ({ frontMatter }) => {
|
||||
{BLOG.COMMENT_GITALK_CLIENT_ID && (<div key='GitTalk'>
|
||||
<GitalkComponent frontMatter={frontMatter}/>
|
||||
</div>)}
|
||||
|
||||
{BLOG.COMMENT_WEBMENTION.ENABLE && (<div key='WebMention'>
|
||||
<WebMentionComponent frontMatter={frontMatter} className="px-2" />
|
||||
</div>)}
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -44,6 +44,16 @@ const CommonHead = ({ meta, children }) => {
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:title" content={title} />
|
||||
|
||||
{BLOG.COMMENT_WEBMENTION.ENABLE && (
|
||||
<>
|
||||
<link rel="webmention" href={`https://webmention.io/${BLOG.COMMENT_WEBMENTION.HOSTNAME}/webmention`} />
|
||||
<link rel="pingback" href={`https://webmention.io/${BLOG.COMMENT_WEBMENTION.HOSTNAME}/xmlrpc`} />
|
||||
</>
|
||||
)}
|
||||
{BLOG.COMMENT_WEBMENTION.ENABLE && BLOG.COMMENT_WEBMENTION.AUTH !== '' && (
|
||||
<link href={BLOG.COMMENT_WEBMENTION.AUTH} rel="me" />
|
||||
)}
|
||||
|
||||
{JSON.parse(BLOG.ANALYTICS_BUSUANZI_ENABLE) && <meta name="referrer" content="no-referrer-when-downgrade" />}
|
||||
{meta?.type === 'Post' && (
|
||||
<>
|
||||
|
||||
173
components/WebMention.js
Normal file
173
components/WebMention.js
Normal file
@@ -0,0 +1,173 @@
|
||||
import BLOG from '@/blog.config'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import Image from 'next/image'
|
||||
|
||||
/**
|
||||
* 评论插件
|
||||
* @param issueTerm
|
||||
* @param layout
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
const WebmentionCount = ({ target }) => {
|
||||
const initialCounts = {
|
||||
count: 0,
|
||||
type: {
|
||||
like: 0,
|
||||
mention: 0,
|
||||
reply: 0,
|
||||
repost: 0
|
||||
}
|
||||
}
|
||||
const [counts, setCounts] = useState(initialCounts)
|
||||
const fetchCounts = async (target) => {
|
||||
const responseData = await fetch(`https://webmention.io/api/count.json?target=${encodeURIComponent(target)}`)
|
||||
return (responseData.json) ? await responseData.json() : responseData
|
||||
}
|
||||
useEffect(() => {
|
||||
async function getCounts() {
|
||||
const responseCounts = await fetchCounts(target)
|
||||
setCounts(responseCounts)
|
||||
}
|
||||
getCounts()
|
||||
}, [target])
|
||||
|
||||
return (
|
||||
<div className='webmention-counts'>
|
||||
{counts
|
||||
? (
|
||||
<div className='counts'>
|
||||
<span>
|
||||
<span className='count'>{counts.type.like || 0}</span>Likes
|
||||
</span>
|
||||
<span>
|
||||
<span className='count'>{counts.type.reply || 0}</span>Replies
|
||||
</span>
|
||||
<span>
|
||||
<span className='count'>
|
||||
{(counts.type.repost || 0) + (counts.type.mention || 0)}
|
||||
</span>
|
||||
Mentions
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<p>Failed to fetch Webmention counts</p>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Avatar = ({ author }) => (
|
||||
<a className='avatar-wrapper' href={author.url} key={author.name}>
|
||||
<Image
|
||||
className="avatar"
|
||||
src={author.photo}
|
||||
alt={author.name}
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw,
|
||||
(max-width: 1200px) 50vw,
|
||||
33vw"
|
||||
/>
|
||||
</a>
|
||||
)
|
||||
|
||||
const WebmentionReplies = ({ target }) => {
|
||||
const [mentions, setMentions] = useState([])
|
||||
const fetchMentions = async (target) =>
|
||||
fetch(
|
||||
`https://webmention.io/api/mentions.jf2?per-page=500&target=${encodeURIComponent(target)}&token=${BLOG.COMMENT_WEBMENTION.TOKEN}`
|
||||
).then((response) => (response.json ? response.json() : response))
|
||||
useEffect(() => {
|
||||
async function getMentions() {
|
||||
const responseMentions = await fetchMentions(target)
|
||||
if (responseMentions.children) {
|
||||
setMentions(responseMentions.children)
|
||||
}
|
||||
}
|
||||
|
||||
getMentions()
|
||||
}, [target])
|
||||
|
||||
const distinctMentions = [
|
||||
...new Map(mentions.map((item) => [item.author.url, item])).values()
|
||||
].sort((a, b) => new Date(a['wm-received']) - new Date(b['wm-received']))
|
||||
|
||||
const replies = mentions.filter(
|
||||
(mention) => 'in-reply-to' in mention && 'content' in mention
|
||||
)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p>
|
||||
{distinctMentions.length > 0
|
||||
? `Already ${distinctMentions.length} people liked, shared or talked about this article:`
|
||||
: 'Be the first one to share this article!'}
|
||||
</p>
|
||||
<div className='webmention-avatars'>
|
||||
{distinctMentions.map((reply) => (
|
||||
<Avatar key={reply.author.name} author={reply.author} />
|
||||
))}
|
||||
</div>
|
||||
{replies && replies.length
|
||||
? (
|
||||
<div className='webmention-replies'>
|
||||
<h4>Replies</h4>
|
||||
<ul className='replies'>
|
||||
{replies.map((reply) => (
|
||||
<li className='reply' key={reply.content.text}>
|
||||
<div>
|
||||
<Avatar key={reply.author.name} author={reply.author} />
|
||||
</div>
|
||||
<div className='text'>
|
||||
<p className='reply-author-name'>{reply.author.name}</p>
|
||||
<p className='reply-content'>{reply.content.text}</p>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const WebMentionBlock = ({ frontMatter }) => {
|
||||
const router = useRouter()
|
||||
const url = `https://${BLOG.COMMENT_WEBMENTION.HOSTNAME}${router.asPath}`
|
||||
const tweet = `${frontMatter.title} by @${BLOG.COMMENT_WEBMENTION.TWITTER_USERNAME} ${url}`
|
||||
|
||||
return (
|
||||
<div className='webmention-block'>
|
||||
<h1 className='webmention-header'>
|
||||
powered by <a href="https://webmention.io" target='_blank' rel='noreferrer'>WebMention.io</a>
|
||||
</h1>
|
||||
<div className='webmention-block-intro'>
|
||||
You can{' '}
|
||||
<a
|
||||
target="_blank"
|
||||
id='tweet-post-url'
|
||||
href={`https://twitter.com/intent/tweet?text=${encodeURIComponent(tweet)}`}
|
||||
rel="noopener noreferrer"
|
||||
>tweet this post</a>{' '}
|
||||
or{' '}
|
||||
<a
|
||||
target='_blank'
|
||||
id='tweet-discuss-url'
|
||||
href={`https://www.twitter.com/search?q=${url}`}
|
||||
rel='noopener noreferrer'
|
||||
>discuss it on Twitter</a>
|
||||
, the comments will show up here.
|
||||
</div>
|
||||
<div className='webmention-info'>
|
||||
<WebmentionCount target={url} />
|
||||
<WebmentionReplies target={url} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default WebMentionBlock
|
||||
@@ -13,7 +13,8 @@ module.exports = withBundleAnalyzer({
|
||||
'avatars.githubusercontent.com',
|
||||
'images.unsplash.com',
|
||||
'source.unsplash.com',
|
||||
'p1.qhimg.com'
|
||||
'p1.qhimg.com',
|
||||
'webmention.io'
|
||||
]
|
||||
},
|
||||
// 默认将feed重定向至 /public/rss/feed.xml
|
||||
|
||||
@@ -224,6 +224,90 @@ nav {
|
||||
@apply text-blue-700
|
||||
}
|
||||
|
||||
/* Webmention style */
|
||||
.webmention-block {
|
||||
background: rgba(0, 116, 222, .2);
|
||||
padding: 1rem 2rem;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.webmention-header {
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
.webmention-block-intro a {
|
||||
color: #0000EE;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.webmention {
|
||||
margin-top: 1rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.webmention-counts {
|
||||
padding: 16px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.webmention-counts .count {
|
||||
font-weight: bold;
|
||||
margin-right: .2rem;
|
||||
}
|
||||
|
||||
/* .webmention-counts .counts > span {
|
||||
margin-right: .8rem;
|
||||
} */
|
||||
.webmention-counts .counts>span:not(:last-child):after {
|
||||
content: " • ";
|
||||
}
|
||||
|
||||
a.avatar-wrapper {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.webmention-avatars .avatar-wrapper {
|
||||
margin-right: -8px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
border-radius: 50%;
|
||||
margin: 0;
|
||||
border: 3px solid rgba(0, 116, 222, .5);
|
||||
}
|
||||
|
||||
.replies {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.reply {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
align-items: flex-start;
|
||||
margin-top: .6rem;
|
||||
}
|
||||
|
||||
.reply p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.reply .text {
|
||||
margin-left: 1rem;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.reply-author-name {
|
||||
font-weight: 500;
|
||||
/* 最多显示4行文字 */
|
||||
.p-4-lines {
|
||||
display: -webkit-box;
|
||||
|
||||
Reference in New Issue
Block a user