add 0125-2239
This commit is contained in:
parent
8f43808ea2
commit
b8a0721358
|
@ -30,12 +30,13 @@
|
||||||
"@floating-ui/react": "^0.26.25",
|
"@floating-ui/react": "^0.26.25",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@hookform/resolvers": "^3.9.1",
|
"@hookform/resolvers": "^3.9.1",
|
||||||
|
"@multiavatar/multiavatar": "^1.0.7",
|
||||||
"@nice/client": "workspace:^",
|
"@nice/client": "workspace:^",
|
||||||
"@nice/common": "workspace:^",
|
"@nice/common": "workspace:^",
|
||||||
"@nice/iconer": "workspace:^",
|
"@nice/iconer": "workspace:^",
|
||||||
"@nice/theme": "workspace:^",
|
"@nice/theme": "workspace:^",
|
||||||
"@nice/utils": "workspace:^",
|
|
||||||
"@nice/ui": "workspace:^",
|
"@nice/ui": "workspace:^",
|
||||||
|
"@nice/utils": "workspace:^",
|
||||||
"@tanstack/query-async-storage-persister": "^5.51.9",
|
"@tanstack/query-async-storage-persister": "^5.51.9",
|
||||||
"@tanstack/react-query": "^5.51.21",
|
"@tanstack/react-query": "^5.51.21",
|
||||||
"@tanstack/react-query-persist-client": "^5.51.9",
|
"@tanstack/react-query-persist-client": "^5.51.9",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import {
|
import {
|
||||||
UploadOutlined,
|
UploadOutlined,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
|
@ -23,12 +23,13 @@ interface UploadingFile {
|
||||||
export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
const { handleFileUpload } = useTusUpload();
|
const { handleFileUpload } = useTusUpload();
|
||||||
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
|
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
|
||||||
const [completedFiles, setCompletedFiles] = useState<UploadingFile[]>(() =>
|
const [completedFiles, setCompletedFiles] = useState<UploadingFile[]>(
|
||||||
value?.map(fileId => ({
|
() =>
|
||||||
name: `File ${fileId}`, // We could fetch the actual filename if needed
|
value?.map((fileId) => ({
|
||||||
|
name: `文件 ${fileId}`, // 可以根据需要获取实际文件名
|
||||||
progress: 1,
|
progress: 1,
|
||||||
status: 'done' as const,
|
status: "done" as const,
|
||||||
fileId
|
fileId,
|
||||||
})) || []
|
})) || []
|
||||||
);
|
);
|
||||||
const [uploadResults, setUploadResults] = useState<string[]>(value || []);
|
const [uploadResults, setUploadResults] = useState<string[]>(value || []);
|
||||||
|
@ -38,7 +39,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
setCompletedFiles((prev) =>
|
setCompletedFiles((prev) =>
|
||||||
prev.filter((f) => f.fileId !== fileId)
|
prev.filter((f) => f.fileId !== fileId)
|
||||||
);
|
);
|
||||||
const newResults = uploadResults.filter(id => id !== fileId);
|
const newResults = uploadResults.filter((id) => id !== fileId);
|
||||||
setUploadResults(newResults);
|
setUploadResults(newResults);
|
||||||
onChange?.(newResults);
|
onChange?.(newResults);
|
||||||
},
|
},
|
||||||
|
@ -48,10 +49,10 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
async (fileList: UploadFile | UploadFile[]) => {
|
async (fileList: UploadFile | UploadFile[]) => {
|
||||||
const files = Array.isArray(fileList) ? fileList : [fileList];
|
const files = Array.isArray(fileList) ? fileList : [fileList];
|
||||||
console.log("files", files);
|
console.log("文件", files);
|
||||||
// 验证文件对象
|
|
||||||
if (!files.every((f) => f instanceof File)) {
|
if (!files.every((f) => f instanceof File)) {
|
||||||
message.error("Invalid file format");
|
message.error("无效的文件格式");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,20 +62,19 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
status: "uploading" as const,
|
status: "uploading" as const,
|
||||||
}));
|
}));
|
||||||
setUploadingFiles((prev) => [...prev, ...newFiles]);
|
setUploadingFiles((prev) => [...prev, ...newFiles]);
|
||||||
const newUploadResults: string[] = [];
|
|
||||||
|
|
||||||
|
const newUploadResults: string[] = [];
|
||||||
try {
|
try {
|
||||||
for (const [index, f] of files.entries()) {
|
for (const [index, f] of files.entries()) {
|
||||||
if (!f) {
|
if (!f) {
|
||||||
throw new Error(`File ${f.name} is invalid`);
|
throw new Error(`文件 ${f.name} 无效`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileId = await new Promise<string>(
|
const fileId = await new Promise<string>(
|
||||||
(resolve, reject) => {
|
(resolve, reject) => {
|
||||||
handleFileUpload(
|
handleFileUpload(
|
||||||
f as File,
|
f as File,
|
||||||
(result) => {
|
(result) => {
|
||||||
console.log("Upload success:", result);
|
console.log("上传成功:", result);
|
||||||
const completedFile = {
|
const completedFile = {
|
||||||
name: f.name,
|
name: f.name,
|
||||||
progress: 1,
|
progress: 1,
|
||||||
|
@ -91,7 +91,7 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
resolve(result.fileId);
|
resolve(result.fileId);
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error("Upload error:", error);
|
console.error("上传错误:", error);
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -100,60 +100,58 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
newUploadResults.push(fileId);
|
newUploadResults.push(fileId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update with all uploaded files
|
const newValue = Array.from(
|
||||||
const newValue = Array.from(new Set([...uploadResults, ...newUploadResults]));
|
new Set([...uploadResults, ...newUploadResults])
|
||||||
|
);
|
||||||
setUploadResults(newValue);
|
setUploadResults(newValue);
|
||||||
onChange?.(newValue);
|
onChange?.(newValue);
|
||||||
message.success(`${files.length} files uploaded successfully`);
|
message.success(`${files.length} 个文件上传成功`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Upload error details:", error);
|
console.error("上传错误详情:", error);
|
||||||
message.error(
|
message.error(
|
||||||
`Upload failed: ${error instanceof Error ? error.message : "Unknown error"}`
|
`上传失败: ${error instanceof Error ? error.message : "未知错误"}`
|
||||||
);
|
);
|
||||||
setUploadingFiles((prev) =>
|
setUploadingFiles((prev) =>
|
||||||
prev.map((f) => ({ ...f, status: "error" }))
|
prev.map((f) => ({ ...f, status: "error" }))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
[uploadResults, onChange, handleFileUpload]
|
[uploadResults, onChange, handleFileUpload]
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-1">
|
||||||
<Upload.Dragger
|
<Upload.Dragger
|
||||||
name="files"
|
name="files"
|
||||||
multiple
|
multiple
|
||||||
showUploadList={false}
|
showUploadList={false}
|
||||||
beforeUpload={handleChange}
|
style={{ background: "white" }}
|
||||||
style={{
|
beforeUpload={handleChange}>
|
||||||
border: "2px dashed #1677ff",
|
|
||||||
borderRadius: "8px",
|
|
||||||
backgroundColor: "#f0f8ff",
|
|
||||||
}}>
|
|
||||||
<p className="ant-upload-drag-icon">
|
<p className="ant-upload-drag-icon">
|
||||||
<UploadOutlined />
|
<UploadOutlined />
|
||||||
</p>
|
</p>
|
||||||
<p className="ant-upload-text">
|
<p className="ant-upload-text">
|
||||||
Click or drag file to this area to upload
|
点击或拖拽文件到此区域进行上传
|
||||||
</p>
|
</p>
|
||||||
<p className="ant-upload-hint">
|
<p className="ant-upload-hint">支持单个或批量上传文件</p>
|
||||||
Support for a single or bulk upload of files
|
{/* 正在上传的文件 */}
|
||||||
</p>
|
{(uploadingFiles.length > 0 || completedFiles.length > 0) && (
|
||||||
</Upload.Dragger>
|
<div className=" p-2 border rounded bg-white mt-1">
|
||||||
|
{uploadingFiles.length > 0 &&
|
||||||
{/* Uploading Files */}
|
uploadingFiles.map((file, index) => (
|
||||||
{uploadingFiles.length > 0 && (
|
<div
|
||||||
<div className="space-y-2 p-4 border rounded">
|
key={index}
|
||||||
<div className="font-medium">Uploading Files</div>
|
className="flex flex-col gap-1">
|
||||||
{uploadingFiles.map((file, index) => (
|
|
||||||
<div key={index} className="flex flex-col gap-1">
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<div className="text-sm">{file.name}</div>
|
<div className="text-sm">
|
||||||
|
{file.name}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Progress
|
<Progress
|
||||||
percent={Math.round(file.progress * 100)}
|
percent={Math.round(
|
||||||
|
file.progress * 100
|
||||||
|
)}
|
||||||
status={
|
status={
|
||||||
file.status === "error"
|
file.status === "error"
|
||||||
? "exception"
|
? "exception"
|
||||||
|
@ -164,33 +162,31 @@ export const TusUploader = ({ value = [], onChange }: TusUploaderProps) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
{completedFiles.length > 0 &&
|
||||||
)}
|
completedFiles.map((file, index) => (
|
||||||
|
|
||||||
{/* Completed Files */}
|
|
||||||
{completedFiles.length > 0 && (
|
|
||||||
<div className="space-y-2 p-4 border rounded">
|
|
||||||
<div className="font-medium">Uploaded Files</div>
|
|
||||||
{completedFiles.map((file, index) => (
|
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="flex items-center justify-between gap-2 py-2">
|
className="flex items-center justify-between gap-2 py-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CheckCircleOutlined className="text-green-500" />
|
<CheckCircleOutlined className="text-green-500" />
|
||||||
<div className="text-sm">{file.name}</div>
|
<div className="text-sm">
|
||||||
|
{file.name}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Button
|
<Button
|
||||||
type="text"
|
type="text"
|
||||||
danger
|
danger
|
||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
file.fileId && handleRemoveFile(file.fileId)
|
file.fileId &&
|
||||||
|
handleRemoveFile(file.fileId)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</Upload.Dragger>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,7 +4,7 @@ import dayjs from "dayjs";
|
||||||
|
|
||||||
import { Avatar } from "antd";
|
import { Avatar } from "antd";
|
||||||
import { useVisitor } from "@nice/client";
|
import { useVisitor } from "@nice/client";
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useEffect, useRef, useState } from "react";
|
||||||
import { PostDetailContext } from "./context/PostDetailContext";
|
import { PostDetailContext } from "./context/PostDetailContext";
|
||||||
import { LikeFilled, LikeOutlined } from "@ant-design/icons";
|
import { LikeFilled, LikeOutlined } from "@ant-design/icons";
|
||||||
import PostLikeButton from "./PostHeader/PostLikeButton";
|
import PostLikeButton from "./PostHeader/PostLikeButton";
|
||||||
|
@ -24,18 +24,18 @@ export default function PostCommentCard({
|
||||||
<motion.div
|
<motion.div
|
||||||
className="bg-white rounded-lg shadow-sm border border-slate-200 p-4"
|
className="bg-white rounded-lg shadow-sm border border-slate-200 p-4"
|
||||||
layout>
|
layout>
|
||||||
<div className="flex items-start space-x-3 gap-4">
|
<div className="flex items-start space-x-2 gap-2">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<CustomAvatar
|
<CustomAvatar
|
||||||
src={post.author?.avatar}
|
src={post.author?.avatar}
|
||||||
size={40}
|
size={50}
|
||||||
name={
|
name={!post.author?.avatar && post.author?.showname}
|
||||||
!post.author?.avatar && post.author?.showname
|
ip={post?.meta?.ip}></CustomAvatar>
|
||||||
}></CustomAvatar>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1">
|
||||||
|
<div className={`flex-1 min-w-0 `}>
|
||||||
<div className="flex flex-1 justify-between ">
|
<div className="flex flex-1 justify-between ">
|
||||||
<div className="flex space-x-2" style={{ height: 40 }}>
|
<div className="flex space-x-2">
|
||||||
<span className="flex font-medium text-slate-900">
|
<span className="flex font-medium text-slate-900">
|
||||||
{post.author?.showname || "匿名用户"}
|
{post.author?.showname || "匿名用户"}
|
||||||
</span>
|
</span>
|
||||||
|
@ -46,7 +46,7 @@ export default function PostCommentCard({
|
||||||
</span>
|
</span>
|
||||||
{isReceiverComment && (
|
{isReceiverComment && (
|
||||||
<div className=" ">
|
<div className=" ">
|
||||||
<span className=" px-2 text-sm rounded-full bg-blue-100 text-blue-800">
|
<span className=" py-0.5 px-2 text-xs rounded-full bg-blue-100 text-blue-800">
|
||||||
官方回复
|
官方回复
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -56,21 +56,25 @@ export default function PostCommentCard({
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-center items-center gap-2">
|
<div className="flex justify-center items-center gap-2">
|
||||||
<span className=" text-sm text-slate-500">{`#${index + 1}`}</span>
|
<span className=" text-sm text-slate-500">{`#${index + 1}`}</span>
|
||||||
<PostLikeButton post={post}></PostLikeButton>
|
<PostLikeButton
|
||||||
|
post={post}></PostLikeButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="ql-editor text-slate-800"
|
className="ql-editor text-slate-800 mt-1"
|
||||||
style={{
|
style={{
|
||||||
padding: 0,
|
padding: 0,
|
||||||
}}
|
}}
|
||||||
dangerouslySetInnerHTML={{ __html: post.content || "" }}
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: post.content || "",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<PostResources post={post}></PostResources>
|
<PostResources post={post}></PostResources>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
import React, { useContext, useState } from "react";
|
import React, { useContext, useState } from "react";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
import { Button, Tabs } from "antd";
|
||||||
import { Button } from "antd";
|
|
||||||
import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor";
|
import QuillEditor from "@web/src/components/common/editor/quill/QuillEditor";
|
||||||
import { PostDetailContext } from "./context/PostDetailContext";
|
import { PostDetailContext } from "./context/PostDetailContext";
|
||||||
import { usePost } from "@nice/client";
|
import { usePost } from "@nice/client";
|
||||||
|
@ -10,12 +9,16 @@ import toast from "react-hot-toast";
|
||||||
import { isContentEmpty } from "./utils";
|
import { isContentEmpty } from "./utils";
|
||||||
import { SendOutlined } from "@ant-design/icons";
|
import { SendOutlined } from "@ant-design/icons";
|
||||||
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
import { TusUploader } from "@web/src/components/common/uploader/TusUploader";
|
||||||
|
|
||||||
|
const { TabPane } = Tabs;
|
||||||
|
|
||||||
export default function PostCommentEditor() {
|
export default function PostCommentEditor() {
|
||||||
const { post } = useContext(PostDetailContext);
|
const { post } = useContext(PostDetailContext);
|
||||||
const [content, setContent] = useState("");
|
const [content, setContent] = useState("");
|
||||||
const [isPreview, setIsPreview] = useState(false);
|
const [isPreview, setIsPreview] = useState(false);
|
||||||
const [fileIds, setFileIds] = useState<string[]>([]);
|
const [fileIds, setFileIds] = useState<string[]>([]);
|
||||||
const { create } = usePost();
|
const { create } = usePost();
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (isContentEmpty(content)) {
|
if (isContentEmpty(content)) {
|
||||||
|
@ -49,13 +52,11 @@ export default function PostCommentEditor() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<div className="w-full mx-auto mt-1">
|
||||||
initial={{ opacity: 0, y: 20 }}
|
<form onSubmit={handleSubmit} className="space-y-2">
|
||||||
animate={{ opacity: 1, y: 0 }}
|
<Tabs defaultActiveKey="1">
|
||||||
className="w-full mx-auto mt-4">
|
<TabPane tab="回复" key="1">
|
||||||
<form onSubmit={handleSubmit} className="space-y-3">
|
|
||||||
<div className="relative rounded-lg border border-slate-200 bg-white shadow-sm">
|
<div className="relative rounded-lg border border-slate-200 bg-white shadow-sm">
|
||||||
{!isPreview ? (
|
|
||||||
<QuillEditor
|
<QuillEditor
|
||||||
value={content}
|
value={content}
|
||||||
onChange={setContent}
|
onChange={setContent}
|
||||||
|
@ -68,7 +69,10 @@ export default function PostCommentEditor() {
|
||||||
toolbar: [
|
toolbar: [
|
||||||
["bold", "italic", "strike"],
|
["bold", "italic", "strike"],
|
||||||
["blockquote", "code-block"],
|
["blockquote", "code-block"],
|
||||||
[{ list: "ordered" }, { list: "bullet" }],
|
[
|
||||||
|
{ list: "ordered" },
|
||||||
|
{ list: "bullet" },
|
||||||
|
],
|
||||||
["link"],
|
["link"],
|
||||||
["clean"],
|
["clean"],
|
||||||
],
|
],
|
||||||
|
@ -80,25 +84,19 @@ export default function PostCommentEditor() {
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
|
||||||
<motion.div
|
|
||||||
initial={{ opacity: 0 }}
|
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
className="ql-editor p-4 min-h-[120px] text-slate-800 prose prose-slate max-w-none quill-editor-container"
|
|
||||||
dangerouslySetInnerHTML={{ __html: content }}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
</TabPane>
|
||||||
|
<TabPane tab="附件" key="2">
|
||||||
<TusUploader
|
<TusUploader
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setFileIds(value);
|
setFileIds(value);
|
||||||
}}></TusUploader>
|
}}
|
||||||
|
/>
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
|
||||||
<div className="flex items-center justify-end">
|
<div className="flex items-center justify-end">
|
||||||
<motion.div
|
<div>
|
||||||
whileHover={{ scale: 1.02 }}
|
|
||||||
whileTap={{ scale: 0.98 }}>
|
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
|
@ -107,9 +105,9 @@ export default function PostCommentEditor() {
|
||||||
icon={<SendOutlined />}>
|
icon={<SendOutlined />}>
|
||||||
提交
|
提交
|
||||||
</Button>
|
</Button>
|
||||||
</motion.div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</motion.div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,4 @@
|
||||||
import React, {
|
import { useContext, useMemo, useEffect } from "react";
|
||||||
useContext,
|
|
||||||
useMemo,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useCallback,
|
|
||||||
} from "react";
|
|
||||||
import { PostDetailContext } from "./context/PostDetailContext";
|
import { PostDetailContext } from "./context/PostDetailContext";
|
||||||
import { api, useVisitor } from "@nice/client";
|
import { api, useVisitor } from "@nice/client";
|
||||||
import { postDetailSelect, PostDto, PostType, Prisma } from "@nice/common";
|
import { postDetailSelect, PostDto, PostType, Prisma } from "@nice/common";
|
||||||
|
@ -12,8 +6,6 @@ import { motion, AnimatePresence } from "framer-motion";
|
||||||
import PostCommentCard from "./PostCommentCard";
|
import PostCommentCard from "./PostCommentCard";
|
||||||
import { useInView } from "react-intersection-observer";
|
import { useInView } from "react-intersection-observer";
|
||||||
import { LoadingCard } from "@web/src/components/models/post/detail/LoadingCard";
|
import { LoadingCard } from "@web/src/components/models/post/detail/LoadingCard";
|
||||||
import { Button } from "antd";
|
|
||||||
|
|
||||||
export default function PostCommentList() {
|
export default function PostCommentList() {
|
||||||
const { post } = useContext(PostDetailContext);
|
const { post } = useContext(PostDetailContext);
|
||||||
const { ref: loadMoreRef, inView } = useInView();
|
const { ref: loadMoreRef, inView } = useInView();
|
||||||
|
@ -155,7 +147,7 @@ export default function PostCommentList() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4 mt-6">
|
<div className="space-y-2 mt-2">
|
||||||
<AnimatePresence mode="popLayout">
|
<AnimatePresence mode="popLayout">
|
||||||
{items.map((comment, index) => (
|
{items.map((comment, index) => (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|
|
@ -2,48 +2,59 @@ import { useContext } from "react";
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
import { PostDetailContext } from "../context/PostDetailContext";
|
import { PostDetailContext } from "../context/PostDetailContext";
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
|
|
||||||
import { StatsSection } from "./StatsSection";
|
import { StatsSection } from "./StatsSection";
|
||||||
|
|
||||||
import PostResources from "../PostResources";
|
import PostResources from "../PostResources";
|
||||||
|
|
||||||
export default function Content() {
|
export default function Content() {
|
||||||
const { post, user } = useContext(PostDetailContext);
|
const { post, user } = useContext(PostDetailContext);
|
||||||
const [isExpanded, setIsExpanded] = useState(false);
|
const [isExpanded, setIsExpanded] = useState(false);
|
||||||
const contentRef = useRef(null);
|
const contentWrapperRef = useRef(null);
|
||||||
const [shouldCollapse, setShouldCollapse] = useState(false);
|
const [shouldCollapse, setShouldCollapse] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contentRef.current) {
|
if (contentWrapperRef.current) {
|
||||||
const shouldCollapse = contentRef.current.scrollHeight > 300; // 300px threshold
|
const shouldCollapse = contentWrapperRef.current.scrollHeight > 100;
|
||||||
setShouldCollapse(shouldCollapse);
|
setShouldCollapse(shouldCollapse);
|
||||||
}
|
}
|
||||||
}, [post?.content]);
|
}, [post?.content]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative bg-white rounded-b-xl p-6 pt-2 shadow-lg border border-[#97A9C4]/30">
|
<div className="relative bg-white rounded-b-xl p-4 pt-2 shadow-lg border border-[#97A9C4]/30">
|
||||||
<motion.div
|
<div className="text-secondary-700">
|
||||||
initial={{ opacity: 0 }}
|
{/* 包装整个内容区域的容器 */}
|
||||||
animate={{ opacity: 1 }}
|
|
||||||
transition={{ delay: 0.6 }}
|
|
||||||
className="text-secondary-700">
|
|
||||||
<div
|
<div
|
||||||
ref={contentRef}
|
ref={contentWrapperRef}
|
||||||
className={`ql-editor p-0 space-y-1 leading-relaxed duration-300 ${
|
className={`duration-300 ${
|
||||||
shouldCollapse && !isExpanded
|
shouldCollapse && !isExpanded
|
||||||
? "max-h-[300px] overflow-hidden"
|
? "max-h-[100px] overflow-hidden relative"
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}>
|
||||||
|
{/* 内容区域 */}
|
||||||
|
<div
|
||||||
|
className="ql-editor p-0 space-y-1 leading-relaxed"
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: post?.content || "",
|
__html: post?.content || "",
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* PostResources 组件 */}
|
||||||
|
<PostResources post={post} />
|
||||||
|
|
||||||
|
{/* 渐变遮罩 */}
|
||||||
|
{shouldCollapse && !isExpanded && (
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 h-20 bg-gradient-to-t from-white to-transparent" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 展开/收起按钮 */}
|
||||||
{shouldCollapse && (
|
{shouldCollapse && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setIsExpanded(!isExpanded)}
|
onClick={() => setIsExpanded(!isExpanded)}
|
||||||
className="mt-2 text-blue-500 hover:text-blue-700">
|
className="mt-2 text-blue-500 hover:text-blue-700">
|
||||||
{isExpanded ? "Collapse" : "Expand"}
|
{isExpanded ? "收起" : "展开"}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
<PostResources post={post} />
|
</div>
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
{/* Stats Section */}
|
{/* Stats Section */}
|
||||||
<StatsSection />
|
<StatsSection />
|
||||||
|
|
|
@ -17,7 +17,7 @@ const { Title, Paragraph, Text } = Typography;
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const { post, user } = useContext(PostDetailContext);
|
const { post, user } = useContext(PostDetailContext);
|
||||||
return (
|
return (
|
||||||
<header className="rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-6 relative">
|
<header className="rounded-t-xl bg-gradient-to-r from-primary to-primary-400 text-white p-4 relative">
|
||||||
{/* 右上角标签 */}
|
{/* 右上角标签 */}
|
||||||
{/* <CornerBadge type="state" value={post?.state}></CornerBadge> */}
|
{/* <CornerBadge type="state" value={post?.state}></CornerBadge> */}
|
||||||
|
|
||||||
|
|
|
@ -28,12 +28,12 @@ export default function PostResources({ post }: { post: PostDto }) {
|
||||||
}, [post]);
|
}, [post]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col ">
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col ">
|
||||||
{resources
|
{resources
|
||||||
?.filter((resource) => resource.isImage)
|
?.filter((resource) => resource.isImage)
|
||||||
.map((resource) => (
|
.map((resource) => (
|
||||||
<div key={resource.url} className="mt-2">
|
<div key={resource.url} className="mt-0.5">
|
||||||
<Image
|
<Image
|
||||||
src={resource.url}
|
src={resource.url}
|
||||||
alt={resource.title}
|
alt={resource.title}
|
||||||
|
|
|
@ -40,11 +40,11 @@ export function LetterBasicForm() {
|
||||||
placeholder="请输入信件标题"
|
placeholder="请输入信件标题"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
{/* Tags Input */}
|
{/* Tags Input */}
|
||||||
<Form.Item name={["meta", "tags"]} className="mb-6">
|
<Form.Item name={["meta", "tags"]} className="mb-6">
|
||||||
<Select
|
<Select
|
||||||
mode="tags"
|
mode="tags"
|
||||||
|
showSearch={false}
|
||||||
placeholder="输入标签后按回车添加"
|
placeholder="输入标签后按回车添加"
|
||||||
value={form.getFieldValue(["meta", "tags"]) || []}
|
value={form.getFieldValue(["meta", "tags"]) || []}
|
||||||
onChange={(value) =>
|
onChange={(value) =>
|
||||||
|
@ -93,10 +93,7 @@ export function LetterBasicForm() {
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{/* Footer Actions */}
|
{/* Footer Actions */}
|
||||||
<div className="flex flex-col-reverse sm:flex-row items-center justify-between gap-4 mt-6">
|
<div className="flex flex-col-reverse sm:flex-row items-center justify-between gap-4 mt-6">
|
||||||
<Form.Item
|
<Form.Item name="isPublic" valuePropName="checked">
|
||||||
name="isPublic"
|
|
||||||
valuePropName="checked"
|
|
||||||
>
|
|
||||||
<Checkbox className="text-gray-600 hover:text-gray-900 transition-colors text-sm">
|
<Checkbox className="text-gray-600 hover:text-gray-900 transition-colors text-sm">
|
||||||
是否公开
|
是否公开
|
||||||
</Checkbox>
|
</Checkbox>
|
||||||
|
|
|
@ -1,33 +1,57 @@
|
||||||
|
import React, { useMemo } from "react";
|
||||||
import { Avatar } from "antd";
|
import { Avatar } from "antd";
|
||||||
import { AvatarProps } from "antd/lib/avatar";
|
import { AvatarProps } from "antd/lib/avatar";
|
||||||
|
import multiavatar from "@multiavatar/multiavatar";
|
||||||
|
|
||||||
interface CustomAvatarProps extends Omit<AvatarProps, "children"> {
|
interface CustomAvatarProps extends Omit<AvatarProps, "children"> {
|
||||||
src?: string;
|
src?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
ip?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function CustomAvatar({
|
export function CustomAvatar({
|
||||||
src,
|
src,
|
||||||
name,
|
name,
|
||||||
className = "",
|
className = "",
|
||||||
|
ip,
|
||||||
...props
|
...props
|
||||||
}: CustomAvatarProps) {
|
}: CustomAvatarProps) {
|
||||||
// 获取名字的第一个字符,如果没有名字则显示"匿"
|
// 获取名字的第一个字符,如果没有名字则显示"匿"
|
||||||
const firstChar = name ? name.charAt(0) : "匿";
|
const firstChar = name ? name.charAt(0) : "匿";
|
||||||
|
|
||||||
|
// 如果没有 src,且 name 不存在或为 "匿名用户",则使用 ip 生成随机头像
|
||||||
|
const generateAvatarFromIp = (ip: string) => {
|
||||||
|
// 使用 multiavatar 生成 SVG 字符串
|
||||||
|
const svgString = multiavatar(ip);
|
||||||
|
return `data:image/svg+xml;utf8,${encodeURIComponent(svgString)}`;
|
||||||
|
};
|
||||||
|
// 判断头像模式
|
||||||
|
const avatarMode = useMemo(() => {
|
||||||
|
if (src) {
|
||||||
|
return "avatar"; // 使用 src 提供的头像
|
||||||
|
}
|
||||||
|
if (name && name !== "匿名用户") {
|
||||||
|
return "name"; // 使用名称的首字母
|
||||||
|
}
|
||||||
|
return "random"; // 使用随机头像(基于 ip)
|
||||||
|
}, [src, name]);
|
||||||
|
// 判断是否需要使用 ip 生成头像
|
||||||
|
const avatarSrc =
|
||||||
|
src || (name && name !== "匿名用户")
|
||||||
|
? src
|
||||||
|
: generateAvatarFromIp(ip || "default");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Avatar
|
<Avatar
|
||||||
className={`ring-2 ring-primary/50
|
className={`
|
||||||
bg-primary-300
|
${avatarMode ? "bg-primary-300text-white" : ""}
|
||||||
text-white
|
|
||||||
transition-all duration-200 ease-in-out shadow-md
|
transition-all duration-200 ease-in-out shadow-md
|
||||||
hover:shadow-lg
|
hover:shadow-lg
|
||||||
${className}`}
|
${className}`}
|
||||||
shape="square"
|
src={avatarSrc}
|
||||||
src={src}
|
|
||||||
size={40}
|
|
||||||
{...props}>
|
{...props}>
|
||||||
{!src && firstChar}
|
{!avatarSrc && firstChar}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ importers:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nestjs/bullmq':
|
'@nestjs/bullmq':
|
||||||
specifier: ^10.2.0
|
specifier: ^10.2.0
|
||||||
version: 10.2.3(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(bullmq@5.34.8)
|
version: 10.2.3(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))(bullmq@5.34.8)
|
||||||
'@nestjs/common':
|
'@nestjs/common':
|
||||||
specifier: ^10.3.10
|
specifier: ^10.3.10
|
||||||
version: 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
version: 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
|
@ -33,7 +33,7 @@ importers:
|
||||||
version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.4.15)(rxjs@7.8.1)
|
version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.4.15)(rxjs@7.8.1)
|
||||||
'@nestjs/schedule':
|
'@nestjs/schedule':
|
||||||
specifier: ^4.1.0
|
specifier: ^4.1.0
|
||||||
version: 4.1.2(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)
|
version: 4.1.2(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))
|
||||||
'@nestjs/websockets':
|
'@nestjs/websockets':
|
||||||
specifier: ^10.3.10
|
specifier: ^10.3.10
|
||||||
version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(@nestjs/platform-socket.io@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(@nestjs/platform-socket.io@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
|
@ -148,7 +148,7 @@ importers:
|
||||||
version: 10.2.3(chokidar@3.6.0)(typescript@5.7.2)
|
version: 10.2.3(chokidar@3.6.0)(typescript@5.7.2)
|
||||||
'@nestjs/testing':
|
'@nestjs/testing':
|
||||||
specifier: ^10.0.0
|
specifier: ^10.0.0
|
||||||
version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(@nestjs/platform-express@10.4.15)
|
version: 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15))
|
||||||
'@types/exceljs':
|
'@types/exceljs':
|
||||||
specifier: ^1.3.0
|
specifier: ^1.3.0
|
||||||
version: 1.3.2
|
version: 1.3.2
|
||||||
|
@ -284,6 +284,9 @@ importers:
|
||||||
'@hookform/resolvers':
|
'@hookform/resolvers':
|
||||||
specifier: ^3.9.1
|
specifier: ^3.9.1
|
||||||
version: 3.10.0(react-hook-form@7.54.2(react@18.2.0))
|
version: 3.10.0(react-hook-form@7.54.2(react@18.2.0))
|
||||||
|
'@multiavatar/multiavatar':
|
||||||
|
specifier: ^1.0.7
|
||||||
|
version: 1.0.7
|
||||||
'@nice/client':
|
'@nice/client':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../packages/client
|
version: link:../../packages/client
|
||||||
|
@ -1979,6 +1982,9 @@ packages:
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
|
|
||||||
|
'@multiavatar/multiavatar@1.0.7':
|
||||||
|
resolution: {integrity: sha512-Yg9Uw57bmlErsWL0CSv4d6D4ZqVBE00OZmYr9MRgygoXZdboNtsEI6FbBRw1AY8l88Sm1ARcyojtlm2uwUn0Zg==}
|
||||||
|
|
||||||
'@nestjs/bull-shared@10.2.3':
|
'@nestjs/bull-shared@10.2.3':
|
||||||
resolution: {integrity: sha512-XcgAjNOgq6b5DVCytxhR5BKiwWo7hsusVeyE7sfFnlXRHeEtIuC2hYWBr/ZAtvL/RH0/O0tqtq0rVl972nbhJw==}
|
resolution: {integrity: sha512-XcgAjNOgq6b5DVCytxhR5BKiwWo7hsusVeyE7sfFnlXRHeEtIuC2hYWBr/ZAtvL/RH0/O0tqtq0rVl972nbhJw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -8638,15 +8644,17 @@ snapshots:
|
||||||
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
|
'@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
'@nestjs/bull-shared@10.2.3(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)':
|
'@multiavatar/multiavatar@1.0.7': {}
|
||||||
|
|
||||||
|
'@nestjs/bull-shared@10.2.3(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
'@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
'@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
'@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@nestjs/bullmq@10.2.3(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(bullmq@5.34.8)':
|
'@nestjs/bullmq@10.2.3(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))(bullmq@5.34.8)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nestjs/bull-shared': 10.2.3(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)
|
'@nestjs/bull-shared': 10.2.3(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))
|
||||||
'@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
'@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
'@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
'@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
bullmq: 5.34.8
|
bullmq: 5.34.8
|
||||||
|
@ -8743,7 +8751,7 @@ snapshots:
|
||||||
- supports-color
|
- supports-color
|
||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
|
|
||||||
'@nestjs/schedule@4.1.2(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)':
|
'@nestjs/schedule@4.1.2(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
'@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
'@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
'@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
|
@ -8761,7 +8769,7 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- chokidar
|
- chokidar
|
||||||
|
|
||||||
'@nestjs/testing@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)(@nestjs/platform-express@10.4.15)':
|
'@nestjs/testing@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
'@nestjs/common': 10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
'@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
'@nestjs/core': 10.4.15(@nestjs/common@10.4.15(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(reflect-metadata@0.2.2)(rxjs@7.8.1)
|
||||||
|
|
Loading…
Reference in New Issue