feat: tweet embed (#488)

Co-authored-by: DIYgod <i@diygod.me>
This commit is contained in:
birdgg 2023-05-09 23:28:20 +08:00 committed by GitHub
parent 32e82e69ba
commit 21cbd12e00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 92 additions and 1 deletions

View File

@ -92,7 +92,11 @@ module.exports = withBundleAnalyzer(
dangerouslyAllowSVG: true,
contentSecurityPolicy:
"default-src 'self'; script-src 'none'; sandbox; style-src 'unsafe-inline';",
remotePatterns: [{ hostname: "**" }],
remotePatterns: [
{ hostname: "**" },
{ protocol: "https", hostname: "pbs.twimg.com" },
{ protocol: "https", hostname: "abs.twimg.com" },
],
},
async generateBuildId() {

View File

@ -105,6 +105,7 @@
"react-popper": "^2.3.0",
"react-sortablejs": "6.1.4",
"react-string-replace": "^1.1.0",
"react-tweet": "^2.0.1",
"react-virtuoso": "4.3.6",
"refractor": "4.8.1",
"rehype-autolink-headings": "6.1.1",

View File

@ -232,6 +232,9 @@ dependencies:
react-string-replace:
specifier: ^1.1.0
version: 1.1.0
react-tweet:
specifier: ^2.0.1
version: 2.0.1(react-dom@18.2.0)(react@18.2.0)
react-virtuoso:
specifier: 4.3.6
version: 4.3.6(react-dom@18.2.0)(react@18.2.0)
@ -4322,6 +4325,12 @@ packages:
string.prototype.matchall: 4.0.8
dev: true
/@swc/helpers@0.4.14:
resolution: {integrity: sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==}
dependencies:
tslib: 2.5.0
dev: false
/@swc/helpers@0.5.1:
resolution: {integrity: sha512-sJ902EfIzn1Fa+qYmjdQqh8tPsoxyBz+8yBKC2HKUxyezKJFwPGOn7pv4WY6QuQW//ySQi5lJjA/ZT9sNWWNTg==}
dependencies:
@ -6909,6 +6918,13 @@ packages:
whatwg-url: 12.0.1
dev: false
/date-fns@2.30.0:
resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==}
engines: {node: '>=0.11'}
dependencies:
'@babel/runtime': 7.21.5
dev: false
/dayjs@1.11.7:
resolution: {integrity: sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ==}
dev: false
@ -11605,6 +11621,20 @@ packages:
react-dom: 18.2.0(react@18.2.0)
dev: false
/react-tweet@2.0.1(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-r8Yrv+cW8ajpCcT2AJCIji9crbbVxiaYSagqU4IW4Btsm3b3Ui65pFOV9gZmnt2xorn8L98kqj8REEuGxwnaeA==}
peerDependencies:
react: '>= 18.0.0'
react-dom: '>= 18.0.0'
dependencies:
'@swc/helpers': 0.4.14
clsx: 1.2.1
date-fns: 2.30.0
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
swr: 2.1.5(react@18.2.0)
dev: false
/react-virtuoso@4.3.6(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-t2yzA9QKwMrcECLytVfZcBZMusuQ9Ahgt62SL8uC/sYm9Sk/h14sYTJC0MVO6CAtl3BzKiLKSC79Vp+W3KOXMg==}
engines: {node: '>=10'}
@ -12673,6 +12703,15 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
/swr@2.1.5(react@18.2.0):
resolution: {integrity: sha512-/OhfZMcEpuz77KavXST5q6XE9nrOBOVcBLWjMT+oAE/kQHyE3PASrevXCtQDZ8aamntOfFkbVJp7Il9tNBQWrw==}
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0
dependencies:
react: 18.2.0
use-sync-external-store: 1.2.0(react@18.2.0)
dev: false
/symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
dev: false

View File

@ -0,0 +1,12 @@
import Image from "next/image"
import { Tweet as ReactTweet } from "react-tweet"
import type { TweetComponents } from "react-tweet"
const components: TweetComponents = {
AvatarImg: (props) => <Image {...props} />,
MediaImg: (props) => <Image {...props} fill unoptimized />,
}
export function Tweet({ id }: { id: string }) {
return <ReactTweet id={id} components={components} />
}

View File

@ -32,6 +32,7 @@ import { APlayer } from "~/components/ui/APlayer"
import { ZoomedImage } from "~/components/ui/Image"
import { Mention } from "~/components/ui/Mention"
import { Mermaid } from "~/components/ui/Mermaid"
import { Tweet } from "~/components/ui/Tweet"
import { rehypeAudio } from "./rehype-audio"
import {
@ -41,6 +42,7 @@ import {
} from "./rehype-custom-wrapper"
import { rehypeImage } from "./rehype-image"
import { rehypeTable } from "./rehype-table"
import { rehypeTweet } from "./rehype-tweet"
import { rehypeWrapCode } from "./rehype-wrap-code"
import { rehypeExternalLink } from "./rehyper-external-link"
import { remarkCallout } from "./remark-callout"
@ -165,6 +167,7 @@ export const renderPageContent = (
.use(rehypeExternalLink)
.use(rehypeWrapCode)
.use(rehypeInferDescriptionMeta)
.use(rehypeTweet)
.use(rehypeRewrite, {
selector: "p, li",
rewrite: (node: any) => {
@ -213,6 +216,7 @@ export const renderPageContent = (
mention: Mention,
mermaid: Mermaid,
audio: APlayer,
tweet: Tweet,
style: Style,
} as any,
})

View File

@ -0,0 +1,31 @@
import { Root } from "rehype-raw"
import { Plugin } from "unified"
import { visit } from "unist-util-visit"
export const rehypeTweet: Plugin<Array<void>, Root> = () => (tree) => {
visit(tree, { tagName: "a" }, (node, i, parent) => {
if (
!parent ||
!("tagName" in parent) ||
parent.tagName !== "p" ||
parent.children.length > 1
)
return
const tweetRegex =
/^https?:\/\/(?:www\.)?twitter\.com\/\w+\/status\/(\d+)(?:\?.*)?(?:#.*)?$/
const href = node.properties?.href
if (typeof href === "string") {
const match = href.match(tweetRegex)
if (match && match[0] === href) {
const tweetId = match[1]
node.tagName = "tweet"
node.properties = {
id: tweetId,
}
}
}
})
}