diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index f40bc70..981548b 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -1,79 +1,144 @@ -# NEX Docus 实施计划 +# NexDocus 功能增强实现计划 -## Stage 1: 基础架构与数据库设计 -**Goal**: 完成数据库设计、后端项目初始化、核心数据模型创建 +## Stage 1: 路由调整和项目成员管理 +**Goal**: 调整路由结构,实现项目卡片上的成员管理功能 **Success Criteria**: -- DATABASE.md 文档完成 -- 后端项目结构搭建完成 -- 数据库连接测试通过 -- 所有数据表创建完成 +- /projects/my 路由正常工作 +- /projects/share 路由正常工作 +- 项目卡片上可以打开成员管理弹窗 +- 可以添加/删除项目成员 **Tests**: -- 数据库连接测试 -- 表结构验证 -- ORM 模型单元测试 +- 访问 /projects/my 显示我创建的项目 +- 访问 /projects/share 显示我参与的项目 +- 点击项目卡片的成员图标打开成员管理弹窗 +- 成功添加用户到项目 +- 成功从项目删除用户 **Status**: ✅ Completed --- -## Stage 2: 用户认证与权限系统 -**Goal**: 实现完整的用户认证、角色权限、菜单管理系统 +## Stage 2: 个人桌面统计增强 +**Goal**: 在个人桌面增加"参加项目数"统计 **Success Criteria**: -- JWT 认证流程完整 -- 用户注册、登录接口正常工作 -- RBAC 权限校验中间件实现 -- 角色-权限-菜单关联关系正确 +- 个人桌面显示参加项目数 +- 统计数据准确(我创建的项目 + 我参与的项目) **Tests**: -- 登录/注册接口测试 -- Token 生成和验证测试 -- 权限校验测试 -- 角色授权测试 +- 创建新项目后,个人项目数 +1 +- 加入协作项目后,参加项目数 +1 +- 统计卡片正确显示两个数值 **Status**: ✅ Completed --- -## Stage 3: 文件存储核心服务 -**Goal**: 实现安全的文件系统存储管理服务 +## Stage 3: 参与项目视图和权限控制 +**Goal**: 实现参与项目单独视图,区分所有者和成员权限 **Success Criteria**: -- 路径安全校验机制完成 -- 文件读写、目录树生成功能正常 -- 文件上传、下载流式传输实现 -- UUID 文件夹映射机制正常 +- /projects/share 页面单独展示我参与的项目(非所有者) +- 项目卡片根据角色显示不同操作选项 +- 成员不能删除项目(删除按钮不显示) +- 成员可以编辑项目内文档 **Tests**: -- 路径注入攻击防御测试 -- 文件读写性能测试 -- 大文件上传测试 -- 目录树生成正确性测试 +- 参与者打开 /projects/share 只看到协作项目 +- 参与者看不到删除项目按钮 +- 参与者可以正常编辑文档 **Status**: ✅ Completed --- -## Stage 4: 项目与文档管理 API -**Goal**: 实现项目管理、文档 CRUD、协作成员管理的完整 API +## Stage 4: 操作日志功能 +**Goal**: 为文档编辑操作记录日志 **Success Criteria**: -- 项目创建/列表/详情接口完成 -- 文档 CRUD 接口完成 -- 成员邀请/权限管理接口完成 -- 图片/附件上传接口完成 +- 创建者编辑文档时记录到 operation_logs 表 +- 参与者编辑文档时记录到 operation_logs 表 +- 日志包含用户ID、操作类型、资源信息 **Tests**: -- 项目 CRUD 接口测试 -- 文档操作接口测试 -- 成员权限验证测试 -- 文件上传接口测试 +- 编辑文档后,operation_logs 表有新记录 +- 日志包含正确的用户ID和文档路径 +- 不同用户编辑生成不同日志 **Status**: ✅ Completed --- -## Stage 5: 前端整合与联调 -**Goal**: 整合现有前端代码,适配新后端 API,实现完整业务流程 +## Stage 5: 项目编辑功能 +**Goal**: 在我的项目卡片上增加编辑功能 **Success Criteria**: -- 前端路由和布局整合完成 -- API 请求封装完成 -- 项目列表页面实现 -- 文档编辑页面实现 -- 用户登录注册页面实现 +- 项目卡片显示编辑按钮(仅所有者) +- 点击编辑弹出表单 +- 可以修改项目名称、描述、公开性 **Tests**: -- 端到端业务流程测试 -- 前后端联调测试 -- 用户体验测试 +- 项目所有者看到编辑按钮 +- 编辑项目信息成功保存 +- 非所有者不显示编辑按钮 **Status**: ✅ Completed +--- + +## Stage 6: 全局搜索功能 +**Goal**: 实现全局搜索文档内容 +**Success Criteria**: +- 后端提供搜索API(搜索文档元数据+内容) +- 前端增加全局搜索框 +- 搜索结果显示匹配的文档列表 +- 点击结果跳转到对应文档 +**Tests**: +- 搜索文档标题能找到对应文档 +- 搜索文档内容能找到包含关键词的文档 +- 搜索结果高亮显示关键词 +- 点击搜索结果正确跳转 +**Status**: ✅ Completed + +--- + +## 技术决策 + +### 数据库变更 +- 无需修改现有表结构(project_members 和 operation_logs 已存在) + +### 后端API新增 +1. `GET /projects/my` - 获取我创建的项目 +2. `GET /projects/shared` - 获取我参与的项目 +3. `POST /projects/{project_id}/members` - 添加项目成员(已存在,需测试) +4. `DELETE /projects/{project_id}/members/{user_id}` - 删除项目成员(需新增) +5. `POST /search/documents` - 全局文档搜索(需新增) +6. 修改 `POST /files/{project_id}/file` - 增加操作日志记录 + +### 前端路由调整 +- `/projects/my` - 我的项目(原 /projects) +- `/projects/share` - 参与的项目(新增) +- 保持 `/projects` 作为默认,重定向到 `/projects/my` + +### 权限控制策略 +- 项目所有者:完全控制(增删改查、成员管理、删除项目) +- 项目管理员(ADMIN):文档管理、成员管理 +- 项目编辑者(EDITOR):文档编辑 +- 项目查看者(VIEWER):仅查看 + +### 操作日志设计 +```json +{ + "user_id": 1, + "username": "admin", + "operation_type": "update", + "resource_type": "document", + "resource_id": 123, + "detail": { + "project_id": 10, + "file_path": "/docs/readme.md", + "action": "edit", + "changes": "content_modified" + }, + "ip_address": "192.168.1.1", + "status": 1 +} +``` + +### 全局搜索实现方案 +1. **基础版本(当前阶段)**: + - 搜索 document_meta 表的 title 字段 + - 读取磁盘文件内容进行关键词匹配 + - 适用于小规模项目 + +2. **未来优化(可选)**: + - 使用 Elasticsearch 全文检索 + - 添加文档内容索引到数据库 + - 实现增量索引更新 diff --git a/backend/app/api/v1/__init__.py b/backend/app/api/v1/__init__.py index 7bdfd7a..32c5d1e 100644 --- a/backend/app/api/v1/__init__.py +++ b/backend/app/api/v1/__init__.py @@ -2,7 +2,7 @@ API v1 路由汇总 """ from fastapi import APIRouter -from app.api.v1 import auth, projects, files, menu, dashboard, preview, role_permissions, users, roles +from app.api.v1 import auth, projects, files, menu, dashboard, preview, role_permissions, users, roles, search, logs api_router = APIRouter() @@ -16,3 +16,5 @@ api_router.include_router(preview.router, prefix="/preview", tags=["项目预览 api_router.include_router(role_permissions.router, prefix="/role-permissions", tags=["角色权限管理"]) api_router.include_router(users.router, prefix="/users", tags=["用户管理"]) api_router.include_router(roles.router, prefix="/roles", tags=["角色管理"]) +api_router.include_router(search.router, prefix="/search", tags=["文档搜索"]) +api_router.include_router(logs.router, prefix="/logs", tags=["系统日志"]) diff --git a/backend/app/api/v1/auth.py b/backend/app/api/v1/auth.py index 5ba8c4b..8150cdb 100644 --- a/backend/app/api/v1/auth.py +++ b/backend/app/api/v1/auth.py @@ -15,13 +15,19 @@ from app.models.user import User from app.models.role import Role, UserRole from app.schemas.user import UserCreate, UserLogin, UserResponse, Token, ChangePassword, UserUpdate from app.schemas.response import success_response, error_response +from app.services.log_service import log_service +from app.core.enums import OperationType, ResourceType logger = logging.getLogger(__name__) router = APIRouter() @router.post("/register", response_model=dict) -async def register(user_in: UserCreate, db: AsyncSession = Depends(get_db)): +async def register( + user_in: UserCreate, + request: Request, + db: AsyncSession = Depends(get_db) +): """用户注册""" # 检查用户名是否存在 result = await db.execute(select(User).where(User.username == user_in.username)) @@ -57,6 +63,17 @@ async def register(user_in: UserCreate, db: AsyncSession = Depends(get_db)): db.add(user_role) await db.commit() + # 记录注册日志 + await log_service.log_operation( + db=db, + operation_type=OperationType.USER_REGISTER, + resource_type=ResourceType.USER, + user=db_user, + resource_id=db_user.id, + detail={"username": db_user.username, "email": user_in.email}, + request=request, + ) + return success_response( data={"user_id": db_user.id, "username": db_user.username}, message="注册成功" @@ -64,7 +81,11 @@ async def register(user_in: UserCreate, db: AsyncSession = Depends(get_db)): @router.post("/login", response_model=dict) -async def login(user_in: UserLogin, db: AsyncSession = Depends(get_db)): +async def login( + user_in: UserLogin, + request: Request, + db: AsyncSession = Depends(get_db) +): """用户登录""" # 查询用户 result = await db.execute(select(User).where(User.username == user_in.username)) @@ -86,6 +107,17 @@ async def login(user_in: UserLogin, db: AsyncSession = Depends(get_db)): # 保存 token 到 Redis(24小时过期) await TokenCache.save_token(user.id, access_token, expire_seconds=86400) + # 记录登录日志 + await log_service.log_operation( + db=db, + operation_type=OperationType.USER_LOGIN, + resource_type=ResourceType.USER, + user=user, + resource_id=user.id, + detail={"username": user.username}, + request=request, + ) + # 返回用户信息和 Token user_data = UserResponse.from_orm(user) token_data = Token(access_token=access_token, user=user_data) @@ -152,7 +184,8 @@ async def change_password( @router.post("/logout", response_model=dict) async def logout( request: Request, - current_user: User = Depends(get_current_user) + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db) ): """退出登录""" # 从请求状态中获取 token(已在 get_current_user 中保存) @@ -162,4 +195,15 @@ async def logout( await TokenCache.delete_token(token) logger.info(f"User {current_user.username} logged out") + # 记录登出日志 + await log_service.log_operation( + db=db, + operation_type=OperationType.USER_LOGOUT, + resource_type=ResourceType.USER, + user=current_user, + resource_id=current_user.id, + detail={"username": current_user.username}, + request=request, + ) + return success_response(message="退出成功") diff --git a/backend/app/api/v1/dashboard.py b/backend/app/api/v1/dashboard.py index cfc2dec..b997a4b 100644 --- a/backend/app/api/v1/dashboard.py +++ b/backend/app/api/v1/dashboard.py @@ -107,6 +107,15 @@ async def get_personal_stats( ) personal_projects_count = personal_projects_count_result.scalar() + # 统计参加项目数(协作项目) + shared_projects_count_result = await db.execute( + select(func.count(Project.id)) + .join(ProjectMember, Project.id == ProjectMember.project_id) + .where(ProjectMember.user_id == current_user.id) + .where(Project.owner_id != current_user.id) + ) + shared_projects_count = shared_projects_count_result.scalar() + # 统计个人文档数(个人项目中的 .md 文件) document_count = 0 personal_projects_result = await db.execute( @@ -169,6 +178,7 @@ async def get_personal_stats( }, "stats": { "personal_projects_count": personal_projects_count, + "shared_projects_count": shared_projects_count, "document_count": document_count, }, "recent_personal_projects": recent_personal_projects_data, diff --git a/backend/app/api/v1/files.py b/backend/app/api/v1/files.py index bd6103a..23d138b 100644 --- a/backend/app/api/v1/files.py +++ b/backend/app/api/v1/files.py @@ -1,7 +1,7 @@ """ 文件系统操作相关 API """ -from fastapi import APIRouter, Depends, HTTPException, UploadFile, File +from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Request from fastapi.responses import StreamingResponse, FileResponse from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select @@ -10,12 +10,14 @@ import os import zipfile import io import mimetypes +import json from pathlib import Path from app.core.database import get_db from app.core.deps import get_current_user, get_user_from_token_or_query from app.models.user import User from app.models.project import Project, ProjectMember +from app.models.log import OperationLog from app.schemas.file import ( FileTreeNode, FileSaveRequest, @@ -24,6 +26,8 @@ from app.schemas.file import ( ) from app.schemas.response import success_response from app.services.storage import storage_service +from app.services.log_service import log_service +from app.core.enums import OperationType router = APIRouter() @@ -82,7 +86,21 @@ async def get_project_tree( # 生成目录树 tree = storage_service.generate_tree(project_root) - return success_response(data=tree) + # 获取当前用户角色 + user_role = "owner" # 默认是所有者 + if project.owner_id != current_user.id: + # 查询成员角色 + member_result = await db.execute( + select(ProjectMember).where( + ProjectMember.project_id == project_id, + ProjectMember.user_id == current_user.id + ) + ) + member = member_result.scalar_one_or_none() + if member: + user_role = member.role + + return success_response(data={"tree": tree, "user_role": user_role}) @router.get("/{project_id}/file", response_model=dict) @@ -108,6 +126,7 @@ async def get_file_content( async def save_file( project_id: int, file_data: FileSaveRequest, + request: Request, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -120,6 +139,17 @@ async def save_file( # 写入文件内容 await storage_service.write_file(file_path, file_data.content) + # 记录操作日志 + await log_service.log_file_operation( + db=db, + operation_type=OperationType.SAVE_FILE, + project_id=project_id, + file_path=file_data.path, + user=current_user, + detail={"content_length": len(file_data.content)}, + request=request, + ) + return success_response(message="文件保存成功") @@ -127,6 +157,7 @@ async def save_file( async def operate_file( project_id: int, operation: FileOperateRequest, + request: Request, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -139,6 +170,15 @@ async def operate_file( if operation.action == "delete": # 删除文件或文件夹 await storage_service.delete_file(current_path) + # 记录日志 + await log_service.log_file_operation( + db=db, + operation_type=OperationType.DELETE_FILE, + project_id=project_id, + file_path=operation.path, + user=current_user, + request=request, + ) return success_response(message="删除成功") elif operation.action == "rename": @@ -147,6 +187,16 @@ async def operate_file( raise HTTPException(status_code=400, detail="缺少新路径参数") new_path = storage_service.get_secure_path(project.storage_key, operation.new_path) await storage_service.rename_file(current_path, new_path) + # 记录日志 + await log_service.log_file_operation( + db=db, + operation_type=OperationType.RENAME_FILE, + project_id=project_id, + file_path=operation.path, + user=current_user, + detail={"new_path": operation.new_path}, + request=request, + ) return success_response(message="重命名成功") elif operation.action == "move": @@ -155,17 +205,45 @@ async def operate_file( raise HTTPException(status_code=400, detail="缺少目标路径参数") new_path = storage_service.get_secure_path(project.storage_key, operation.new_path) await storage_service.rename_file(current_path, new_path) + # 记录日志 + await log_service.log_file_operation( + db=db, + operation_type=OperationType.MOVE_FILE, + project_id=project_id, + file_path=operation.path, + user=current_user, + detail={"new_path": operation.new_path}, + request=request, + ) return success_response(message="移动成功") elif operation.action == "create_dir": # 创建目录 await storage_service.create_directory(current_path) + # 记录日志 + await log_service.log_file_operation( + db=db, + operation_type=OperationType.CREATE_DIR, + project_id=project_id, + file_path=operation.path, + user=current_user, + request=request, + ) return success_response(message="目录创建成功") elif operation.action == "create_file": # 创建文件 content = operation.content or "" await storage_service.write_file(current_path, content) + # 记录日志 + await log_service.log_file_operation( + db=db, + operation_type=OperationType.CREATE_FILE, + project_id=project_id, + file_path=operation.path, + user=current_user, + request=request, + ) return success_response(message="文件创建成功") else: @@ -177,6 +255,7 @@ async def upload_file( project_id: int, file: UploadFile = File(...), subfolder: str = "images", + request: Request = None, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -193,6 +272,20 @@ async def upload_file( # 构建访问 URL file_info["url"] = f"/api/v1/files/{project_id}/assets/{subfolder}/{file_info['filename']}" + # 记录日志 + await log_service.log_file_operation( + db=db, + operation_type=OperationType.UPLOAD_IMAGE, + project_id=project_id, + file_path=f"_assets/{subfolder}/{file_info['filename']}", + user=current_user, + detail={ + "original_filename": file.filename, + "file_size": file_info.get("size", 0), + }, + request=request, + ) + return success_response(data=file_info, message="文件上传成功") @@ -236,6 +329,7 @@ async def import_documents( project_id: int, files: List[UploadFile] = File(...), target_path: str = "", + request: Request = None, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -268,6 +362,20 @@ async def import_documents( relative_path = f"{target_path}/{file.filename}" if target_path else file.filename imported_files.append(relative_path) + # 记录日志 + await log_service.log_file_operation( + db=db, + operation_type=OperationType.IMPORT_DOCUMENTS, + project_id=project_id, + file_path=target_path or "/", + user=current_user, + detail={ + "file_count": len(imported_files), + "files": imported_files, + }, + request=request, + ) + return success_response( data={"imported_files": imported_files}, message=f"成功导入 {len(imported_files)} 个文档" @@ -278,6 +386,7 @@ async def import_documents( async def export_directory( project_id: int, directory_path: str = "", + request: Request = None, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -292,6 +401,7 @@ async def export_directory( # 创建ZIP文件在内存中 zip_buffer = io.BytesIO() + file_count = 0 with zipfile.ZipFile(zip_buffer, 'w', zipfile.ZIP_DEFLATED) as zip_file: # 遍历目录添加所有文件 @@ -300,6 +410,7 @@ async def export_directory( # 计算相对路径 arcname = file_path.relative_to(source_dir) zip_file.write(file_path, arcname) + file_count += 1 # 重置buffer位置 zip_buffer.seek(0) @@ -307,6 +418,20 @@ async def export_directory( # 生成ZIP文件名 zip_filename = f"{project.name}_{directory_path.replace('/', '_') if directory_path else 'root'}.zip" + # 记录日志 + await log_service.log_file_operation( + db=db, + operation_type=OperationType.EXPORT_DOCUMENTS, + project_id=project_id, + file_path=directory_path or "/", + user=current_user, + detail={ + "file_count": file_count, + "zip_filename": zip_filename, + }, + request=request, + ) + return StreamingResponse( zip_buffer, media_type="application/zip", diff --git a/backend/app/api/v1/logs.py b/backend/app/api/v1/logs.py new file mode 100644 index 0000000..880ba35 --- /dev/null +++ b/backend/app/api/v1/logs.py @@ -0,0 +1,164 @@ +""" +系统日志相关 API +""" +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, func, and_ +from typing import Optional +from datetime import datetime + +from app.core.database import get_db +from app.core.deps import get_current_user +from app.models.user import User +from app.models.log import OperationLog +from app.models.project import Project +from app.schemas.response import success_response + +router = APIRouter() + + +@router.get("/", response_model=dict) +async def get_operation_logs( + page: int = Query(1, ge=1), + page_size: int = Query(20, ge=1, le=100), + operation_type: Optional[str] = None, + resource_type: Optional[str] = None, + user_id: Optional[int] = None, + project_id: Optional[int] = None, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db) +): + """ + 获取操作日志列表 + + 支持过滤: + - operation_type: 操作类型 + - resource_type: 资源类型 + - user_id: 用户ID + - project_id: 项目ID + - start_date: 开始日期 (YYYY-MM-DD) + - end_date: 结束日期 (YYYY-MM-DD) + """ + # 构建查询条件 + conditions = [] + + if operation_type: + conditions.append(OperationLog.operation_type == operation_type) + + if resource_type: + conditions.append(OperationLog.resource_type == resource_type) + + if user_id: + conditions.append(OperationLog.user_id == user_id) + + if project_id: + conditions.append(OperationLog.resource_id == project_id) + + if start_date: + try: + start_datetime = datetime.strptime(start_date, "%Y-%m-%d") + conditions.append(OperationLog.created_at >= start_datetime) + except ValueError: + raise HTTPException(status_code=400, detail="开始日期格式错误") + + if end_date: + try: + end_datetime = datetime.strptime(f"{end_date} 23:59:59", "%Y-%m-%d %H:%M:%S") + conditions.append(OperationLog.created_at <= end_datetime) + except ValueError: + raise HTTPException(status_code=400, detail="结束日期格式错误") + + # 查询总数 + count_query = select(func.count(OperationLog.id)) + if conditions: + count_query = count_query.where(and_(*conditions)) + + total_result = await db.execute(count_query) + total = total_result.scalar() + + # 查询日志列表 + query = select(OperationLog).order_by(OperationLog.created_at.desc()) + + if conditions: + query = query.where(and_(*conditions)) + + # 分页 + offset = (page - 1) * page_size + query = query.offset(offset).limit(page_size) + + result = await db.execute(query) + logs = result.scalars().all() + + # 转换为字典格式 + logs_data = [] + for log in logs: + log_dict = { + "id": log.id, + "user_id": log.user_id, + "username": log.username, + "operation_type": log.operation_type, + "resource_type": log.resource_type, + "resource_id": log.resource_id, + "detail": log.detail, + "ip_address": log.ip_address, + "user_agent": log.user_agent, + "status": log.status, + "error_message": log.error_message, + "created_at": log.created_at.isoformat() if log.created_at else None, + } + logs_data.append(log_dict) + + return success_response(data={ + "items": logs_data, + "total": total, + "page": page, + "page_size": page_size, + "total_pages": (total + page_size - 1) // page_size + }) + + +@router.get("/stats", response_model=dict) +async def get_log_stats( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db) +): + """获取日志统计信息""" + # 统计各类操作数量 + operation_stats_query = select( + OperationLog.operation_type, + func.count(OperationLog.id).label('count') + ).group_by(OperationLog.operation_type) + + operation_result = await db.execute(operation_stats_query) + operation_stats = {row.operation_type: row.count for row in operation_result} + + # 统计各资源类型数量 + resource_stats_query = select( + OperationLog.resource_type, + func.count(OperationLog.id).label('count') + ).group_by(OperationLog.resource_type) + + resource_result = await db.execute(resource_stats_query) + resource_stats = {row.resource_type: row.count for row in resource_result} + + # 统计今天的操作数量 + today_start = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0) + today_count_query = select(func.count(OperationLog.id)).where( + OperationLog.created_at >= today_start + ) + today_result = await db.execute(today_count_query) + today_count = today_result.scalar() + + # 总日志数量 + total_query = select(func.count(OperationLog.id)) + total_result = await db.execute(total_query) + total_count = total_result.scalar() + + return success_response(data={ + "operation_stats": operation_stats, + "resource_stats": resource_stats, + "today_count": today_count, + "total_count": total_count, + }) diff --git a/backend/app/api/v1/preview.py b/backend/app/api/v1/preview.py index f4a5a75..3ec889c 100644 --- a/backend/app/api/v1/preview.py +++ b/backend/app/api/v1/preview.py @@ -1,5 +1,5 @@ """ -项目预览相关 API(公开访问,支持分享) +项目预览相关 API(支持公开和私密项目) """ from fastapi import APIRouter, Depends, HTTPException, Header from sqlalchemy.ext.asyncio import AsyncSession @@ -7,19 +7,55 @@ from sqlalchemy import select from typing import Optional from app.core.database import get_db -from app.models.project import Project +from app.core.deps import get_current_user_optional +from app.models.project import Project, ProjectMember +from app.models.user import User from app.schemas.response import success_response from app.services.storage import storage_service router = APIRouter() +async def check_preview_access( + project: Project, + current_user: Optional[User], + db: AsyncSession +): + """检查预览访问权限""" + # 公开项目:任何人都可以访问 + if project.is_public == 1: + return True + + # 私密项目:必须是项目成员 + if not current_user: + raise HTTPException(status_code=401, detail="私密项目需要登录才能访问") + + # 检查是否是项目所有者 + if project.owner_id == current_user.id: + return True + + # 检查是否是项目成员 + member_result = await db.execute( + select(ProjectMember).where( + ProjectMember.project_id == project.id, + ProjectMember.user_id == current_user.id + ) + ) + member = member_result.scalar_one_or_none() + + if not member: + raise HTTPException(status_code=403, detail="无权访问该私密项目") + + return True + + @router.get("/{project_id}/info", response_model=dict) async def get_preview_info( project_id: int, + current_user: Optional[User] = Depends(get_current_user_optional), db: AsyncSession = Depends(get_db) ): - """获取预览项目基本信息(公开访问)""" + """获取预览项目基本信息""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() @@ -27,11 +63,15 @@ async def get_preview_info( if not project: raise HTTPException(status_code=404, detail="项目不存在") + # 检查访问权限 + await check_preview_access(project, current_user, db) + # 返回基本信息 info = { "id": project.id, "name": project.name, "description": project.description, + "is_public": project.is_public, "has_password": bool(project.access_pass), } @@ -42,6 +82,7 @@ async def get_preview_info( async def verify_access_password( project_id: int, password: str = Header(..., alias="X-Access-Password"), + current_user: Optional[User] = Depends(get_current_user_optional), db: AsyncSession = Depends(get_db) ): """验证访问密码""" @@ -52,6 +93,9 @@ async def verify_access_password( if not project: raise HTTPException(status_code=404, detail="项目不存在") + # 检查访问权限 + await check_preview_access(project, current_user, db) + # 验证密码 if not project.access_pass: return success_response(message="该项目无需密码访问") @@ -66,9 +110,10 @@ async def verify_access_password( async def get_preview_tree( project_id: int, password: Optional[str] = Header(None, alias="X-Access-Password"), + current_user: Optional[User] = Depends(get_current_user_optional), db: AsyncSession = Depends(get_db) ): - """获取预览项目的文档树(公开访问,需验证密码)""" + """获取预览项目的文档树""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() @@ -76,6 +121,9 @@ async def get_preview_tree( if not project: raise HTTPException(status_code=404, detail="项目不存在") + # 检查访问权限 + await check_preview_access(project, current_user, db) + # 如果设置了密码,需要验证 if project.access_pass: if not password or project.access_pass != password: @@ -93,9 +141,10 @@ async def get_preview_file( project_id: int, path: str, password: Optional[str] = Header(None, alias="X-Access-Password"), + current_user: Optional[User] = Depends(get_current_user_optional), db: AsyncSession = Depends(get_db) ): - """获取预览项目的文件内容(公开访问,需验证密码)""" + """获取预览项目的文件内容""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() @@ -103,6 +152,9 @@ async def get_preview_file( if not project: raise HTTPException(status_code=404, detail="项目不存在") + # 检查访问权限 + await check_preview_access(project, current_user, db) + # 如果设置了密码,需要验证 if project.access_pass: if not password or project.access_pass != password: diff --git a/backend/app/api/v1/projects.py b/backend/app/api/v1/projects.py index 52e2f11..a7f754c 100644 --- a/backend/app/api/v1/projects.py +++ b/backend/app/api/v1/projects.py @@ -1,7 +1,7 @@ """ 项目管理相关 API """ -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends, HTTPException, Request from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, or_ from typing import List @@ -10,7 +10,7 @@ import uuid from app.core.database import get_db from app.core.deps import get_current_user from app.models.user import User -from app.models.project import Project, ProjectMember, ProjectMemberRole +from app.models.project import Project, ProjectMember from app.schemas.project import ( ProjectCreate, ProjectUpdate, @@ -23,6 +23,8 @@ from app.schemas.project import ( ) from app.schemas.response import success_response from app.services.storage import storage_service +from app.services.log_service import log_service +from app.core.enums import OperationType, ResourceType router = APIRouter() @@ -58,9 +60,53 @@ async def get_my_projects( return success_response(data=projects_data) +@router.get("/my", response_model=dict) +async def get_owned_projects( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db) +): + """获取我创建的项目列表""" + result = await db.execute( + select(Project).where(Project.owner_id == current_user.id, Project.status == 1) + ) + projects = result.scalars().all() + projects_data = [ProjectResponse.from_orm(p).dict() for p in projects] + return success_response(data=projects_data) + + +@router.get("/shared", response_model=dict) +async def get_shared_projects( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db) +): + """获取我参与的项目列表(不包括我创建的)""" + result = await db.execute( + select(Project, User, ProjectMember) + .join(ProjectMember, ProjectMember.project_id == Project.id) + .join(User, User.id == Project.owner_id) + .where( + ProjectMember.user_id == current_user.id, + Project.owner_id != current_user.id, + Project.status == 1 + ) + ) + projects_with_info = result.all() + + projects_data = [] + for project, owner, member in projects_with_info: + project_dict = ProjectResponse.from_orm(project).dict() + project_dict['owner_name'] = owner.username + project_dict['owner_nickname'] = owner.nickname + project_dict['user_role'] = member.role # 添加用户角色 + projects_data.append(project_dict) + + return success_response(data=projects_data) + + @router.post("/", response_model=dict) async def create_project( project_in: ProjectCreate, + request: Request, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -94,11 +140,21 @@ async def create_project( db_member = ProjectMember( project_id=db_project.id, user_id=current_user.id, - role=ProjectMemberRole.ADMIN, + role="admin", ) db.add(db_member) await db.commit() + # 记录操作日志 + await log_service.log_project_operation( + db=db, + operation_type=OperationType.CREATE_PROJECT, + project_id=db_project.id, + user=current_user, + detail={"project_name": project_in.name}, + request=request, + ) + project_data = ProjectResponse.from_orm(db_project) return success_response(data=project_data.dict(), message="项目创建成功") @@ -141,6 +197,7 @@ async def get_project( async def update_project( project_id: int, project_in: ProjectUpdate, + request: Request, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -164,6 +221,16 @@ async def update_project( await db.commit() await db.refresh(project) + # 记录操作日志 + await log_service.log_project_operation( + db=db, + operation_type=OperationType.UPDATE_PROJECT, + project_id=project_id, + user=current_user, + detail={"updated_fields": list(update_data.keys())}, + request=request, + ) + project_data = ProjectResponse.from_orm(project) return success_response(data=project_data.dict(), message="项目更新成功") @@ -171,10 +238,11 @@ async def update_project( @router.delete("/{project_id}", response_model=dict) async def delete_project( project_id: int, + request: Request, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): - """删除项目(归档)""" + """删除项目""" # 查询项目 result = await db.execute(select(Project).where(Project.id == project_id)) project = result.scalar_one_or_none() @@ -186,11 +254,52 @@ async def delete_project( if project.owner_id != current_user.id: raise HTTPException(status_code=403, detail="无权删除该项目") - # 软删除(归档) - project.status = 0 + # 检查项目目录下是否有文件(排除_assets目录和隐藏文件) + project_root = storage_service.get_secure_path(project.storage_key) + + has_files = False + if project_root.exists() and project_root.is_dir(): + for item in project_root.iterdir(): + # 跳过 _assets 目录和以.开头的隐藏文件(如.DS_Store) + if item.name == "_assets" or item.name.startswith("."): + continue + has_files = True + break + + if has_files: + raise HTTPException( + status_code=400, + detail="项目目录下存在文件,无法删除。请先清空项目文件。" + ) + + # 删除物理目录 + if project_root.exists(): + import shutil + try: + shutil.rmtree(project_root) + except Exception as e: + raise HTTPException( + status_code=500, + detail=f"删除项目目录失败: {str(e)}" + ) + + project_name = project.name + + # 删除数据库记录 + await db.delete(project) await db.commit() - return success_response(message="项目已归档") + # 记录操作日志 + await log_service.log_project_operation( + db=db, + operation_type=OperationType.DELETE_PROJECT, + project_id=project_id, + user=current_user, + detail={"project_name": project_name}, + request=request, + ) + + return success_response(message="项目已删除") @router.get("/{project_id}/members", response_model=dict) @@ -219,13 +328,27 @@ async def get_project_members( if not member: raise HTTPException(status_code=403, detail="无权访问该项目") - # 查询成员列表 + # 查询成员列表并关联用户信息 members_result = await db.execute( - select(ProjectMember).where(ProjectMember.project_id == project_id) + select(ProjectMember, User) + .join(User, ProjectMember.user_id == User.id) + .where(ProjectMember.project_id == project_id) ) - members = members_result.scalars().all() + members_with_users = members_result.all() + + # 构建返回数据,包含用户名信息 + members_data = [] + for member, user in members_with_users: + members_data.append({ + "id": member.id, + "project_id": member.project_id, + "user_id": member.user_id, + "role": member.role, + "joined_at": member.joined_at.isoformat() if member.joined_at else None, + "username": user.username, + "nickname": user.nickname, + }) - members_data = [ProjectMemberResponse.from_orm(m).dict() for m in members] return success_response(data=members_data) @@ -233,6 +356,7 @@ async def get_project_members( async def add_project_member( project_id: int, member_in: ProjectMemberAdd, + request: Request, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -250,7 +374,7 @@ async def add_project_member( select(ProjectMember).where( ProjectMember.project_id == project_id, ProjectMember.user_id == current_user.id, - ProjectMember.role == ProjectMemberRole.ADMIN + ProjectMember.role == "admin" ) ) member = member_result.scalar_one_or_none() @@ -279,10 +403,83 @@ async def add_project_member( await db.commit() await db.refresh(db_member) + # 记录操作日志 + await log_service.log_member_operation( + db=db, + operation_type=OperationType.ADD_MEMBER, + project_id=project_id, + target_user_id=member_in.user_id, + user=current_user, + detail={"role": member_in.role}, + request=request, + ) + member_data = ProjectMemberResponse.from_orm(db_member) return success_response(data=member_data.dict(), message="成员添加成功") +@router.delete("/{project_id}/members/{user_id}", response_model=dict) +async def remove_project_member( + project_id: int, + user_id: int, + request: Request, + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db) +): + """删除项目成员""" + # 查询项目 + result = await db.execute(select(Project).where(Project.id == project_id)) + project = result.scalar_one_or_none() + + if not project: + raise HTTPException(status_code=404, detail="项目不存在") + + # 只有项目所有者和管理员可以删除成员 + if project.owner_id != current_user.id: + member_result = await db.execute( + select(ProjectMember).where( + ProjectMember.project_id == project_id, + ProjectMember.user_id == current_user.id, + ProjectMember.role == "admin" + ) + ) + member = member_result.scalar_one_or_none() + if not member: + raise HTTPException(status_code=403, detail="无权删除成员") + + # 不能删除项目所有者 + if user_id == project.owner_id: + raise HTTPException(status_code=400, detail="不能删除项目所有者") + + # 查询要删除的成员 + member_result = await db.execute( + select(ProjectMember).where( + ProjectMember.project_id == project_id, + ProjectMember.user_id == user_id + ) + ) + member = member_result.scalar_one_or_none() + + if not member: + raise HTTPException(status_code=404, detail="成员不存在") + + # 删除成员 + await db.delete(member) + await db.commit() + + # 记录操作日志 + await log_service.log_member_operation( + db=db, + operation_type=OperationType.REMOVE_MEMBER, + project_id=project_id, + target_user_id=user_id, + user=current_user, + request=request, + ) + + return success_response(message="成员删除成功") + + @router.get("/{project_id}/share", response_model=dict) async def get_project_share_info( project_id: int, @@ -297,17 +494,28 @@ async def get_project_share_info( if not project: raise HTTPException(status_code=404, detail="项目不存在") - # 只有项目所有者可以获取分享信息 - if project.owner_id != current_user.id: - raise HTTPException(status_code=403, detail="只有项目所有者可以查看分享信息") + # 检查是否是项目所有者或成员 + is_owner = project.owner_id == current_user.id + if not is_owner: + # 检查是否是项目成员 + member_result = await db.execute( + select(ProjectMember).where( + ProjectMember.project_id == project_id, + ProjectMember.user_id == current_user.id + ) + ) + member = member_result.scalar_one_or_none() + if not member: + raise HTTPException(status_code=403, detail="无权访问该项目") # 构建分享链接 share_url = f"/preview/{project_id}" + # 只有项目所有者可以看到实际密码,成员只能知道是否设置了密码 share_info = ProjectShareInfo( share_url=share_url, has_password=bool(project.access_pass), - access_pass=project.access_pass # 返回实际密码给项目所有者 + access_pass=project.access_pass if is_owner else None ) return success_response(data=share_info.dict()) @@ -317,6 +525,7 @@ async def get_project_share_info( async def update_share_settings( project_id: int, settings: ProjectShareSettings, + request: Request, current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -336,5 +545,19 @@ async def update_share_settings( project.access_pass = settings.access_pass await db.commit() + # 记录操作日志 + await log_service.log_operation( + db=db, + operation_type=OperationType.UPDATE_SHARE_SETTINGS, + resource_type=ResourceType.SHARE, + user=current_user, + resource_id=project_id, + detail={ + "has_password": bool(settings.access_pass), + "project_name": project.name, + }, + request=request, + ) + message = "访问密码已取消" if not settings.access_pass else "访问密码已设置" return success_response(message=message) diff --git a/backend/app/api/v1/search.py b/backend/app/api/v1/search.py new file mode 100644 index 0000000..aa5b8ce --- /dev/null +++ b/backend/app/api/v1/search.py @@ -0,0 +1,123 @@ +""" +文档搜索相关 API +""" +from fastapi import APIRouter, Depends, HTTPException, Query +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import select, or_ +import os +import glob + +from app.core.database import get_db +from app.core.deps import get_current_user +from app.models.user import User +from app.models.project import Project, ProjectMember +from app.services.storage import storage_service +from app.schemas.response import success_response + +router = APIRouter() + + +@router.get("/documents", response_model=dict) +async def search_documents( + keyword: str = Query(..., min_length=1, description="搜索关键词"), + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db) +): + """ + 文档搜索(简化版) + 搜索范围:项目名称、项目描述、文件名 + """ + if not keyword: + return success_response(data=[]) + + keyword_lower = keyword.lower() + + # 获取用户有权限访问的项目 + # 1. 用户创建的项目 + owned_projects_result = await db.execute( + select(Project).where(Project.owner_id == current_user.id, Project.status == 1) + ) + owned_projects = owned_projects_result.scalars().all() + + # 2. 用户参与的项目 + member_projects_result = await db.execute( + select(Project) + .join(ProjectMember, ProjectMember.project_id == Project.id) + .where( + ProjectMember.user_id == current_user.id, + Project.owner_id != current_user.id, + Project.status == 1 + ) + ) + member_projects = member_projects_result.scalars().all() + + # 合并所有可访问的项目 + all_projects = owned_projects + member_projects + + # 搜索结果列表 + search_results = [] + + # 搜索项目和文件 + for project in all_projects: + # 检查项目名称或描述是否匹配 + project_matched = False + if keyword_lower in project.name.lower(): + project_matched = True + elif project.description and keyword_lower in project.description.lower(): + project_matched = True + + # 如果项目本身匹配,添加到结果 + if project_matched: + search_results.append({ + "type": "project", + "project_id": project.id, + "project_name": project.name, + "project_description": project.description or "", + "match_type": "项目", + }) + + # 搜索项目中的文件名 + try: + project_path = storage_service.get_secure_path(project.storage_key) + + if not project_path.exists() or not project_path.is_dir(): + continue + + # 查找所有 .md 文件 + md_files = list(project_path.rglob("*.md")) + + for file_path in md_files: + # 跳过 _assets 目录中的文件 + if "_assets" in file_path.parts: + continue + + try: + # 获取相对路径 + relative_path = str(file_path.relative_to(project_path)) + + # 获取文件名(不含扩展名) + file_name = file_path.stem + + # 检查关键词是否在文件名或路径中 + if keyword_lower in file_name.lower() or keyword_lower in relative_path.lower(): + search_results.append({ + "type": "file", + "project_id": project.id, + "project_name": project.name, + "file_path": relative_path, + "file_name": file_name, + "match_type": "文件", + }) + + except Exception: + # 忽略无法处理的文件 + continue + + except Exception: + # 忽略无法遍历的目录 + continue + + # 限制返回结果数量 + search_results = search_results[:100] + + return success_response(data=search_results, message=f"找到 {len(search_results)} 个结果") diff --git a/backend/app/api/v1/users.py b/backend/app/api/v1/users.py index 4c752c1..4bb27be 100644 --- a/backend/app/api/v1/users.py +++ b/backend/app/api/v1/users.py @@ -51,6 +51,7 @@ async def get_users( page_size: int = Query(10, ge=1, le=100), keyword: Optional[str] = Query(None, description="搜索关键词(用户名、昵称、邮箱)"), status: Optional[int] = Query(None, description="状态筛选:0-禁用 1-启用"), + role_id: Optional[int] = Query(None, description="角色ID筛选"), current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db) ): @@ -69,19 +70,42 @@ async def get_users( if status is not None: conditions.append(User.status == status) - # 查询总数 - count_query = select(func.count(User.id)) - if conditions: - count_query = count_query.where(*conditions) - total_result = await db.execute(count_query) - total = total_result.scalar() + # 如果需要按角色筛选 + if role_id is not None: + # 查询用户列表时需要JOIN UserRole表 + query = ( + select(User) + .join(UserRole, UserRole.user_id == User.id) + .where(UserRole.role_id == role_id) + ) + if conditions: + query = query.where(*conditions) + query = query.order_by(User.created_at.desc()) + + # 查询总数 + count_query = ( + select(func.count(User.id.distinct())) + .join(UserRole, UserRole.user_id == User.id) + .where(UserRole.role_id == role_id) + ) + if conditions: + count_query = count_query.where(*conditions) + total_result = await db.execute(count_query) + total = total_result.scalar() + else: + # 查询总数 + count_query = select(func.count(User.id)) + if conditions: + count_query = count_query.where(*conditions) + total_result = await db.execute(count_query) + total = total_result.scalar() + + # 查询用户列表 + query = select(User).order_by(User.created_at.desc()) + if conditions: + query = query.where(*conditions) - # 查询用户列表 - query = select(User).order_by(User.created_at.desc()) - if conditions: - query = query.where(*conditions) query = query.offset((page - 1) * page_size).limit(page_size) - result = await db.execute(query) users = result.scalars().all() diff --git a/backend/app/core/deps.py b/backend/app/core/deps.py index 37bc097..e63a52a 100644 --- a/backend/app/core/deps.py +++ b/backend/app/core/deps.py @@ -105,6 +105,57 @@ async def get_current_user( return user +async def get_current_user_optional( + credentials: Optional[HTTPAuthorizationCredentials] = Depends(security_optional), + db: AsyncSession = Depends(get_db) +) -> Optional[User]: + """ + 获取当前登录用户(可选,不强制登录) + 如果未提供token或token无效,返回None + """ + if not credentials: + return None + + token = credentials.credentials + + try: + # 验证 Redis 中是否存在该 token + user_id_from_redis = await TokenCache.get_user_id(token) + if user_id_from_redis is None: + return None + + # 解码 JWT 验证完整性 + payload = decode_access_token(token) + if payload is None: + return None + + user_id_str = payload.get("sub") + if user_id_str is None: + return None + + # 将字符串转为整数 + try: + user_id = int(user_id_str) + except (ValueError, TypeError): + return None + + # 验证 Redis 中的 user_id 与 JWT 中的是否一致 + if user_id != user_id_from_redis: + return None + + # 查询用户 + result = await db.execute(select(User).where(User.id == user_id)) + user = result.scalar_one_or_none() + + if user is None or user.status != 1: + return None + + return user + except Exception as e: + logger.warning(f"Optional auth failed: {str(e)}") + return None + + async def get_current_active_user( current_user: User = Depends(get_current_user) ) -> User: diff --git a/backend/app/core/enums.py b/backend/app/core/enums.py new file mode 100644 index 0000000..1dd6654 --- /dev/null +++ b/backend/app/core/enums.py @@ -0,0 +1,48 @@ +""" +系统枚举类 +""" +from enum import Enum + + +class OperationType(str, Enum): + """操作类型枚举""" + + # 项目操作 + CREATE_PROJECT = "create_project" + UPDATE_PROJECT = "update_project" + DELETE_PROJECT = "delete_project" + + # 成员操作 + ADD_MEMBER = "add_member" + REMOVE_MEMBER = "remove_member" + + # 文件操作 + CREATE_FILE = "create_file" + CREATE_DIR = "create_dir" + DELETE_FILE = "delete_file" + RENAME_FILE = "rename_file" + MOVE_FILE = "move_file" + SAVE_FILE = "save_file" + + # 导入导出 + UPLOAD_IMAGE = "upload_image" + IMPORT_DOCUMENTS = "import_documents" + EXPORT_DOCUMENTS = "export_documents" + + # 分享操作 + UPDATE_SHARE_SETTINGS = "update_share_settings" + + # 用户操作 + USER_LOGIN = "user_login" + USER_LOGOUT = "user_logout" + USER_REGISTER = "user_register" + + +class ResourceType(str, Enum): + """资源类型枚举""" + + PROJECT = "project" + FILE = "file" + MEMBER = "member" + USER = "user" + SHARE = "share" diff --git a/backend/app/models/project.py b/backend/app/models/project.py index 07f9fb6..e92ed94 100644 --- a/backend/app/models/project.py +++ b/backend/app/models/project.py @@ -47,10 +47,10 @@ class ProjectMember(Base): project_id = Column(BigInteger, nullable=False, index=True, comment="项目ID") user_id = Column(BigInteger, nullable=False, index=True, comment="用户ID") role = Column( - Enum(ProjectMemberRole), - default=ProjectMemberRole.VIEWER, + String(20), + default="viewer", index=True, - comment="项目角色" + comment="项目角色: admin/editor/viewer" ) invited_by = Column(BigInteger, comment="邀请人ID") joined_at = Column(DateTime, server_default=func.now(), comment="加入时间") diff --git a/backend/app/schemas/project.py b/backend/app/schemas/project.py index bee857c..21e8d04 100644 --- a/backend/app/schemas/project.py +++ b/backend/app/schemas/project.py @@ -1,10 +1,9 @@ """ 项目相关的 Pydantic Schema """ -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator from typing import Optional from datetime import datetime -from app.models.project import ProjectMemberRole class ProjectBase(BaseModel): @@ -47,12 +46,26 @@ class ProjectResponse(ProjectBase): class ProjectMemberAdd(BaseModel): """添加项目成员 Schema""" user_id: int = Field(..., description="用户ID") - role: ProjectMemberRole = Field(ProjectMemberRole.VIEWER, description="项目角色") + role: str = Field("viewer", description="项目角色: admin/editor/viewer") + + @field_validator('role') + @classmethod + def validate_role(cls, v): + if v not in ['admin', 'editor', 'viewer']: + raise ValueError('role must be one of: admin, editor, viewer') + return v class ProjectMemberUpdate(BaseModel): """更新项目成员 Schema""" - role: ProjectMemberRole = Field(..., description="项目角色") + role: str = Field(..., description="项目角色: admin/editor/viewer") + + @field_validator('role') + @classmethod + def validate_role(cls, v): + if v not in ['admin', 'editor', 'viewer']: + raise ValueError('role must be one of: admin, editor, viewer') + return v class ProjectMemberResponse(BaseModel): @@ -60,7 +73,7 @@ class ProjectMemberResponse(BaseModel): id: int project_id: int user_id: int - role: ProjectMemberRole + role: str joined_at: datetime class Config: diff --git a/backend/app/services/log_service.py b/backend/app/services/log_service.py new file mode 100644 index 0000000..8cd29b8 --- /dev/null +++ b/backend/app/services/log_service.py @@ -0,0 +1,138 @@ +""" +操作日志服务 +""" +from sqlalchemy.ext.asyncio import AsyncSession +from fastapi import Request +from typing import Optional +import json + +from app.models.log import OperationLog +from app.models.user import User +from app.core.enums import OperationType, ResourceType + + +class LogService: + """操作日志服务""" + + @staticmethod + async def log_operation( + db: AsyncSession, + operation_type: OperationType, + resource_type: ResourceType, + user: Optional[User] = None, + resource_id: Optional[int] = None, + detail: Optional[dict] = None, + request: Optional[Request] = None, + status: int = 1, + error_message: Optional[str] = None, + ): + """ + 记录操作日志 + + Args: + db: 数据库会话 + operation_type: 操作类型 + resource_type: 资源类型 + user: 操作用户 + resource_id: 资源ID + detail: 操作详情(字典格式) + request: FastAPI Request对象 + status: 状态 0-失败 1-成功 + error_message: 错误信息 + """ + # 获取IP和User-Agent + ip_address = None + user_agent = None + if request: + ip_address = request.client.host if request.client else None + user_agent = request.headers.get("user-agent", "") + + # 创建日志记录 + log = OperationLog( + user_id=user.id if user else None, + username=user.username if user else "匿名", + operation_type=operation_type.value, + resource_type=resource_type.value, + resource_id=resource_id, + detail=json.dumps(detail, ensure_ascii=False) if detail else None, + ip_address=ip_address, + user_agent=user_agent, + status=status, + error_message=error_message, + ) + + db.add(log) + await db.commit() + + @staticmethod + async def log_project_operation( + db: AsyncSession, + operation_type: OperationType, + project_id: int, + user: User, + detail: Optional[dict] = None, + request: Optional[Request] = None, + ): + """记录项目相关操作""" + await LogService.log_operation( + db=db, + operation_type=operation_type, + resource_type=ResourceType.PROJECT, + user=user, + resource_id=project_id, + detail=detail, + request=request, + ) + + @staticmethod + async def log_file_operation( + db: AsyncSession, + operation_type: OperationType, + project_id: int, + file_path: str, + user: User, + detail: Optional[dict] = None, + request: Optional[Request] = None, + ): + """记录文件相关操作""" + file_detail = {"project_id": project_id, "file_path": file_path} + if detail: + file_detail.update(detail) + + await LogService.log_operation( + db=db, + operation_type=operation_type, + resource_type=ResourceType.FILE, + user=user, + resource_id=project_id, # 将project_id作为resource_id + detail=file_detail, + request=request, + ) + + @staticmethod + async def log_member_operation( + db: AsyncSession, + operation_type: OperationType, + project_id: int, + target_user_id: int, + user: User, + detail: Optional[dict] = None, + request: Optional[Request] = None, + ): + """记录成员相关操作""" + member_detail = {"project_id": project_id, "target_user_id": target_user_id} + if detail: + member_detail.update(detail) + + await LogService.log_operation( + db=db, + operation_type=operation_type, + resource_type=ResourceType.MEMBER, + user=user, + resource_id=project_id, + detail=member_detail, + request=request, + ) + + +log_service = LogService() diff --git a/backend/app/services/storage.py b/backend/app/services/storage.py index a560e94..f21748d 100644 --- a/backend/app/services/storage.py +++ b/backend/app/services/storage.py @@ -16,8 +16,12 @@ class StorageService: """文件存储服务类""" def __init__(self): - self.projects_root = Path(settings.PROJECTS_PATH) - self.temp_root = Path(settings.TEMP_PATH) + # 获取 backend 目录的绝对路径(app/services 的父目录的父目录) + backend_dir = Path(__file__).parent.parent.parent + + # 将配置中的路径转换为绝对路径 + self.projects_root = (backend_dir / settings.PROJECTS_PATH).resolve() + self.temp_root = (backend_dir / settings.TEMP_PATH).resolve() # 确保根目录存在 self.projects_root.mkdir(parents=True, exist_ok=True) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index c5e59ed..8107eea 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,4 +1,4 @@ -import { BrowserRouter, Routes, Route, Navigate, useParams } from 'react-router-dom' +import { BrowserRouter, Routes, Route, Navigate, useParams, Outlet } from 'react-router-dom' import { ConfigProvider } from 'antd' import zhCN from 'antd/locale/zh_CN' import Login from '@/pages/Login/Login' @@ -13,7 +13,9 @@ import ProfilePage from '@/pages/Profile/ProfilePage' import Permissions from '@/pages/System/Permissions' import Users from '@/pages/System/Users' import Roles from '@/pages/System/Roles' +import SystemLogs from '@/pages/SystemLogs/SystemLogs' import ProtectedRoute from '@/components/ProtectedRoute' +import MainLayout from '@/components/MainLayout/MainLayout' import '@/App.css' // 重定向到文档页面的组件 @@ -22,6 +24,15 @@ function RedirectToDocs() { return } +// 共享布局的包装组件 +function LayoutWrapper() { + return ( + + + + ) +} + function App() { return ( @@ -30,95 +41,25 @@ function App() { } /> {/* 项目预览(公开访问,无需登录) */} } /> - - - - } - /> - - - - } - /> - - - - } - /> - {/* 文档阅读模式 */} - - - - } - /> - {/* 文档编辑模式 */} - - - - } - /> - {/* 捕获所有 /projects/:projectId/* 路径(包括中文路径),重定向到文档页面 */} - } /> - {/* 功能开发中页面 */} - - - - } - /> - {/* 个人中心 */} - - - - } - /> - {/* 角色权限管理 */} - - - - } - /> - {/* 用户管理 */} - - - - } - /> - {/* 角色管理 */} - - - - } - /> + + {/* 使用共享布局的路由 */} + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> diff --git a/frontend/src/api/logs.js b/frontend/src/api/logs.js new file mode 100644 index 0000000..b8a4eb9 --- /dev/null +++ b/frontend/src/api/logs.js @@ -0,0 +1,22 @@ +import request from '@/utils/request' + +/** + * 获取操作日志列表 + */ +export const getOperationLogs = (params) => { + return request({ + url: '/logs/', + method: 'get', + params, + }) +} + +/** + * 获取日志统计信息 + */ +export const getLogStats = () => { + return request({ + url: '/logs/stats', + method: 'get', + }) +} diff --git a/frontend/src/api/project.js b/frontend/src/api/project.js index 2cd2cbe..7f76ca2 100644 --- a/frontend/src/api/project.js +++ b/frontend/src/api/project.js @@ -13,6 +13,26 @@ export function getMyProjects() { }) } +/** + * 获取我创建的项目列表 + */ +export function getOwnedProjects() { + return request({ + url: '/projects/my', + method: 'get', + }) +} + +/** + * 获取我参与的项目列表 + */ +export function getSharedProjects() { + return request({ + url: '/projects/shared', + method: 'get', + }) +} + /** * 创建项目 */ @@ -75,3 +95,13 @@ export function addProjectMember(projectId, data) { data, }) } + +/** + * 删除项目成员 + */ +export function removeProjectMember(projectId, userId) { + return request({ + url: `/projects/${projectId}/members/${userId}`, + method: 'delete', + }) +} diff --git a/frontend/src/api/search.js b/frontend/src/api/search.js new file mode 100644 index 0000000..894b99e --- /dev/null +++ b/frontend/src/api/search.js @@ -0,0 +1,15 @@ +/** + * 文档搜索相关 API + */ +import request from '@/utils/request' + +/** + * 搜索文档 + */ +export function searchDocuments(keyword) { + return request({ + url: '/search/documents', + method: 'get', + params: { keyword }, + }) +} diff --git a/frontend/src/components/MainLayout/AppSider.jsx b/frontend/src/components/MainLayout/AppSider.jsx index d20cae7..bfb8939 100644 --- a/frontend/src/components/MainLayout/AppSider.jsx +++ b/frontend/src/components/MainLayout/AppSider.jsx @@ -76,13 +76,13 @@ function AppSider({ collapsed, onToggle }) { return [] } - // 监听路径变化和收拢状态,自动打开父菜单 + // 监听菜单数据加载完成,初始化打开的父菜单 useEffect(() => { - if (!collapsed && menuData.length > 0) { + if (menuData.length > 0 && openKeys.length === 0) { const defaultKeys = getDefaultOpenKeys() setOpenKeys(defaultKeys) } - }, [location.pathname, collapsed, menuData]) + }, [menuData]) const handleMenuClick = ({ key }) => { // 查找对应的路径 diff --git a/frontend/src/pages/Constructing.jsx b/frontend/src/pages/Constructing.jsx index 059834e..4be8a95 100644 --- a/frontend/src/pages/Constructing.jsx +++ b/frontend/src/pages/Constructing.jsx @@ -1,13 +1,12 @@ import { Result, Button } from 'antd' import { useNavigate } from 'react-router-dom' import { ToolOutlined } from '@ant-design/icons' -import MainLayout from '@/components/MainLayout/MainLayout' function Constructing() { const navigate = useNavigate() return ( - +
-
+ ) } diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index aa46176..2e7b0c1 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -2,7 +2,6 @@ import { useState, useEffect } from 'react' import { Card, Row, Col, Statistic, Table, Spin } from 'antd' import { UserOutlined, ProjectOutlined, FileTextOutlined } from '@ant-design/icons' import { getDashboardStats } from '@/api/dashboard' -import MainLayout from '@/components/MainLayout/MainLayout' import Toast from '@/components/Toast/Toast' function Dashboard() { @@ -80,16 +79,16 @@ function Dashboard() { if (loading) { return ( - +
-
+ ) } return ( - +

管理员仪表盘

@@ -147,7 +146,7 @@ function Dashboard() { />
-
+ ) } diff --git a/frontend/src/pages/Desktop.jsx b/frontend/src/pages/Desktop.jsx index 65edbcc..ec1d5b3 100644 --- a/frontend/src/pages/Desktop.jsx +++ b/frontend/src/pages/Desktop.jsx @@ -1,8 +1,7 @@ import { useState, useEffect } from 'react' import { Card, Row, Col, Statistic, Table, Spin, Descriptions } from 'antd' -import { ProjectOutlined, FileTextOutlined } from '@ant-design/icons' +import { ProjectOutlined, FileTextOutlined, TeamOutlined } from '@ant-design/icons' import { getPersonalStats } from '@/api/dashboard' -import MainLayout from '@/components/MainLayout/MainLayout' import Toast from '@/components/Toast/Toast' function Desktop() { @@ -10,6 +9,7 @@ function Desktop() { const [userInfo, setUserInfo] = useState({}) const [stats, setStats] = useState({ personal_projects_count: 0, + shared_projects_count: 0, document_count: 0, }) const [recentPersonalProjects, setRecentPersonalProjects] = useState([]) @@ -89,16 +89,16 @@ function Desktop() { if (loading) { return ( - +
-
+ ) } return ( - +

个人桌面

@@ -116,7 +116,7 @@ function Desktop() { {/* 统计卡片 */} - + - + + + } + valueStyle={{ color: '#52c41a' }} + /> + + +
-
+ ) } diff --git a/frontend/src/pages/Document/DocumentEditor.jsx b/frontend/src/pages/Document/DocumentEditor.jsx index c0b3c7d..df35248 100644 --- a/frontend/src/pages/Document/DocumentEditor.jsx +++ b/frontend/src/pages/Document/DocumentEditor.jsx @@ -33,7 +33,6 @@ import { importDocuments, exportDirectory, } from '@/api/file' -import MainLayout from '@/components/MainLayout/MainLayout' import './DocumentEditor.css' const { Sider, Content } = Layout @@ -80,7 +79,9 @@ function DocumentEditor() { const fetchTree = async () => { try { const res = await getProjectTree(projectId) - setTreeData(res.data || []) + const data = res.data || {} + const tree = data.tree || data || [] // 兼容新旧格式 + setTreeData(tree) } catch (error) { console.error('Fetch tree error:', error) } @@ -141,13 +142,6 @@ function DocumentEditor() { return } - // 保护README.md - const fileName = path.split('/').pop() - if (fileName === 'README.md' && path.indexOf('/') === -1) { - message.error('根目录的README.md不允许删除') - return - } - Modal.confirm({ title: '确认删除', content: `确定要删除 ${path} 吗?`, @@ -603,8 +597,7 @@ function DocumentEditor() { } return ( - -
+
-
-
+ ) } diff --git a/frontend/src/pages/Document/DocumentPage.jsx b/frontend/src/pages/Document/DocumentPage.jsx index a07bffe..61f8956 100644 --- a/frontend/src/pages/Document/DocumentPage.jsx +++ b/frontend/src/pages/Document/DocumentPage.jsx @@ -10,7 +10,6 @@ import rehypeHighlight from 'rehype-highlight' import 'highlight.js/styles/github.css' import { getProjectTree, getFileContent } from '@/api/file' import { getProjectShareInfo, updateShareSettings } from '@/api/share' -import MainLayout from '@/components/MainLayout/MainLayout' import './DocumentPage.css' const { Sider, Content } = Layout @@ -29,6 +28,7 @@ function DocumentPage() { const [shareInfo, setShareInfo] = useState(null) const [hasPassword, setHasPassword] = useState(false) const [password, setPassword] = useState('') + const [userRole, setUserRole] = useState('viewer') // 用户角色:owner/admin/editor/viewer const contentRef = useRef(null) useEffect(() => { @@ -39,8 +39,12 @@ function DocumentPage() { const loadFileTree = async () => { try { const res = await getProjectTree(projectId) - const tree = res.data || [] + const data = res.data || {} + const tree = data.tree || data || [] // 兼容新旧格式 + const role = data.user_role || 'viewer' + setFileTree(tree) + setUserRole(role) // 默认打开 README.md const readmeNode = findReadme(tree) @@ -280,22 +284,24 @@ function DocumentPage() { const menuItems = convertTreeToMenuItems(fileTree) return ( - -
- +
+ {/* 左侧目录 */}

项目文档

- -
- ) } diff --git a/frontend/src/pages/Profile/ProfilePage.jsx b/frontend/src/pages/Profile/ProfilePage.jsx index 74aeb20..e4f32e5 100644 --- a/frontend/src/pages/Profile/ProfilePage.jsx +++ b/frontend/src/pages/Profile/ProfilePage.jsx @@ -3,7 +3,6 @@ import { Card, Tabs, Form, Input, Button, Avatar, Upload, message } from 'antd' import { UserOutlined, LockOutlined, UploadOutlined } from '@ant-design/icons' import { getCurrentUser, updateProfile, changePassword } from '@/api/auth' import useUserStore from '@/stores/userStore' -import MainLayout from '@/components/MainLayout/MainLayout' import Toast from '@/components/Toast/Toast' import './ProfilePage.css' @@ -228,14 +227,14 @@ function ProfilePage() { ] return ( - +

个人中心

-
+ ) } diff --git a/frontend/src/pages/ProjectList/ProjectList.css b/frontend/src/pages/ProjectList/ProjectList.css index ada13f8..599628b 100644 --- a/frontend/src/pages/ProjectList/ProjectList.css +++ b/frontend/src/pages/ProjectList/ProjectList.css @@ -19,6 +19,8 @@ text-align: center; cursor: pointer; transition: all 0.3s; + position: relative; + overflow: hidden; } .project-card:hover { @@ -26,6 +28,20 @@ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } +.project-card-public-badge { + position: absolute; + top: 8px; + right: -28px; + background: linear-gradient(135deg, #52c41a 0%, #389e0d 100%); + color: white; + padding: 2px 32px; + font-size: 11px; + font-weight: 500; + transform: rotate(45deg); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + z-index: 1; +} + .project-card-icon { margin-bottom: 16px; } diff --git a/frontend/src/pages/ProjectList/ProjectList.jsx b/frontend/src/pages/ProjectList/ProjectList.jsx index 0def11d..f93ee41 100644 --- a/frontend/src/pages/ProjectList/ProjectList.jsx +++ b/frontend/src/pages/ProjectList/ProjectList.jsx @@ -1,35 +1,53 @@ import { useState, useEffect } from 'react' import { useNavigate } from 'react-router-dom' -import { Card, Empty, Modal, Form, Input, Row, Col, Space, Button, Switch, message } from 'antd' -import { PlusOutlined, FolderOutlined, TeamOutlined, EyeOutlined, ShareAltOutlined, CopyOutlined } from '@ant-design/icons' -import { getMyProjects, createProject, deleteProject } from '@/api/project' +import { Card, Empty, Modal, Form, Input, Row, Col, Space, Button, Switch, message, Select, Table, Tag } from 'antd' +import { PlusOutlined, FolderOutlined, TeamOutlined, EyeOutlined, ShareAltOutlined, CopyOutlined, DeleteOutlined, EditOutlined, FileOutlined } from '@ant-design/icons' +import { getMyProjects, getOwnedProjects, getSharedProjects, createProject, deleteProject, updateProject, getProjectMembers, addProjectMember, removeProjectMember } from '@/api/project' import { getProjectShareInfo, updateShareSettings } from '@/api/share' -import MainLayout from '@/components/MainLayout/MainLayout' +import { getUserList } from '@/api/users' +import { searchDocuments } from '@/api/search' import ListActionBar from '@/components/ListActionBar/ListActionBar' import Toast from '@/components/Toast/Toast' import './ProjectList.css' -function ProjectList() { +function ProjectList({ type = 'my' }) { const [projects, setProjects] = useState([]) const [loading, setLoading] = useState(false) const [modalVisible, setModalVisible] = useState(false) + const [editModalVisible, setEditModalVisible] = useState(false) const [shareModalVisible, setShareModalVisible] = useState(false) + const [membersModalVisible, setMembersModalVisible] = useState(false) const [currentProject, setCurrentProject] = useState(null) const [shareInfo, setShareInfo] = useState(null) const [hasPassword, setHasPassword] = useState(false) const [password, setPassword] = useState('') const [searchKeyword, setSearchKeyword] = useState('') + const [searchResults, setSearchResults] = useState([]) + const [searching, setSearching] = useState(false) + const [hasSearched, setHasSearched] = useState(false) + const [members, setMembers] = useState([]) + const [users, setUsers] = useState([]) + const [loadingMembers, setLoadingMembers] = useState(false) const [form] = Form.useForm() + const [editForm] = Form.useForm() + const [memberForm] = Form.useForm() const navigate = useNavigate() useEffect(() => { fetchProjects() - }, []) + }, [type]) const fetchProjects = async () => { setLoading(true) try { - const res = await getMyProjects() + let res + if (type === 'my') { + res = await getOwnedProjects() + } else if (type === 'share') { + res = await getSharedProjects() + } else { + res = await getMyProjects() + } setProjects(res.data || []) } catch (error) { console.error('Fetch projects error:', error) @@ -53,19 +71,53 @@ function ProjectList() { const handleDeleteProject = async (projectId) => { Modal.confirm({ title: '确认删除', - content: '确定要删除这个项目吗?删除后可以在归档中找到', + content: '确定要删除这个项目吗?如果项目中存在文件,将无法删除。删除后将无法恢复!', + okText: '确定删除', + okType: 'danger', + cancelText: '取消', onOk: async () => { try { await deleteProject(projectId) - Toast.success('归档成功', '项目已归档') + Toast.success('删除成功', '项目已删除') fetchProjects() } catch (error) { console.error('Delete project error:', error) + const errorMsg = error.response?.data?.detail || error.message || '删除失败' + Toast.error('删除失败', errorMsg) } }, }) } + // 打开编辑项目 + const handleEdit = (e, project) => { + e.stopPropagation() + setCurrentProject(project) + editForm.setFieldsValue({ + name: project.name, + description: project.description, + is_public: project.is_public === 1, + }) + setEditModalVisible(true) + } + + // 更新项目 + const handleUpdateProject = async (values) => { + try { + await updateProject(currentProject.id, { + ...values, + is_public: values.is_public ? 1 : 0, + }) + message.success('项目更新成功') + setEditModalVisible(false) + editForm.resetFields() + fetchProjects() + } catch (error) { + console.error('Update project error:', error) + message.error('项目更新失败') + } + } + const handleOpenProject = (projectId) => { navigate(`/projects/${projectId}/docs`) } @@ -134,16 +186,136 @@ function ProjectList() { } } - // 过滤项目 - const filteredProjects = projects.filter((project) => - project.name.toLowerCase().includes(searchKeyword.toLowerCase()) - ) + // 打开成员管理 + const handleMembers = async (e, project) => { + e.stopPropagation() + setCurrentProject(project) + setMembersModalVisible(true) + setLoadingMembers(true) + + try { + // 并行加载成员列表和用户列表(只获取普通用户 role_id=3) + const [membersRes, usersRes] = await Promise.all([ + getProjectMembers(project.id), + getUserList({ page: 1, page_size: 100, status: 1, role_id: 3 }) + ]) + + console.log('Members Response:', membersRes) + console.log('Users Response:', usersRes) + + const membersData = membersRes.data || [] + // 后端返回格式: { code: 200, message: "success", data: [...], total, page, page_size } + const usersData = Array.isArray(usersRes.data) ? usersRes.data : [] + + console.log('Setting members:', membersData) + console.log('Setting users:', usersData) + + setMembers(membersData) + setUsers(usersData) + } catch (error) { + console.error('Get members error:', error) + console.error('Error details:', error.response) + message.error('获取数据失败: ' + (error.response?.data?.detail || error.message)) + } finally { + setLoadingMembers(false) + } + } + + // 添加成员 + const handleAddMember = async (values) => { + try { + await addProjectMember(currentProject.id, values) + message.success('成员添加成功') + memberForm.resetFields() + // 刷新成员列表(带用户名信息) + const res = await getProjectMembers(currentProject.id) + setMembers(res.data || []) + } catch (error) { + console.error('Add member error:', error) + const errorMsg = error.response?.data?.detail || error.message || '添加成员失败' + message.error(errorMsg) + } + } + + // 删除成员 + const handleRemoveMember = async (userId) => { + Modal.confirm({ + title: '确认删除', + content: '确定要删除这个成员吗?', + onOk: async () => { + try { + await removeProjectMember(currentProject.id, userId) + message.success('成员删除成功') + // 刷新成员列表(带用户名信息) + const res = await getProjectMembers(currentProject.id) + setMembers(res.data || []) + } catch (error) { + console.error('Remove member error:', error) + const errorMsg = error.response?.data?.detail || error.message || '删除成员失败' + message.error(errorMsg) + } + }, + }) + } + + // 处理搜索输入变化 + const handleSearchChange = (value) => { + setSearchKeyword(value) + // 如果清空了输入框,重置搜索状态 + if (!value || !value.trim()) { + setSearchResults([]) + setHasSearched(false) + } + } + + // 处理搜索 + const handleSearch = async (keyword) => { + setSearchKeyword(keyword) + + if (!keyword || !keyword.trim()) { + // 清空搜索,显示所有项目 + setSearchResults([]) + setSearching(false) + setHasSearched(false) + return + } + + setSearching(true) + setHasSearched(true) + try { + const res = await searchDocuments(keyword.trim()) + setSearchResults(res.data || []) + } catch (error) { + console.error('Search error:', error) + Toast.error('搜索失败', error.response?.data?.detail || error.message) + } finally { + setSearching(false) + } + } + + // 处理搜索结果点击 + const handleSearchResultClick = (item) => { + if (item.type === 'project') { + // 跳转到项目文档页 + navigate(`/projects/${item.project_id}/docs`) + } else if (item.type === 'file') { + // 跳转到文件 + navigate(`/projects/${item.project_id}/docs?file=${encodeURIComponent(item.file_path)}`) + } + } + + // 过滤项目(仅在未使用全局搜索时进行本地过滤) + const filteredProjects = !hasSearched && searchKeyword + ? projects.filter((project) => + project.name.toLowerCase().includes(searchKeyword.toLowerCase()) || + (project.description && project.description.toLowerCase().includes(searchKeyword.toLowerCase())) + ) + : projects return ( - -
+
, onClick: () => setModalVisible(true), }, - ]} + ] : []} search={{ - placeholder: '搜索项目...', + placeholder: '搜索项目或文件...', value: searchKeyword, - onChange: setSearchKeyword, - onSearch: (value) => setSearchKeyword(value), + onChange: handleSearchChange, + onSearch: handleSearch, }} showRefresh onRefresh={fetchProjects} /> - + {/* 搜索结果 */} + {hasSearched && searchResults.length > 0 && ( +
+
+ 找到 {searchResults.length} 个结果 +
+ + {searchResults.map((item, index) => ( + + handleSearchResultClick(item)} + > +
+ {item.type === 'project' ? ( + + ) : ( + + )} +
+ + +

+ {item.type === 'project' ? item.project_name : item.file_name} +

+ + {item.match_type} + +
+ {item.type === 'project' && ( +

+ {item.project_description || '暂无描述'} +

+ )} + {item.type === 'file' && ( +
+
项目: {item.project_name}
+
路径: {item.file_path}
+
+ )} +
+
+ + ))} +
+
+ )} + + {/* 正常项目列表 */} + {!hasSearched && ( + {filteredProjects.map((project) => ( handleOpenProject(project.id)} - actions={[ + actions={type === 'my' ? [ + handleEdit(e, project)} />, handleShare(e, project)} />, + handleMembers(e, project)} />, + ] : [ , - , + handleShare(e, project)} />, ]} > + {/* 公开项目标识 */} + {project.is_public === 1 && ( +
公开
+ )}
@@ -182,17 +412,35 @@ function ProjectList() {

{project.description || '暂无描述'}

访问: {project.visit_count} + {type === 'share' && project.owner_name && ( + + 所有者: {project.owner_nickname || project.owner_name} + + )} + {type === 'share' && project.user_role && ( + + 角色: {project.user_role === 'admin' ? '管理者' : project.user_role === 'editor' ? '编辑者' : '查看者'} + + )}
))} - {projects.length === 0 && !loading && ( + {filteredProjects.length === 0 && !loading && ( - + )}
+ )} + + {/* 搜索无结果提示 */} + {hasSearched && !searching && searchResults.length === 0 && ( +
+ +
+ )} + { + setEditModalVisible(false) + editForm.resetFields() + }} + footer={null} + > +
+ + + + + + + + + + + + + + + + + + + + + +
+
+
-
- - 访问密码保护 - - -
+ {/* 只有在我的项目中才显示密码设置功能 */} + {type === 'my' && ( + <> +
+ + 访问密码保护 + + +
- {hasPassword && ( -
- setPassword(e.target.value)} - /> - + {hasPassword && ( +
+ setPassword(e.target.value)} + /> + +
+ )} + + )} + + {/* 参与项目显示提示 */} + {type === 'share' && shareInfo.has_password && ( +
+ 该项目已设置访问密码保护
)} )} + + { + setMembersModalVisible(false) + memberForm.resetFields() + setMembers([]) + setUsers([]) + }} + footer={null} + width={700} + > + +
+ {users.length === 0 ? ( +
+ {loadingMembers ? '正在加载用户列表...' : '没有可添加的用户'} +
) : null} +
+ + + 管理员 + 编辑者 + 查看者 + + + + + +
+
+ +
+

