整合评论
123
abc
sdf
wec
- 如果“评论”作为一个独立的项目。使用自己的依赖。
git clone --depth=1 -b ghost-5.116.2-next git@github.com:aidabo/Ghost.git ghostxx
cd .\ghostxx\apps\comments-ui\
// 似乎不能将这个项目单独使用。它好像依赖外部整个项目。
yarn
yarn build
此时,主要观察umd文件夹里生成了umd格式的js,可以通过script标签使用。js内容里包含react本体等。
- 修改它,让其可以通过import来使用。
//修改 vite.config.ts (修改入口,格式,除外的东西等)
build: {
reportCompressedSize: false,
outDir: resolve(__dirname, 'dist'), // 改成通用输出目录
emptyOutDir: true,
minify: true,
sourcemap: true,
cssCodeSplit: true,
lib: {
entry: resolve(__dirname, 'src/App.tsx'),
formats: ['es', 'cjs'],
name: pkg.name,
fileName(format) {
if (format === 'umd') {
return `${outputFileName}.min.js`;
}
return `${outputFileName}.js`;
}
},
rollupOptions: {
external: [
'react',
'react-dom',
'react/jsx-runtime'
],
output: {
}
},
commonjsOptions: {
include: [/ghost/, /node_modules/],
dynamicRequireRoot: '../../',
dynamicRequireTargets: SUPPORTED_LOCALES.map(locale => `../../ghost/i18n/locales/${locale}/comments.json`)
}
},
11111
- 改package.json
"module": "./dist/comments-ui.js",
"exports": {
".": {
"import": "./dist/comments-ui.js"
}
},
- 使得这个项目可以被我们的项目参照。
yarn link
还修改文件。src\utils\options.ts 因为这样就不能像以前一样在script里传参了,先写上。
import React, {useMemo} from 'react';
import {CommentsOptions} from '../AppContext';
export function useOptions(scriptTag: HTMLElement) {
const buildOptions = React.useCallback(() => {
/**
* @type {HTMLElement}
*
* 哦,大概是这样的。
*/
const siteUrl = "http://localhost:3000/";
const apiKey = "cedbaa7390f9be0dc28f3c03ed";
const apiUrl = "http://localhost:3000/ghost/api/content/";
const adminUrl = "https://ghost2.240302.xyz/ghost/";
const postId = '689af6d105e5cd0001d0a515';
const colorScheme = "auto";
const avatarSaturation = undefined;
const accentColor = '#000000';
const commentsEnabled = "all";
const title = null; // Null means use the default title. Missing = no title.
const showCount = true;
const publication = 'jibun-see'; // TODO: replace with dynamic data from script
const locale = 'en';
const options = {locale, siteUrl, apiKey, apiUrl, postId, adminUrl, colorScheme, avatarSaturation, accentColor, commentsEnabled, title, showCount, publication};
return options;
}, [scriptTag]);
const initialOptions = useMemo(() => buildOptions(), []);
const [options, setOptions] = React.useState<CommentsOptions>(initialOptions);
React.useEffect(() => {
const observer = new MutationObserver((mutationList) => {
if (mutationList.some(mutation => mutation.type === 'attributes')) {
setOptions(buildOptions());
}
});
// observer.observe(scriptTag, {
// attributes: true
// });
return () => {
observer.disconnect();
};
}, [scriptTag, buildOptions]);
return options;
}
下面开始修改咱们项目本体。
首先link
yarn link "@tryghost/comments-ui"
然后在画面上引入。
先建一个组件。Testtest.tsx
import {App} from "@tryghost/comments-ui";
export default function Testtest() {
return (
在Top里引入。
"use client";
import dynamic from "next/dynamic";
import { Box, useTheme, useMediaQuery } from "@mui/material";
import TopBubble from "./TopBubble";
import { useState, useEffect, useRef, useCallback } from "react";
const Masonry = dynamic(() => import("@mui/lab/Masonry"), { ssr: false });
// const App = dynamic(() => import("@tryghost/comments-ui"), {
// ssr: false,
// });
const Testtest = dynamic(() => import("@/components/Testtest"), { ssr: false });
export default function Top() {
const theme = useTheme();
const hasFetchedInitial = useRef(false);
const isSmDown = useMediaQuery(theme.breakpoints.down("sm"));
const [bubbles, setBubbles] = useState<{
pc: any[];
tel: any[][];
}>({
pc: [],
tel: [[]],
});
const [excludeIds, setExcludeIds] = useState<string[]>([]);
const [totalPosts, setTotalPosts] = useState<number | null>(null);
const loadingRef = useRef(false);
const debounceTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const PCbubblesTemplate = [
{ size: 212, text: "" },
{ size: 110, text: "" },
{ size: 238, text: "" },
{ size: 150, text: "" },
{ size: 212, text: "" },
{ size: 251, text: "" },
{ size: 110, text: "" },
{ size: 260, text: "" },
{ size: 212, text: "" },
{ size: 150, text: "" },
{ size: 260, text: "" },
{ size: 150, text: "" },
{ size: 238, text: "" },
{ size: 150, text: "" },
{ size: 250, text: "" },
{ size: 150, text: "" },
{ size: 150, text: "" },
{ size: 150, text: "" },
];
const TELbubblesTemplate = [
{ size: 200, text: "" },
{ size: 150, text: "", class: "h-[130px] ml-20" },
{ size: 238, text: "", class: "-ml-14" },
{ size: 110, text: "", class: "h-[80px]" },
{ size: 260, text: "" },
{ size: 212, text: "", class: "h-[250px] mt-[150px] -ml-4" },
{ size: 150, text: "", class: "h-[150px]" },
{ size: 110, text: "", class: "h-[190px] ml-[80px]" },
{ size: 251, text: "" },
{ size: 212, text: "", class: "h-[200px] -ml-4" },
{ size: 150, text: "" },
{ size: 150, text: "", class: "ml-16" },
{ size: 260, text: "" },
{ size: 150, text: "", class: "h-[270px] mt-[100px] ml-[50px]" },
{ size: 238, text: "", class: "h-[400px] -mt-10" },
{ size: 250, text: "", class: "h-[220px] -ml-[50px]" },
{ size: 180, text: "" },
{ size: 150, text: "", class: "ml-16" },
];
const fetchPosts = useCallback(
async (isLoadMore = false) => {
if (loadingRef.current) return;
if (isLoadMore && !isSmDown) return;
if (totalPosts !== null && excludeIds.length >= totalPosts) return;
loadingRef.current = true;
try {
const response = await fetch("/api/posts/", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
limit: isLoadMore ? 18 : 10000,
excludeIds: isLoadMore ? excludeIds : [],
}),
});
const result = await response.json();
if (!response.ok) throw new Error(result.error);
const shuffledPosts = result.data;
const newPCBubbles = shuffledPosts.map((post: any, index: number) => {
const template = PCbubblesTemplate[index % PCbubblesTemplate.length];
return {
...template,
src: post?.src,
text: post?.text,
id: post?.id,
};
});
const newTELBubbles = TELbubblesTemplate.slice(
0,
Math.min(shuffledPosts.length, TELbubblesTemplate.length)
).map((template: any, index: number) => ({
...template,
src: shuffledPosts[index]?.src,
text: shuffledPosts[index]?.text,
id: shuffledPosts[index]?.id,
}));
setBubbles((prev) => ({
pc: isLoadMore ? prev.pc : newPCBubbles,
tel: isLoadMore ? [...prev.tel, newTELBubbles] : [[...newTELBubbles]],
}));
setExcludeIds((prev) =>
isLoadMore
? result.meta.excludeIds
: shuffledPosts
.slice(0, TELbubblesTemplate.length)
.map((post: any) => post.id)
);
setTotalPosts(result.meta.total);
} catch (error) {
console.error("Top -> fetchPosts:", error);
if (!isLoadMore) setBubbles({ pc: [], tel: [[]] });
} finally {
loadingRef.current = false;
}
},
[isSmDown, excludeIds, totalPosts, PCbubblesTemplate, TELbubblesTemplate]
);
useEffect(() => {
if (hasFetchedInitial.current) return;
const timer = setTimeout(() => {
fetchPosts(false);
hasFetchedInitial.current = true;
}, 0);
return () => clearTimeout(timer);
}, [fetchPosts, isSmDown]);
useEffect(() => {
if (!isSmDown) return;
const handleScroll = () => {
const scrollHeight = document.documentElement.scrollHeight;
const scrollTop = document.documentElement.scrollTop;
const clientHeight = document.documentElement.clientHeight;
const distanceToBottom = scrollHeight - scrollTop - clientHeight;
if (distanceToBottom < 2000) {
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
debounceTimeoutRef.current = setTimeout(() => {
if (!loadingRef.current) {
fetchPosts(true);
}
}, 500);
}
};
window.addEventListener("scroll", handleScroll);
return () => {
window.removeEventListener("scroll", handleScroll);
if (debounceTimeoutRef.current) {
clearTimeout(debounceTimeoutRef.current);
}
};
}, [isSmDown, fetchPosts]);
return (
<div
style={{
position: "absolute",
top: "20lvh",
left: "50%",
transform: "translateX(-50%)",
width: "90%",
maxWidth: "450px",
zIndex: 30,
}}
>
<img
src="/images/jiBUN-See.png"
alt="JiBUN"
style={{ width: "100%", height: "auto" }}
/>
{!isSmDown ? (
<Masonry
columns={{ sm: 3, md: 4, lg: 5, xl: 6 }}
spacing={4}
sx={{ pt: 3 }}
>
{bubbles.pc.map((bubble: any, index: number) => (
<Box
key={index}
className={`item ${bubble.class || ""}`}
sx={{ mb: 5 }}
>
<TopBubble
id={bubble.id}
src={bubble.src}
size={bubble.size}
text={bubble.text}
className="top-bubble"
/>
</Box>
))}
</Masonry>
) : (
<Box sx={{ width: "100%" }}>
{bubbles.tel.map((pageBubbles, pageIndex) => (
<Masonry
key={pageIndex}
columns={2}
spacing={4}
sx={{ pt: 3, mb: -8, width: "100%" }}
>
{pageBubbles.map((bubble: any, index: number) => (
<Box
key={`${pageIndex}-${index}`}
className={`item ${bubble.class || ""}`}
sx={{ mb: 5 }}
>
<TopBubble
id={bubble.id}
src={bubble.src}
size={bubble.size}
text={bubble.text}
className="top-bubble"
/>
</Box>
))}
</Masonry>
))}
</Box>
)}
</Box>
</div>
);
}
最后在next.config里加一个转发规则。
async rewrites() {
return [
{
source: "/ghost/api/:path*/",
destination: ${process.env.GHOST_ADMIN_API_URL}/ghost/api/:path*/,
},
{
source: "/members/api/:path*/",
destination: ${process.env.GHOST_ADMIN_API_URL}/members/api/:path*/,
}
];
},

到处有很多警告,还没一个一个调查。