当前成员

+ {members.length === 0 ? ( + + ) : ( + { + if (record.nickname) { + return `${username} (${record.nickname})` + } + return username || `用户ID: ${record.user_id}` + }, + }, + { + title: '角色', + dataIndex: 'role', + key: 'role', + render: (role) => { + const roleMap = { + admin: '管理员', + editor: '编辑者', + viewer: '查看者', + } + return roleMap[role] || role + }, + }, + { + title: '加入时间', + dataIndex: 'joined_at', + key: 'joined_at', + render: (time) => time ? new Date(time).toLocaleString('zh-CN') : '-', + }, + { + title: '操作', + key: 'action', + render: (_, record) => { + const isOwner = record.user_id === currentProject?.owner_id + return ( + + ) + }, + }, + ]} + /> + )} + + + - ) } diff --git a/frontend/src/pages/System/Permissions.jsx b/frontend/src/pages/System/Permissions.jsx index cf2b6c9..2b1e163 100644 --- a/frontend/src/pages/System/Permissions.jsx +++ b/frontend/src/pages/System/Permissions.jsx @@ -7,7 +7,6 @@ import { getRolePermissions, updateRolePermissions, } from '@/api/rolePermissions' -import MainLayout from '@/components/MainLayout/MainLayout' import ListTable from '@/components/ListTable/ListTable' import Toast from '@/components/Toast/Toast' @@ -153,7 +152,7 @@ function Permissions() { ] return ( - +

@@ -229,7 +228,7 @@ function Permissions() {

-
+ ) } diff --git a/frontend/src/pages/System/Roles.jsx b/frontend/src/pages/System/Roles.jsx index a2febc8..a7a4beb 100644 --- a/frontend/src/pages/System/Roles.jsx +++ b/frontend/src/pages/System/Roles.jsx @@ -28,7 +28,6 @@ import { deleteRole, getRoleUsers, } from '@/api/roles' -import MainLayout from '@/components/MainLayout/MainLayout' import ListTable from '@/components/ListTable/ListTable' import Toast from '@/components/Toast/Toast' @@ -314,7 +313,7 @@ function Roles() { ] return ( - +

角色管理

@@ -485,7 +484,7 @@ function Roles() { />
-
+ ) } diff --git a/frontend/src/pages/System/Users.jsx b/frontend/src/pages/System/Users.jsx index 24f9ae1..9980143 100644 --- a/frontend/src/pages/System/Users.jsx +++ b/frontend/src/pages/System/Users.jsx @@ -33,7 +33,6 @@ import { resetUserPassword, } from '@/api/users' import { getAllRoles } from '@/api/rolePermissions' -import MainLayout from '@/components/MainLayout/MainLayout' import ListTable from '@/components/ListTable/ListTable' import Toast from '@/components/Toast/Toast' @@ -299,7 +298,7 @@ function Users() { ] return ( - +

用户管理

@@ -459,7 +458,7 @@ function Users() {
-
+ ) } diff --git a/frontend/src/pages/SystemLogs/SystemLogs.css b/frontend/src/pages/SystemLogs/SystemLogs.css new file mode 100644 index 0000000..7aaa01c --- /dev/null +++ b/frontend/src/pages/SystemLogs/SystemLogs.css @@ -0,0 +1,9 @@ +.system-logs-container { + padding: 24px; +} + +.system-logs-container h2 { + margin-bottom: 24px; + font-size: 24px; + font-weight: 600; +} diff --git a/frontend/src/pages/SystemLogs/SystemLogs.jsx b/frontend/src/pages/SystemLogs/SystemLogs.jsx new file mode 100644 index 0000000..2ed6fc3 --- /dev/null +++ b/frontend/src/pages/SystemLogs/SystemLogs.jsx @@ -0,0 +1,388 @@ +import { useState, useEffect } from 'react' +import { Table, Card, Select, DatePicker, Space, Button, Tag, Statistic, Row, Col, Input, message } from 'antd' +import { ReloadOutlined, SearchOutlined } from '@ant-design/icons' +import { getOperationLogs, getLogStats } from '@/api/logs' +import { getUserList } from '@/api/users' +import dayjs from 'dayjs' +import './SystemLogs.css' + +const { RangePicker } = DatePicker +const { Option } = Select + +// 操作类型映射 +const operationTypeMap = { + create_project: '创建项目', + update_project: '更新项目', + delete_project: '删除项目', + add_member: '添加成员', + remove_member: '删除成员', + create_file: '创建文件', + create_dir: '创建目录', + delete_file: '删除文件', + rename_file: '重命名文件', + move_file: '移动文件', + save_file: '保存文件', + upload_image: '上传图片', + import_documents: '导入文档', + export_documents: '导出文档', + update_share_settings: '更新分享设置', + user_login: '用户登录', + user_logout: '用户登出', + user_register: '用户注册', +} + +// 资源类型映射 +const resourceTypeMap = { + project: '项目', + file: '文件', + member: '成员', + user: '用户', + share: '分享', +} + +// 操作类型颜色 +const operationTypeColors = { + create_project: 'green', + update_project: 'blue', + delete_project: 'red', + add_member: 'cyan', + remove_member: 'orange', + create_file: 'green', + create_dir: 'green', + delete_file: 'red', + rename_file: 'blue', + move_file: 'blue', + save_file: 'blue', + upload_image: 'purple', + import_documents: 'purple', + export_documents: 'purple', + update_share_settings: 'blue', + user_login: 'green', + user_logout: 'default', + user_register: 'green', +} + +function SystemLogs() { + const [logs, setLogs] = useState([]) + const [loading, setLoading] = useState(false) + const [pagination, setPagination] = useState({ + current: 1, + pageSize: 20, + total: 0, + }) + const [stats, setStats] = useState(null) + const [users, setUsers] = useState([]) + + // 筛选条件 + const [filters, setFilters] = useState({ + operation_type: undefined, + resource_type: undefined, + user_id: undefined, + project_id: undefined, + dateRange: null, + }) + + useEffect(() => { + fetchLogs() + fetchStats() + fetchUsers() + }, []) + + const fetchLogs = async (page = 1, pageSize = 20) => { + setLoading(true) + try { + const params = { + page, + page_size: pageSize, + } + + // 只添加有值的过滤条件 + if (filters.operation_type) { + params.operation_type = filters.operation_type + } + if (filters.resource_type) { + params.resource_type = filters.resource_type + } + if (filters.user_id) { + params.user_id = filters.user_id + } + if (filters.project_id) { + params.project_id = filters.project_id + } + + // 处理日期范围 + if (filters.dateRange && filters.dateRange.length === 2) { + params.start_date = filters.dateRange[0].format('YYYY-MM-DD') + params.end_date = filters.dateRange[1].format('YYYY-MM-DD') + } + + const res = await getOperationLogs(params) + setLogs(res.data.items || []) + setPagination({ + current: res.data.page, + pageSize: res.data.page_size, + total: res.data.total, + }) + } catch (error) { + console.error('Fetch logs error:', error) + message.error('获取日志失败') + } finally { + setLoading(false) + } + } + + const fetchStats = async () => { + try { + const res = await getLogStats() + setStats(res.data) + } catch (error) { + console.error('Fetch stats error:', error) + } + } + + const fetchUsers = async () => { + try { + const res = await getUserList({ page: 1, page_size: 100, status: 1 }) + console.log('Fetch users response:', res) + // 后端返回格式: { code: 200, message: "success", data: [...], total, page, page_size } + const usersData = Array.isArray(res.data) ? res.data : [] + console.log('Users data:', usersData) + setUsers(usersData) + } catch (error) { + console.error('Fetch users error:', error) + message.error('获取用户列表失败') + } + } + + const handleTableChange = (newPagination) => { + fetchLogs(newPagination.current, newPagination.pageSize) + } + + const handleSearch = () => { + // 清理空值参数 + const cleanFilters = { ...filters } + if (!cleanFilters.project_id || cleanFilters.project_id === '') { + delete cleanFilters.project_id + } + setFilters(cleanFilters) + fetchLogs(1, pagination.pageSize) + } + + const handleReset = () => { + setFilters({ + operation_type: undefined, + resource_type: undefined, + user_id: undefined, + project_id: undefined, + dateRange: null, + }) + setTimeout(() => { + fetchLogs(1, pagination.pageSize) + }, 0) + } + + const columns = [ + { + title: 'ID', + dataIndex: 'id', + key: 'id', + width: 80, + }, + { + title: '操作用户', + dataIndex: 'username', + key: 'username', + width: 120, + }, + { + title: '操作类型', + dataIndex: 'operation_type', + key: 'operation_type', + width: 140, + render: (type) => ( + + {operationTypeMap[type] || type} + + ), + }, + { + title: '资源类型', + dataIndex: 'resource_type', + key: 'resource_type', + width: 100, + render: (type) => resourceTypeMap[type] || type, + }, + { + title: '资源ID', + dataIndex: 'resource_id', + key: 'resource_id', + width: 100, + }, + { + title: '详情', + dataIndex: 'detail', + key: 'detail', + ellipsis: true, + render: (detail) => { + if (!detail) return '-' + try { + const parsed = JSON.parse(detail) + return ( +
+ {Object.entries(parsed).map(([key, value]) => ( +
+ {key}: {JSON.stringify(value)} +
+ ))} +
+ ) + } catch { + return detail + } + }, + }, + { + title: 'IP地址', + dataIndex: 'ip_address', + key: 'ip_address', + width: 140, + }, + { + title: '状态', + dataIndex: 'status', + key: 'status', + width: 80, + render: (status) => ( + + {status === 1 ? '成功' : '失败'} + + ), + }, + { + title: '操作时间', + dataIndex: 'created_at', + key: 'created_at', + width: 180, + render: (time) => time ? dayjs(time).format('YYYY-MM-DD HH:mm:ss') : '-', + }, + ] + + return ( +
+

系统日志

+ + {/* 统计信息 */} + {stats && ( + +
+ + + + + + + + + + + + + + + + + + + + + )} + + {/* 筛选条件 */} + + + + + + + + + setFilters({ ...filters, project_id: e.target.value })} + /> + + setFilters({ ...filters, dateRange: dates })} + /> + + + + + + + {/* 日志列表 */} + +
+ + + ) +} + +export default SystemLogs diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 6b937b9..8c82751 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -84,7 +84,7 @@ resolved "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.28.5.tgz" integrity sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.28.0": +"@babel/core@^7.28.0": version "7.28.5" resolved "https://registry.npmmirror.com/@babel/core/-/core-7.28.5.tgz" integrity sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw== @@ -288,11 +288,121 @@ resolved "https://registry.npmmirror.com/@emotion/unitless/-/unitless-0.7.5.tgz" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + "@esbuild/darwin-arm64@0.21.5": version "0.21.5" resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz" integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + "@eslint-community/eslint-utils@^4.2.0": version "4.9.0" resolved "https://registry.npmmirror.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz" @@ -386,7 +496,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -494,11 +604,116 @@ resolved "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz" integrity sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA== +"@rollup/rollup-android-arm-eabi@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz#3d12635170ef3d32aa9222b4fb92b5d5400f08e9" + integrity sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ== + +"@rollup/rollup-android-arm64@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz#cd2a448be8fb337f6e5ca12a3053a407ac80766d" + integrity sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw== + "@rollup/rollup-darwin-arm64@4.53.5": version "4.53.5" resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz" integrity sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ== +"@rollup/rollup-darwin-x64@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz#76956ce183eb461a58735770b7bf3030a549ceea" + integrity sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA== + +"@rollup/rollup-freebsd-arm64@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz#287448b57d619007b14d34ed35bf1bc4f41c023b" + integrity sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw== + +"@rollup/rollup-freebsd-x64@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz#e6dca813e189aa189dab821ea8807f48873b80f2" + integrity sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ== + +"@rollup/rollup-linux-arm-gnueabihf@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz#74045a96fa6c5b1b1269440a68d1496d34b1730a" + integrity sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA== + +"@rollup/rollup-linux-arm-musleabihf@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz#7d175bddc9acffc40431ee3fb9417136ccc499e1" + integrity sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ== + +"@rollup/rollup-linux-arm64-gnu@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz#228b0aec95b24f4080175b51396cd14cb275e38f" + integrity sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg== + +"@rollup/rollup-linux-arm64-musl@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz#079050e023fad9bbb95d1d36fcfad23eeb0e1caa" + integrity sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g== + +"@rollup/rollup-linux-loong64-gnu@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz#3849451858c4d5c8838b5e16ec339b8e49aaf68a" + integrity sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA== + +"@rollup/rollup-linux-ppc64-gnu@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz#10bdab69c660f6f7b48e23f17c42637aa1f9b29a" + integrity sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q== + +"@rollup/rollup-linux-riscv64-gnu@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz#c0e776c6193369ee16f8e9ebf6a6ec09828d603d" + integrity sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ== + +"@rollup/rollup-linux-riscv64-musl@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz#6fcfb9084822036b9e4ff66e5c8b472d77226fae" + integrity sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w== + +"@rollup/rollup-linux-s390x-gnu@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz#204bf1f758b65263adad3183d1ea7c9fc333e453" + integrity sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw== + +"@rollup/rollup-linux-x64-gnu@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz#704a927285c370b4481a77e5a6468ebc841f72ca" + integrity sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw== + +"@rollup/rollup-linux-x64-musl@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz#3a7ccf6239f7efc6745b95075cf855b137cd0b52" + integrity sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg== + +"@rollup/rollup-openharmony-arm64@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz#cb29644e4330b8d9aec0c594bf092222545db218" + integrity sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg== + +"@rollup/rollup-win32-arm64-msvc@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz#bae9daf924900b600f6a53c0659b12cb2f6c33e4" + integrity sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA== + +"@rollup/rollup-win32-ia32-msvc@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz#48002d2b9e4ab93049acd0d399c7aa7f7c5f363c" + integrity sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w== + +"@rollup/rollup-win32-x64-gnu@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz#aa0344b25dc31f2d822caf886786e377b45e660b" + integrity sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A== + +"@rollup/rollup-win32-x64-msvc@4.53.5": + version "4.53.5" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz#e0d19dffcf25f0fd86f50402a3413004003939e9" + integrity sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ== + "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.npmmirror.com/@types/babel__core/-/babel__core-7.20.5.tgz" @@ -532,7 +747,7 @@ dependencies: "@babel/types" "^7.28.2" -"@types/codemirror@^5.0.0", "@types/codemirror@^5.60.7": +"@types/codemirror@^5.60.7": version "5.60.17" resolved "https://registry.npmmirror.com/@types/codemirror/-/codemirror-5.60.17.tgz" integrity sha512-AZq2FIsUHVMlp7VSe2hTfl5w4pcUkoFkM3zVsRKsn1ca8CXRDYvnin04+HP2REkwsxemuHqvDofdlhUWNpbwfw== @@ -546,7 +761,14 @@ dependencies: "@types/ms" "*" -"@types/estree@*", "@types/estree@1.0.8": +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.npmmirror.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@1.0.8", "@types/estree@^1.0.0": version "1.0.8" resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.8.tgz" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== @@ -558,6 +780,13 @@ dependencies: "@types/unist" "^2" +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.npmmirror.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== + dependencies: + "@types/unist" "*" + "@types/js-yaml@^4.0.5": version "4.0.9" resolved "https://registry.npmmirror.com/@types/js-yaml/-/js-yaml-4.0.9.tgz" @@ -582,6 +811,13 @@ dependencies: "@types/unist" "^2" +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + "@types/ms@*": version "2.1.0" resolved "https://registry.npmmirror.com/@types/ms/-/ms-2.1.0.tgz" @@ -602,7 +838,7 @@ resolved "https://registry.npmmirror.com/@types/react-dom/-/react-dom-18.3.7.tgz" integrity sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ== -"@types/react@^18.0.0", "@types/react@^18.2.43", "@types/react@>=16.8": +"@types/react@^18.2.43": version "18.3.27" resolved "https://registry.npmmirror.com/@types/react/-/react-18.3.27.tgz" integrity sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w== @@ -617,14 +853,19 @@ dependencies: "@types/estree" "*" +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + "@types/unist@^2", "@types/unist@^2.0.0": version "2.0.11" resolved "https://registry.npmmirror.com/@types/unist/-/unist-2.0.11.tgz" integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== -"@ungap/structured-clone@^1.2.0": +"@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0": version "1.3.0" - resolved "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz" + resolved "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== "@vitejs/plugin-react@^4.2.1": @@ -644,7 +885,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: +acorn@^8.9.0: version "8.15.0" resolved "https://registry.npmmirror.com/acorn/-/acorn-8.15.0.tgz" integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== @@ -899,7 +1140,7 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.24.0, browserslist@^4.28.1, "browserslist@>= 4.21.0": +browserslist@^4.24.0, browserslist@^4.28.1: version "4.28.1" resolved "https://registry.npmmirror.com/browserslist/-/browserslist-4.28.1.tgz" integrity sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA== @@ -910,7 +1151,7 @@ browserslist@^4.24.0, browserslist@^4.28.1, "browserslist@>= 4.21.0": node-releases "^2.0.27" update-browserslist-db "^1.2.0" -bytemd@^1.22.0, bytemd@^1.5.0, bytemd@1.22.0: +bytemd@1.22.0, bytemd@^1.22.0: version "1.22.0" resolved "https://registry.npmmirror.com/bytemd/-/bytemd-1.22.0.tgz" integrity sha512-2vmegXnnsOxNufRrrQGHYKwgTmx6H+h40ZZs3DAw/SS5O4mBzO9evc1HD39CqW9wglGNBJxMg257pv9pgAGl+A== @@ -1004,6 +1245,11 @@ character-entities@^2.0.0: resolved "https://registry.npmmirror.com/character-entities/-/character-entities-2.0.2.tgz" integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + chokidar@^3.6.0: version "3.6.0" resolved "https://registry.npmmirror.com/chokidar/-/chokidar-3.6.0.tgz" @@ -1019,7 +1265,7 @@ chokidar@^3.6.0: optionalDependencies: fsevents "~2.3.2" -classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2, classnames@^2.5.1, classnames@2.x: +classnames@2.x, classnames@^2.2.1, classnames@^2.2.3, classnames@^2.2.5, classnames@^2.2.6, classnames@^2.3.1, classnames@^2.3.2, classnames@^2.5.1: version "2.5.1" resolved "https://registry.npmmirror.com/classnames/-/classnames-2.5.1.tgz" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== @@ -1126,7 +1372,7 @@ data-view-byte-offset@^1.0.1: es-errors "^1.3.0" is-data-view "^1.0.1" -dayjs@^1.11.10, dayjs@^1.11.11, "dayjs@>= 1.x": +dayjs@^1.11.10, dayjs@^1.11.11: version "1.11.19" resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz" integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw== @@ -1178,6 +1424,13 @@ dequal@^2.0.0: resolved "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.npmmirror.com/didyoumean/-/didyoumean-1.2.2.tgz" @@ -1221,6 +1474,11 @@ electron-to-chromium@^1.5.263: resolved "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz" integrity sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw== +entities@^6.0.0: + version "6.0.1" + resolved "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz#c28c34a43379ca7f61d074130b2f5f7020a30694" + integrity sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g== + es-abstract@^1.17.5, es-abstract@^1.23.2, es-abstract@^1.23.3, es-abstract@^1.23.5, es-abstract@^1.23.6, es-abstract@^1.23.9, es-abstract@^1.24.0, es-abstract@^1.24.1: version "1.24.1" resolved "https://registry.npmmirror.com/es-abstract/-/es-abstract-1.24.1.tgz" @@ -1437,7 +1695,7 @@ eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: resolved "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -"eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^8.55.0, eslint@>=8.40: +eslint@^8.55.0: version "8.57.1" resolved "https://registry.npmmirror.com/eslint/-/eslint-8.57.1.tgz" integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA== @@ -1509,6 +1767,11 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz" @@ -1713,6 +1976,11 @@ get-symbol-description@^1.1.0: es-errors "^1.3.0" get-intrinsic "^1.2.6" +github-slugger@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/github-slugger/-/github-slugger-2.0.0.tgz#52cf2f9279a21eb6c59dd385b410f0c0adda8f1a" + integrity sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz" @@ -1820,6 +2088,34 @@ hast-util-from-parse5@^7.0.0: vfile-location "^4.0.0" web-namespaces "^2.0.0" +hast-util-from-parse5@^8.0.0: + version "8.0.3" + resolved "https://registry.npmmirror.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e" + integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + devlop "^1.0.0" + hastscript "^9.0.0" + property-information "^7.0.0" + vfile "^6.0.0" + vfile-location "^5.0.0" + web-namespaces "^2.0.0" + +hast-util-heading-rank@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz#2d5c6f2807a7af5c45f74e623498dd6054d2aba8" + integrity sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-is-element@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz#6e31a6532c217e5b533848c7e52c9d9369ca0932" + integrity sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g== + dependencies: + "@types/hast" "^3.0.0" + hast-util-parse-selector@^3.0.0: version "3.1.1" resolved "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-3.1.1.tgz" @@ -1827,6 +2123,13 @@ hast-util-parse-selector@^3.0.0: dependencies: "@types/hast" "^2.0.0" +hast-util-parse-selector@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27" + integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw@^7.0.0, hast-util-raw@^7.2.0: version "7.2.3" resolved "https://registry.npmmirror.com/hast-util-raw/-/hast-util-raw-7.2.3.tgz" @@ -1844,6 +2147,25 @@ hast-util-raw@^7.0.0, hast-util-raw@^7.2.0: web-namespaces "^2.0.0" zwitch "^2.0.0" +hast-util-raw@^9.0.0: + version "9.1.0" + resolved "https://registry.npmmirror.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz#79b66b26f6f68fb50dfb4716b2cdca90d92adf2e" + integrity sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + "@ungap/structured-clone" "^1.0.0" + hast-util-from-parse5 "^8.0.0" + hast-util-to-parse5 "^8.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + parse5 "^7.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + hast-util-sanitize@^4.0.0, hast-util-sanitize@^4.1.0: version "4.1.0" resolved "https://registry.npmmirror.com/hast-util-sanitize/-/hast-util-sanitize-4.1.0.tgz" @@ -1868,6 +2190,27 @@ hast-util-to-html@^8.0.0: stringify-entities "^4.0.0" zwitch "^2.0.4" +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.6" + resolved "https://registry.npmmirror.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + hast-util-to-parse5@^7.0.0: version "7.1.0" resolved "https://registry.npmmirror.com/hast-util-to-parse5/-/hast-util-to-parse5-7.1.0.tgz" @@ -1880,11 +2223,48 @@ hast-util-to-parse5@^7.0.0: web-namespaces "^2.0.0" zwitch "^2.0.0" +hast-util-to-parse5@^8.0.0: + version "8.0.1" + resolved "https://registry.npmmirror.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz#95aa391cc0514b4951418d01c883d1038af42f5d" + integrity sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + web-namespaces "^2.0.0" + zwitch "^2.0.0" + +hast-util-to-string@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz#a4f15e682849326dd211c97129c94b0c3e76527c" + integrity sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A== + dependencies: + "@types/hast" "^3.0.0" + +hast-util-to-text@^4.0.0: + version "4.0.2" + resolved "https://registry.npmmirror.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz#57b676931e71bf9cb852453678495b3080bfae3e" + integrity sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + hast-util-is-element "^3.0.0" + unist-util-find-after "^5.0.0" + hast-util-whitespace@^2.0.0: version "2.0.1" resolved "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz" integrity sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng== +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + hastscript@^7.0.0: version "7.2.0" resolved "https://registry.npmmirror.com/hastscript/-/hastscript-7.2.0.tgz" @@ -1896,16 +2276,37 @@ hastscript@^7.0.0: property-information "^6.0.0" space-separated-tokens "^2.0.0" -highlight.js@^11.7.0: +hastscript@^9.0.0: + version "9.0.1" + resolved "https://registry.npmmirror.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff" + integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w== + dependencies: + "@types/hast" "^3.0.0" + comma-separated-tokens "^2.0.0" + hast-util-parse-selector "^4.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + +highlight.js@^11.7.0, highlight.js@~11.11.0: version "11.11.1" resolved "https://registry.npmmirror.com/highlight.js/-/highlight.js-11.11.1.tgz" integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w== +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== + html-void-elements@^2.0.0: version "2.0.1" resolved "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-2.0.1.tgz" integrity sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A== +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== + ignore@^5.2.0: version "5.3.2" resolved "https://registry.npmmirror.com/ignore/-/ignore-5.3.2.tgz" @@ -1937,6 +2338,11 @@ inherits@2: resolved "https://registry.npmmirror.com/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +inline-style-parser@0.2.7: + version "0.2.7" + resolved "https://registry.npmmirror.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909" + integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== + internal-slot@^1.1.0: version "1.1.0" resolved "https://registry.npmmirror.com/internal-slot/-/internal-slot-1.1.0.tgz" @@ -1946,6 +2352,19 @@ internal-slot@^1.1.0: hasown "^2.0.2" side-channel "^1.1.0" +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + is-array-buffer@^3.0.4, is-array-buffer@^3.0.5: version "3.0.5" resolved "https://registry.npmmirror.com/is-array-buffer/-/is-array-buffer-3.0.5.tgz" @@ -2022,6 +2441,11 @@ is-date-object@^1.0.5, is-date-object@^1.1.0: call-bound "^1.0.2" has-tostringtag "^1.0.2" +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz" @@ -2052,6 +2476,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + is-map@^2.0.3: version "2.0.3" resolved "https://registry.npmmirror.com/is-map/-/is-map-2.0.3.tgz" @@ -2173,7 +2602,7 @@ iterator.prototype@^1.1.5: has-symbols "^1.1.0" set-function-name "^2.0.2" -jiti@^1.21.7, jiti@>=1.21.0: +jiti@^1.21.7: version "1.21.7" resolved "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz" integrity sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A== @@ -2291,6 +2720,15 @@ loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" +lowlight@^3.0.0: + version "3.3.0" + resolved "https://registry.npmmirror.com/lowlight/-/lowlight-3.3.0.tgz#007b8a5bfcfd27cc65b96246d2de3e9dd4e23c6c" + integrity sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ== + dependencies: + "@types/hast" "^3.0.0" + devlop "^1.0.0" + highlight.js "~11.11.0" + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz" @@ -2327,6 +2765,16 @@ mdast-util-find-and-replace@^2.0.0: unist-util-is "^5.0.0" unist-util-visit-parents "^5.0.0" +mdast-util-find-and-replace@^3.0.0: + version "3.0.2" + resolved "https://registry.npmmirror.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz#70a3174c894e14df722abf43bc250cbae44b11df" + integrity sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg== + dependencies: + "@types/mdast" "^4.0.0" + escape-string-regexp "^5.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + mdast-util-from-markdown@^1.0.0: version "1.3.1" resolved "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-1.3.1.tgz" @@ -2345,6 +2793,24 @@ mdast-util-from-markdown@^1.0.0: unist-util-stringify-position "^3.0.0" uvu "^0.5.0" +mdast-util-from-markdown@^2.0.0: + version "2.0.2" + resolved "https://registry.npmmirror.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz#4850390ca7cf17413a9b9a0fbefcd1bc0eb4160a" + integrity sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + mdast-util-frontmatter@^1.0.0: version "1.0.1" resolved "https://registry.npmmirror.com/mdast-util-frontmatter/-/mdast-util-frontmatter-1.0.1.tgz" @@ -2364,6 +2830,17 @@ mdast-util-gfm-autolink-literal@^1.0.0: mdast-util-find-and-replace "^2.0.0" micromark-util-character "^1.0.0" +mdast-util-gfm-autolink-literal@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz#abd557630337bd30a6d5a4bd8252e1c2dc0875d5" + integrity sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ== + dependencies: + "@types/mdast" "^4.0.0" + ccount "^2.0.0" + devlop "^1.0.0" + mdast-util-find-and-replace "^3.0.0" + micromark-util-character "^2.0.0" + mdast-util-gfm-footnote@^1.0.0: version "1.0.2" resolved "https://registry.npmmirror.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-1.0.2.tgz" @@ -2373,6 +2850,17 @@ mdast-util-gfm-footnote@^1.0.0: mdast-util-to-markdown "^1.3.0" micromark-util-normalize-identifier "^1.0.0" +mdast-util-gfm-footnote@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz#7778e9d9ca3df7238cc2bd3fa2b1bf6a65b19403" + integrity sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + mdast-util-gfm-strikethrough@^1.0.0: version "1.0.3" resolved "https://registry.npmmirror.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-1.0.3.tgz" @@ -2381,6 +2869,15 @@ mdast-util-gfm-strikethrough@^1.0.0: "@types/mdast" "^3.0.0" mdast-util-to-markdown "^1.3.0" +mdast-util-gfm-strikethrough@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16" + integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + mdast-util-gfm-table@^1.0.0: version "1.0.7" resolved "https://registry.npmmirror.com/mdast-util-gfm-table/-/mdast-util-gfm-table-1.0.7.tgz" @@ -2391,6 +2888,17 @@ mdast-util-gfm-table@^1.0.0: mdast-util-from-markdown "^1.0.0" mdast-util-to-markdown "^1.3.0" +mdast-util-gfm-table@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38" + integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + markdown-table "^3.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + mdast-util-gfm-task-list-item@^1.0.0: version "1.0.2" resolved "https://registry.npmmirror.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-1.0.2.tgz" @@ -2399,6 +2907,16 @@ mdast-util-gfm-task-list-item@^1.0.0: "@types/mdast" "^3.0.0" mdast-util-to-markdown "^1.3.0" +mdast-util-gfm-task-list-item@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936" + integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + mdast-util-gfm@^2.0.0: version "2.0.2" resolved "https://registry.npmmirror.com/mdast-util-gfm/-/mdast-util-gfm-2.0.2.tgz" @@ -2412,6 +2930,61 @@ mdast-util-gfm@^2.0.0: mdast-util-gfm-task-list-item "^1.0.0" mdast-util-to-markdown "^1.0.0" +mdast-util-gfm@^3.0.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz#2cdf63b92c2a331406b0fb0db4c077c1b0331751" + integrity sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-gfm-autolink-literal "^2.0.0" + mdast-util-gfm-footnote "^2.0.0" + mdast-util-gfm-strikethrough "^2.0.0" + mdast-util-gfm-table "^2.0.0" + mdast-util-gfm-task-list-item "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.npmmirror.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + mdast-util-newline-to-break@^1.0.0: version "1.0.0" resolved "https://registry.npmmirror.com/mdast-util-newline-to-break/-/mdast-util-newline-to-break-1.0.0.tgz" @@ -2428,6 +3001,14 @@ mdast-util-phrasing@^3.0.0: "@types/mdast" "^3.0.0" unist-util-is "^5.0.0" +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.npmmirror.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + mdast-util-to-hast@^12.1.0: version "12.3.0" resolved "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-12.3.0.tgz" @@ -2442,6 +3023,21 @@ mdast-util-to-hast@^12.1.0: unist-util-position "^4.0.0" unist-util-visit "^4.0.0" +mdast-util-to-hast@^13.0.0: + version "13.2.1" + resolved "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" + integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0: version "1.5.0" resolved "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-1.5.0.tgz" @@ -2456,6 +3052,21 @@ mdast-util-to-markdown@^1.0.0, mdast-util-to-markdown@^1.3.0: unist-util-visit "^4.0.0" zwitch "^2.0.0" +mdast-util-to-markdown@^2.0.0: + version "2.1.2" + resolved "https://registry.npmmirror.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz#f910ffe60897f04bb4b7e7ee434486f76288361b" + integrity sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0: version "3.2.0" resolved "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-3.2.0.tgz" @@ -2463,6 +3074,13 @@ mdast-util-to-string@^3.0.0, mdast-util-to-string@^3.1.0: dependencies: "@types/mdast" "^3.0.0" +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + merge2@^1.3.0: version "1.4.1" resolved "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz" @@ -2490,6 +3108,28 @@ micromark-core-commonmark@^1.0.0, micromark-core-commonmark@^1.0.1: micromark-util-types "^1.0.1" uvu "^0.5.0" +micromark-core-commonmark@^2.0.0: + version "2.0.3" + resolved "https://registry.npmmirror.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4" + integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-extension-frontmatter@^1.0.0: version "1.1.1" resolved "https://registry.npmmirror.com/micromark-extension-frontmatter/-/micromark-extension-frontmatter-1.1.1.tgz" @@ -2510,6 +3150,16 @@ micromark-extension-gfm-autolink-literal@^1.0.0: micromark-util-symbol "^1.0.0" micromark-util-types "^1.0.0" +micromark-extension-gfm-autolink-literal@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz#6286aee9686c4462c1e3552a9d505feddceeb935" + integrity sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-extension-gfm-footnote@^1.0.0: version "1.1.2" resolved "https://registry.npmmirror.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-1.1.2.tgz" @@ -2524,6 +3174,20 @@ micromark-extension-gfm-footnote@^1.0.0: micromark-util-types "^1.0.0" uvu "^0.5.0" +micromark-extension-gfm-footnote@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz#4dab56d4e398b9853f6fe4efac4fc9361f3e0750" + integrity sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw== + dependencies: + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-extension-gfm-strikethrough@^1.0.0: version "1.0.7" resolved "https://registry.npmmirror.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-1.0.7.tgz" @@ -2536,6 +3200,18 @@ micromark-extension-gfm-strikethrough@^1.0.0: micromark-util-types "^1.0.0" uvu "^0.5.0" +micromark-extension-gfm-strikethrough@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz#86106df8b3a692b5f6a92280d3879be6be46d923" + integrity sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-extension-gfm-table@^1.0.0: version "1.0.7" resolved "https://registry.npmmirror.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-1.0.7.tgz" @@ -2547,6 +3223,17 @@ micromark-extension-gfm-table@^1.0.0: micromark-util-types "^1.0.0" uvu "^0.5.0" +micromark-extension-gfm-table@^2.0.0: + version "2.1.1" + resolved "https://registry.npmmirror.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz#fac70bcbf51fe65f5f44033118d39be8a9b5940b" + integrity sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-extension-gfm-tagfilter@^1.0.0: version "1.0.2" resolved "https://registry.npmmirror.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-1.0.2.tgz" @@ -2554,6 +3241,13 @@ micromark-extension-gfm-tagfilter@^1.0.0: dependencies: micromark-util-types "^1.0.0" +micromark-extension-gfm-tagfilter@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57" + integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg== + dependencies: + micromark-util-types "^2.0.0" + micromark-extension-gfm-task-list-item@^1.0.0: version "1.0.5" resolved "https://registry.npmmirror.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-1.0.5.tgz" @@ -2565,6 +3259,17 @@ micromark-extension-gfm-task-list-item@^1.0.0: micromark-util-types "^1.0.0" uvu "^0.5.0" +micromark-extension-gfm-task-list-item@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz#bcc34d805639829990ec175c3eea12bb5b781f2c" + integrity sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw== + dependencies: + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-extension-gfm@^2.0.0: version "2.0.3" resolved "https://registry.npmmirror.com/micromark-extension-gfm/-/micromark-extension-gfm-2.0.3.tgz" @@ -2579,6 +3284,20 @@ micromark-extension-gfm@^2.0.0: micromark-util-combine-extensions "^1.0.0" micromark-util-types "^1.0.0" +micromark-extension-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b" + integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w== + dependencies: + micromark-extension-gfm-autolink-literal "^2.0.0" + micromark-extension-gfm-footnote "^2.0.0" + micromark-extension-gfm-strikethrough "^2.0.0" + micromark-extension-gfm-table "^2.0.0" + micromark-extension-gfm-tagfilter "^2.0.0" + micromark-extension-gfm-task-list-item "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + micromark-factory-destination@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-1.1.0.tgz" @@ -2588,6 +3307,15 @@ micromark-factory-destination@^1.0.0: micromark-util-symbol "^1.0.0" micromark-util-types "^1.0.0" +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-factory-label@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-1.1.0.tgz" @@ -2598,6 +3326,16 @@ micromark-factory-label@^1.0.0: micromark-util-types "^1.0.0" uvu "^0.5.0" +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-factory-space@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-1.1.0.tgz" @@ -2606,6 +3344,14 @@ micromark-factory-space@^1.0.0: micromark-util-character "^1.0.0" micromark-util-types "^1.0.0" +micromark-factory-space@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz#36d0212e962b2b3121f8525fc7a3c7c029f334fc" + integrity sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + micromark-factory-title@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-1.1.0.tgz" @@ -2616,6 +3362,16 @@ micromark-factory-title@^1.0.0: micromark-util-symbol "^1.0.0" micromark-util-types "^1.0.0" +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-factory-whitespace@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-1.1.0.tgz" @@ -2626,6 +3382,16 @@ micromark-factory-whitespace@^1.0.0: micromark-util-symbol "^1.0.0" micromark-util-types "^1.0.0" +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-util-character@^1.0.0: version "1.2.0" resolved "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-1.2.0.tgz" @@ -2634,6 +3400,14 @@ micromark-util-character@^1.0.0: micromark-util-symbol "^1.0.0" micromark-util-types "^1.0.0" +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-util-chunked@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-1.1.0.tgz" @@ -2641,6 +3415,13 @@ micromark-util-chunked@^1.0.0: dependencies: micromark-util-symbol "^1.0.0" +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-classify-character@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-1.1.0.tgz" @@ -2650,6 +3431,15 @@ micromark-util-classify-character@^1.0.0: micromark-util-symbol "^1.0.0" micromark-util-types "^1.0.0" +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-util-combine-extensions@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.1.0.tgz" @@ -2658,6 +3448,14 @@ micromark-util-combine-extensions@^1.0.0: micromark-util-chunked "^1.0.0" micromark-util-types "^1.0.0" +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + micromark-util-decode-numeric-character-reference@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.1.0.tgz" @@ -2665,6 +3463,13 @@ micromark-util-decode-numeric-character-reference@^1.0.0: dependencies: micromark-util-symbol "^1.0.0" +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.npmmirror.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-decode-string@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-1.1.0.tgz" @@ -2675,16 +3480,36 @@ micromark-util-decode-string@^1.0.0: micromark-util-decode-numeric-character-reference "^1.0.0" micromark-util-symbol "^1.0.0" +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-encode@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-1.1.0.tgz" integrity sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw== +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + micromark-util-html-tag-name@^1.0.0: version "1.2.0" resolved "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.2.0.tgz" integrity sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q== +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + micromark-util-normalize-identifier@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.1.0.tgz" @@ -2692,6 +3517,13 @@ micromark-util-normalize-identifier@^1.0.0: dependencies: micromark-util-symbol "^1.0.0" +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-resolve-all@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-1.1.0.tgz" @@ -2699,6 +3531,13 @@ micromark-util-resolve-all@^1.0.0: dependencies: micromark-util-types "^1.0.0" +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + micromark-util-sanitize-uri@^1.0.0, micromark-util-sanitize-uri@^1.1.0: version "1.2.0" resolved "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.2.0.tgz" @@ -2708,6 +3547,15 @@ micromark-util-sanitize-uri@^1.0.0, micromark-util-sanitize-uri@^1.1.0: micromark-util-encode "^1.0.0" micromark-util-symbol "^1.0.0" +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-subtokenize@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-1.1.0.tgz" @@ -2718,16 +3566,36 @@ micromark-util-subtokenize@^1.0.0: micromark-util-types "^1.0.0" uvu "^0.5.0" +micromark-util-subtokenize@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromark-util-symbol@^1.0.0: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-1.1.0.tgz" integrity sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag== +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + micromark-util-types@^1.0.0, micromark-util-types@^1.0.1: version "1.1.0" resolved "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-1.1.0.tgz" integrity sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg== +micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== + micromark@^3.0.0: version "3.2.0" resolved "https://registry.npmmirror.com/micromark/-/micromark-3.2.0.tgz" @@ -2751,6 +3619,29 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" uvu "^0.5.0" +micromark@^4.0.0: + version "4.0.2" + resolved "https://registry.npmmirror.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb" + integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + micromatch@^4.0.8: version "4.0.8" resolved "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz" @@ -2928,11 +3819,31 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.npmmirror.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== + dependencies: + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + parse5@^6.0.0: version "6.0.1" resolved "https://registry.npmmirror.com/parse5/-/parse5-6.0.1.tgz" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== +parse5@^7.0.0: + version "7.3.0" + resolved "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz#d7e224fa72399c7a175099f45fc2ad024b05ec05" + integrity sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw== + dependencies: + entities "^6.0.0" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/path-exists/-/path-exists-4.0.0.tgz" @@ -2963,7 +3874,7 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: resolved "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== -"picomatch@^3 || ^4", picomatch@^4.0.3: +picomatch@^4.0.3: version "4.0.3" resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.3.tgz" integrity sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q== @@ -3026,7 +3937,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.0.0, postcss@^8.1.0, postcss@^8.2.14, postcss@^8.4.21, postcss@^8.4.32, postcss@^8.4.43, postcss@^8.4.47, postcss@>=8.0.9: +postcss@^8.4.32, postcss@^8.4.43, postcss@^8.4.47: version "8.5.6" resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz" integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== @@ -3054,6 +3965,11 @@ property-information@^6.0.0: resolved "https://registry.npmmirror.com/property-information/-/property-information-6.5.0.tgz" integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig== +property-information@^7.0.0: + version "7.1.0" + resolved "https://registry.npmmirror.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d" + integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ== + proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz" @@ -3420,7 +4336,7 @@ rc-virtual-list@^3.14.2, rc-virtual-list@^3.5.1, rc-virtual-list@^3.5.2: rc-resize-observer "^1.0.0" rc-util "^5.36.0" -react-dom@*, react-dom@^18.2.0, react-dom@>=16.0.0, react-dom@>=16.11.0, react-dom@>=16.8, react-dom@>=16.9.0: +react-dom@^18.2.0: version "18.3.1" resolved "https://registry.npmmirror.com/react-dom/-/react-dom-18.3.1.tgz" integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== @@ -3438,6 +4354,23 @@ react-is@^18.2.0: resolved "https://registry.npmmirror.com/react-is/-/react-is-18.3.1.tgz" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== +react-markdown@^9.0.1: + version "9.1.0" + resolved "https://registry.npmmirror.com/react-markdown/-/react-markdown-9.1.0.tgz#606bd74c6af131ba382a7c1282ff506708ed2e26" + integrity sha512-xaijuJB0kzGiUdG7nc2MOMDUDBWPyGAjZtUrow9XxUeua8IqeP+VlIfAZ3bphpcLTnSZXz6z9jcVC/TCwbfgdw== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + react-refresh@^0.17.0: version "0.17.0" resolved "https://registry.npmmirror.com/react-refresh/-/react-refresh-0.17.0.tgz" @@ -3458,7 +4391,7 @@ react-router@6.30.2: dependencies: "@remix-run/router" "1.23.1" -react@*, "react@^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", react@^18.2.0, react@^18.3.1, react@>=16.0.0, react@>=16.11.0, react@>=16.8, react@>=16.9.0: +react@^18.2.0: version "18.3.1" resolved "https://registry.npmmirror.com/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -3505,6 +4438,17 @@ regexp.prototype.flags@^1.5.3, regexp.prototype.flags@^1.5.4: gopd "^1.2.0" set-function-name "^2.0.2" +rehype-highlight@^7.0.2: + version "7.0.2" + resolved "https://registry.npmmirror.com/rehype-highlight/-/rehype-highlight-7.0.2.tgz#997e05e3a336853f6f6b2cfc450c5dad0f960b07" + integrity sha512-k158pK7wdC2qL3M5NcZROZ2tR/l7zOzjxXd5VGdcfIyoijjQqpHd3JKtYSBDpDZ38UI2WJWuFAtkMDxmx5kstA== + dependencies: + "@types/hast" "^3.0.0" + hast-util-to-text "^4.0.0" + lowlight "^3.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + rehype-raw@^6.1.1: version "6.1.1" resolved "https://registry.npmmirror.com/rehype-raw/-/rehype-raw-6.1.1.tgz" @@ -3514,6 +4458,15 @@ rehype-raw@^6.1.1: hast-util-raw "^7.2.0" unified "^10.0.0" +rehype-raw@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4" + integrity sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww== + dependencies: + "@types/hast" "^3.0.0" + hast-util-raw "^9.0.0" + vfile "^6.0.0" + rehype-sanitize@^5.0.1: version "5.0.1" resolved "https://registry.npmmirror.com/rehype-sanitize/-/rehype-sanitize-5.0.1.tgz" @@ -3523,6 +4476,17 @@ rehype-sanitize@^5.0.1: hast-util-sanitize "^4.0.0" unified "^10.0.0" +rehype-slug@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/rehype-slug/-/rehype-slug-6.0.0.tgz#1d21cf7fc8a83ef874d873c15e6adaee6344eaf1" + integrity sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A== + dependencies: + "@types/hast" "^3.0.0" + github-slugger "^2.0.0" + hast-util-heading-rank "^3.0.0" + hast-util-to-string "^3.0.0" + unist-util-visit "^5.0.0" + rehype-stringify@^9.0.3: version "9.0.4" resolved "https://registry.npmmirror.com/rehype-stringify/-/rehype-stringify-9.0.4.tgz" @@ -3571,6 +4535,18 @@ remark-gfm@^3.0.1: micromark-extension-gfm "^2.0.0" unified "^10.0.0" +remark-gfm@^4.0.1: + version "4.0.1" + resolved "https://registry.npmmirror.com/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b" + integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-gfm "^3.0.0" + micromark-extension-gfm "^3.0.0" + remark-parse "^11.0.0" + remark-stringify "^11.0.0" + unified "^11.0.0" + remark-parse@^10.0.1: version "10.0.2" resolved "https://registry.npmmirror.com/remark-parse/-/remark-parse-10.0.2.tgz" @@ -3580,6 +4556,16 @@ remark-parse@^10.0.1: mdast-util-from-markdown "^1.0.0" unified "^10.0.0" +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.npmmirror.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + remark-rehype@^10.1.0: version "10.1.0" resolved "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-10.1.0.tgz" @@ -3590,6 +4576,26 @@ remark-rehype@^10.1.0: mdast-util-to-hast "^12.1.0" unified "^10.0.0" +remark-rehype@^11.0.0: + version "11.1.2" + resolved "https://registry.npmmirror.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37" + integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +remark-stringify@^11.0.0: + version "11.0.0" + resolved "https://registry.npmmirror.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3" + integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-to-markdown "^2.0.0" + unified "^11.0.0" + resize-observer-polyfill@^1.5.1: version "1.5.1" resolved "https://registry.npmmirror.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz" @@ -3912,6 +4918,20 @@ strip-json-comments@^3.1.1: resolved "https://registry.npmmirror.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +style-to-js@^1.0.0: + version "1.1.21" + resolved "https://registry.npmmirror.com/style-to-js/-/style-to-js-1.1.21.tgz#2908941187f857e79e28e9cd78008b9a0b3e0e8d" + integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ== + dependencies: + style-to-object "1.0.14" + +style-to-object@1.0.14: + version "1.0.14" + resolved "https://registry.npmmirror.com/style-to-object/-/style-to-object-1.0.14.tgz#1d22f0e7266bb8c6d8cae5caf4ec4f005e08f611" + integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw== + dependencies: + inline-style-parser "0.2.7" + stylis@^4.3.4: version "4.3.6" resolved "https://registry.npmmirror.com/stylis/-/stylis-4.3.6.tgz" @@ -4116,6 +5136,27 @@ unified@^10.0.0, unified@^10.1.2: trough "^2.0.0" vfile "^5.0.0" +unified@^11.0.0: + version "11.0.5" + resolved "https://registry.npmmirror.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + +unist-util-find-after@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz#3fccc1b086b56f34c8b798e1ff90b5c54468e896" + integrity sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-generated@^2.0.0: version "2.0.1" resolved "https://registry.npmmirror.com/unist-util-generated/-/unist-util-generated-2.0.1.tgz" @@ -4128,6 +5169,13 @@ unist-util-is@^5.0.0: dependencies: "@types/unist" "^2.0.0" +unist-util-is@^6.0.0: + version "6.0.1" + resolved "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9" + integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g== + dependencies: + "@types/unist" "^3.0.0" + unist-util-position@^4.0.0: version "4.0.4" resolved "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-4.0.4.tgz" @@ -4135,6 +5183,13 @@ unist-util-position@^4.0.0: dependencies: "@types/unist" "^2.0.0" +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position@^3.0.0: version "3.0.3" resolved "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-3.0.3.tgz" @@ -4142,6 +5197,13 @@ unist-util-stringify-position@^3.0.0: dependencies: "@types/unist" "^2.0.0" +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1: version "5.1.3" resolved "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-5.1.3.tgz" @@ -4150,6 +5212,14 @@ unist-util-visit-parents@^5.0.0, unist-util-visit-parents@^5.1.1: "@types/unist" "^2.0.0" unist-util-is "^5.0.0" +unist-util-visit-parents@^6.0.0: + version "6.0.2" + resolved "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02" + integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit@^4.0.0, unist-util-visit@^4.1.2: version "4.1.2" resolved "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-4.1.2.tgz" @@ -4159,6 +5229,15 @@ unist-util-visit@^4.0.0, unist-util-visit@^4.1.2: unist-util-is "^5.0.0" unist-util-visit-parents "^5.1.1" +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + update-browserslist-db@^1.2.0: version "1.2.3" resolved "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz" @@ -4202,6 +5281,14 @@ vfile-location@^4.0.0: "@types/unist" "^2.0.0" vfile "^5.0.0" +vfile-location@^5.0.0: + version "5.0.3" + resolved "https://registry.npmmirror.com/vfile-location/-/vfile-location-5.0.3.tgz#cb9eacd20f2b6426d19451e0eafa3d0a846225c3" + integrity sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg== + dependencies: + "@types/unist" "^3.0.0" + vfile "^6.0.0" + vfile-message@^3.0.0: version "3.1.4" resolved "https://registry.npmmirror.com/vfile-message/-/vfile-message-3.1.4.tgz" @@ -4210,6 +5297,14 @@ vfile-message@^3.0.0: "@types/unist" "^2.0.0" unist-util-stringify-position "^3.0.0" +vfile-message@^4.0.0: + version "4.0.3" + resolved "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + vfile@^5.0.0, vfile@^5.3.7: version "5.3.7" resolved "https://registry.npmmirror.com/vfile/-/vfile-5.3.7.tgz" @@ -4220,7 +5315,15 @@ vfile@^5.0.0, vfile@^5.3.7: unist-util-stringify-position "^3.0.0" vfile-message "^3.0.0" -"vite@^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", vite@^5.0.8: +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + +vite@^5.0.8: version "5.4.21" resolved "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz" integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==