diff --git a/.kilo/plans/1775304798427-lucky-panda.md b/.kilo/plans/1775304798427-lucky-panda.md new file mode 100644 index 0000000..a86e7fe --- /dev/null +++ b/.kilo/plans/1775304798427-lucky-panda.md @@ -0,0 +1,52 @@ +# 导出按钮缺失修复计划 + +## 问题分析 +当前 `action-buttons` 区域只有以下按钮可见: +- 上传文件 +- 导入 Markdown +- 导出 Markdown +- 上传图片 +- AI 切换按钮 + +**缺失功能**:DOCX 和 PDF 导出按钮 + +## 调查结果 +1. ✅ 翻译文件中已存在 `exportDocx` 和 `exportPdf` 键名(src/utils/i18n.js) +2. ❌ 模板中**完全缺失**这两个按钮的 HTML 代码 +3. ❓ 导出功能后端已实现,前端只需要添加调用接口的按钮 +4. ✅ 相关 CSS 样式已存在,按钮外观无需额外调整 + +## 实施计划 + +### 1. 添加 UI 按钮 +在 `src/components/MilkdownEditor.vue:79` 之后添加两个新按钮: +- DOCX 导出按钮 +- PDF 导出按钮 + +按钮位置: +``` +导出 Markdown → 导出 DOCX → 导出 PDF → 上传图片 +``` + +### 2. 实现前端导出功能 +使用已安装的依赖库: +- `docx` 库:用于 DOCX 导出 +- `html2pdf.js` 库:用于 PDF 导出 + +需要添加的函数: +```javascript +const exportDocx = async () => { + // 使用 docx 库实现导出 +} + +const exportPdf = async () => { + // 使用 html2pdf.js 实现导出 +} +``` + +### 3. 按钮图标 +- DOCX:使用文档图标 +- PDF:使用 PDF 专用图标 + +### 4. 状态管理 +添加加载状态和错误处理,与现有按钮保持一致风格 diff --git a/README.md b/README.md index 405a3f0..c4ed81c 100644 --- a/README.md +++ b/README.md @@ -1,264 +1,93 @@ -# LLM in Text - 智能写作助手 +# LLM in Text - 智能写作助手 -基于 Vue3 和 FastAPI 的智能 Markdown 编辑器,集成大语言模型(LLM)实时补全建议功能,提供类似 GitHub Copilot 的 Ghost Text 体验。 +基于 Vue3 和 FastAPI 的智能 Markdown 编辑器,集成大语言模型(LLM)实时补全建议功能。 ## 功能特性 ### Markdown 编辑器 - 基于 Milkdown Crepe 的所见即所得编辑体验 -- 支持完整 Markdown 语法和 LaTeX 公式 +- 支持 Markdown 语法和 LaTeX 公式 +- 支持 Mermaid 图表渲染 - 导入/导出 Markdown 文件 +- 导出 DOCX 和 PDF 格式 ### AI 智能补全 - 实时生成文本补全建议(灰色显示) - 流式响应,低延迟体验 -- 多种交互方式: - - **Tab 键**:接受建议 - - **Esc 键**:拒绝建议 - - **点击灰色文本**:接受建议 - - **继续输入**:自动拒绝建议 +- 多种交互方式:Tab接受、Esc拒绝、点击接受 -### AI 开关控制 -- 右下角 AI 开关按钮 -- 白色 = AI 启用,黑色 = AI 禁用 -- 禁用时自动清除灰色文本并停止 API 调用 +### 文档处理 +- OCR 图片识别:上传图片自动识别文字 +- 文档转换:PDF、DOCX、PPTX、TXT 转 Markdown +- 文档块嵌入:可折叠的文档预览块 +- 智能大小限制:32KB自动禁用AI + +### 设置面板 +- 外观主题:亮色/暗色/跟随系统 +- 背景模式:默认/暖色/阅读灯/自定义图片 +- 模型智能:低/中/高思考级别 +- 隐私控制:隐私模式防止发送IP +- 多语言界面:中英日韩德法 + +### 语音功能 +- TTS文字转语音(macOS) +- STT语音转文字 ## 技术架构 -```mermaid -flowchart TB - subgraph Frontend["前端 (Vue3 + Vite)"] - A[App.vue] --> B[MilkdownEditor.vue] - B --> C[Crepe Editor] - C --> D[ProseMirror] - D --> E[copilotPlugin.ts] - E --> F[copilotGhostMark] - E --> G[api.js] - end - - subgraph Backend["后端 (FastAPI + Python)"] - H[main.py
FastAPI Server] --> I[prompt.py
Prompt 构建] - H --> J[llm.py
Ollama 调用] - J --> K[Ollama API] - end - - G -->|POST /v1/completions
SSE 流式响应| H - K -->|LLM 响应| J -``` - -## 项目结构 - -``` -llm-in-text/ -├── src/ -│ ├── components/ -│ │ └── MilkdownEditor.vue # 主编辑器组件 -│ ├── plugins/ -│ │ ├── copilotPlugin.ts # ProseMirror AI 补全插件 -│ │ ├── types.ts # 类型定义 -│ │ └── index.ts # 插件导出 -│ ├── utils/ -│ │ ├── api.js # API 调用封装 -│ │ ├── config.js # 配置文件 -│ │ └── ocrCache.js # OCR 缓存管理 -│ ├── App.vue -│ └── main.js -├── backend/ -│ ├── main.py # FastAPI 服务器 -│ ├── llm.py # LLM API 调用 -│ ├── prompt.py # Prompt 构建 -│ └── requirements.txt -└── README.md -``` +前端: Vue3 + Vite + Milkdown + ProseMirror +后端: FastAPI + Python + Ollama ## 快速开始 -### 环境要求 -- Node.js 18+ -- Python 3.8+ -- Ollama 服务(或其他兼容 OpenAI API 的服务) +环境: Node.js 18+、Python 3.8+、Ollama -### 安装 +安装: +- 前端: npm install +- 后端: pip install -r backend/requirements.txt -```bash -# 前端 -npm install +启动: +- 后端: python backend/main.py (端口8001) +- 前端: npm run dev (端口5173) -# 后端 -cd backend -pip install -r requirements.txt -``` +## API接口 -### 配置 - -在 `backend/.env` 中配置: - -```env -OLLAMA_MODEL=gpt-oss:20b -OLLAMA_HOST=http://localhost:11434 -``` - -### 启动 - -```bash -# 后端(端口 8000) -cd backend -python main.py - -# 前端(端口 5173) -npm run dev -``` - -访问 http://localhost:5173 - -## API 接口 - -### POST /v1/completions - -流式获取补全建议 - -**请求:** -```json -{ - "prefix": "# Title\n\nContent ", - "suffix": "", - "languageId": "markdown" -} -``` - -**响应(SSE):** -``` -data: {"content": "here"} -data: {"content": "here is"} -data: {"done": true} -``` +- POST /v1/completions 流式补全建议 +- POST /v1/ocr 图片文字识别 +- POST /v1/convert 文档转换 +- POST /v1/completions/cancel 取消请求 ## 核心实现 -### 后端设计 +### 后端 +- main.py: FastAPI服务器、SSE流式响应 +- llm.py: 异步Ollama调用、超时控制 +- prompt.py: 7条Prompt规则 +- tts_asr.py: macOS 语音处理 -#### main.py - FastAPI 服务器 -- 定义 `/v1/completions` 端点 -- 使用 `StreamingResponse` 返回 SSE 流式响应 -- CORS 配置允许跨域请求 - -#### llm.py - LLM 调用封装 -- 使用 `ollama.AsyncClient` 异步调用 -- 支持 `think='high'` 思考模式 -- 返回 `content` 和 `thinking` 字段 - -#### prompt.py - Prompt 工程 -精心设计的 Prompt 模板,包含 7 条核心规则: - -| 规则 | 说明 | -|------|------| -| RULE #1 | 无缝连接 - 不重复 suffix 内容,避免"复读机"错误 | -| RULE #2 | 空白处理 - 避免双空格,正确对接标点 | -| RULE #3 | 缩进对齐 - 匹配当前缩进级别和类型 | -| RULE #4 | 列表维护 - 识别并继续任务列表、有序列表、无序列表 | -| RULE #5 | 语法闭合 - 自动闭合未完成的 Markdown 语法 | -| RULE #6 | 输出格式 - 仅输出续写文本,无解释无注释 | -| RULE #7 | 必须输出 - 始终提供有用的续写建议 | - -### 前端设计 - -#### ProseMirror Mark 系统 - -使用 ProseMirror 的 Mark 系统实现灰色建议文本: - -```typescript -// 定义 ghost mark -export const copilotGhostMark = $markSchema('copilot_ghost', () => ({ - excludes: '_', - inclusive: true, - toDOM: () => ['span', { - 'data-copilot-ghost': '', - class: 'copilot-ghost-text' - }, 0] -})) - -// CSS 样式 -.copilot-ghost-text { - color: #999; - opacity: 0.6; -} -``` - -#### copilotPlugin 核心逻辑 - -```mermaid -flowchart LR - A[用户输入] --> B{文档变化?} - B -->|是| C[清除旧建议] - C --> D[防抖 1000ms] - D --> E[发送 API 请求] - E --> F[收到建议] - F --> G[插入 Ghost Text] - - G --> H{用户操作} - H -->|Tab| I[接受建议
移除 mark] - H -->|Esc| J[拒绝建议
删除文本] - H -->|点击 Ghost| I - H -->|继续输入| J -``` - -#### 关键函数 - -| 函数 | 作用 | -|------|------| -| `scheduleFetch` | 防抖调度 API 请求 | -| `insertGhostText` | 插入带 mark 的建议文本 | -| `acceptSuggestion` | Tab 接受建议 | -| `rejectSuggestion` | Esc 拒绝建议 | -| `clearGhostText` | 清除当前建议 | - -### 数据流 - -```mermaid -sequenceDiagram - participant U as 用户 - participant E as Editor (ProseMirror) - participant P as copilotPlugin - participant A as api.js - participant B as Backend - participant L as LLM - - U->>E: 输入文本 - E->>P: view.update() - P->>P: 清除旧建议 - P->>P: 防抖 1000ms - P->>A: fetchSuggestion(prefix, suffix) - A->>B: POST /v1/completions - B->>B: build_prompt() - B->>L: ollama.chat() - L-->>B: {content, thinking} - B-->>A: SSE stream - A-->>P: suggestion text - P->>E: insertGhostText() - E-->>U: 显示灰色建议 - - alt Tab 键 - U->>P: Tab - P->>E: acceptSuggestion() - E-->>U: 建议变为正常文本 - else Esc 键 - U->>P: Esc - P->>E: rejectSuggestion() - E-->>U: 建议消失 - else 继续输入 - U->>E: 输入其他字符 - E->>P: handleKeyDown() - P->>E: clearGhostText() - end -``` +### 前端 +- copilotPlugin.ts: ProseMirror Mark系统 +- 关键函数: scheduleFetch、insertGhostText +- Pinia Store状态管理 ## 设计亮点 -1. **前后端分离**:前端只负责渲染和数据回传,后端负责 LLM 调用、Prompt 构建和数据解析 -2. **低延迟优化**:防抖机制 (1000ms) + SSE 流式响应 + AbortController 取消过期请求 -3. **ProseMirror Mark 系统**:与编辑器状态完美集成,支持 Undo/Redo -4. **多种交互方式**:Tab/Esc/点击/输入,用户体验友好 -5. **智能大小限制**:文档超过 32KB 自动禁用 AI 功能 +1. 前后端分离 +2. 低延迟优化:防抖+SSE+AbortController +3. ProseMirror Mark系统 +4. 多种交互方式 +5. 智能大小限制 +6. 隐私保护 +7. 多语言支持 +8. 主题定制 +9. 文档处理 +10. 语音功能 + +## 开发指南 + +代码风格: Python(4空格,snake_case) JS/TS(2空格,camelCase) +测试: pytest +构建: npm run build ## 许可证 diff --git a/README.md.fixed b/README.md.fixed deleted file mode 100644 index 20c3741..0000000 Binary files a/README.md.fixed and /dev/null differ diff --git a/README.md.original b/README.md.original deleted file mode 100644 index 20c3741..0000000 Binary files a/README.md.original and /dev/null differ diff --git a/README.md.tmp b/README.md.tmp deleted file mode 100644 index 405a3f0..0000000 --- a/README.md.tmp +++ /dev/null @@ -1,265 +0,0 @@ -# LLM in Text - 智能写作助手 - -基于 Vue3 和 FastAPI 的智能 Markdown 编辑器,集成大语言模型(LLM)实时补全建议功能,提供类似 GitHub Copilot 的 Ghost Text 体验。 - -## 功能特性 - -### Markdown 编辑器 -- 基于 Milkdown Crepe 的所见即所得编辑体验 -- 支持完整 Markdown 语法和 LaTeX 公式 -- 导入/导出 Markdown 文件 - -### AI 智能补全 -- 实时生成文本补全建议(灰色显示) -- 流式响应,低延迟体验 -- 多种交互方式: - - **Tab 键**:接受建议 - - **Esc 键**:拒绝建议 - - **点击灰色文本**:接受建议 - - **继续输入**:自动拒绝建议 - -### AI 开关控制 -- 右下角 AI 开关按钮 -- 白色 = AI 启用,黑色 = AI 禁用 -- 禁用时自动清除灰色文本并停止 API 调用 - -## 技术架构 - -```mermaid -flowchart TB - subgraph Frontend["前端 (Vue3 + Vite)"] - A[App.vue] --> B[MilkdownEditor.vue] - B --> C[Crepe Editor] - C --> D[ProseMirror] - D --> E[copilotPlugin.ts] - E --> F[copilotGhostMark] - E --> G[api.js] - end - - subgraph Backend["后端 (FastAPI + Python)"] - H[main.py
FastAPI Server] --> I[prompt.py
Prompt 构建] - H --> J[llm.py
Ollama 调用] - J --> K[Ollama API] - end - - G -->|POST /v1/completions
SSE 流式响应| H - K -->|LLM 响应| J -``` - -## 项目结构 - -``` -llm-in-text/ -├── src/ -│ ├── components/ -│ │ └── MilkdownEditor.vue # 主编辑器组件 -│ ├── plugins/ -│ │ ├── copilotPlugin.ts # ProseMirror AI 补全插件 -│ │ ├── types.ts # 类型定义 -│ │ └── index.ts # 插件导出 -│ ├── utils/ -│ │ ├── api.js # API 调用封装 -│ │ ├── config.js # 配置文件 -│ │ └── ocrCache.js # OCR 缓存管理 -│ ├── App.vue -│ └── main.js -├── backend/ -│ ├── main.py # FastAPI 服务器 -│ ├── llm.py # LLM API 调用 -│ ├── prompt.py # Prompt 构建 -│ └── requirements.txt -└── README.md -``` - -## 快速开始 - -### 环境要求 -- Node.js 18+ -- Python 3.8+ -- Ollama 服务(或其他兼容 OpenAI API 的服务) - -### 安装 - -```bash -# 前端 -npm install - -# 后端 -cd backend -pip install -r requirements.txt -``` - -### 配置 - -在 `backend/.env` 中配置: - -```env -OLLAMA_MODEL=gpt-oss:20b -OLLAMA_HOST=http://localhost:11434 -``` - -### 启动 - -```bash -# 后端(端口 8000) -cd backend -python main.py - -# 前端(端口 5173) -npm run dev -``` - -访问 http://localhost:5173 - -## API 接口 - -### POST /v1/completions - -流式获取补全建议 - -**请求:** -```json -{ - "prefix": "# Title\n\nContent ", - "suffix": "", - "languageId": "markdown" -} -``` - -**响应(SSE):** -``` -data: {"content": "here"} -data: {"content": "here is"} -data: {"done": true} -``` - -## 核心实现 - -### 后端设计 - -#### main.py - FastAPI 服务器 -- 定义 `/v1/completions` 端点 -- 使用 `StreamingResponse` 返回 SSE 流式响应 -- CORS 配置允许跨域请求 - -#### llm.py - LLM 调用封装 -- 使用 `ollama.AsyncClient` 异步调用 -- 支持 `think='high'` 思考模式 -- 返回 `content` 和 `thinking` 字段 - -#### prompt.py - Prompt 工程 -精心设计的 Prompt 模板,包含 7 条核心规则: - -| 规则 | 说明 | -|------|------| -| RULE #1 | 无缝连接 - 不重复 suffix 内容,避免"复读机"错误 | -| RULE #2 | 空白处理 - 避免双空格,正确对接标点 | -| RULE #3 | 缩进对齐 - 匹配当前缩进级别和类型 | -| RULE #4 | 列表维护 - 识别并继续任务列表、有序列表、无序列表 | -| RULE #5 | 语法闭合 - 自动闭合未完成的 Markdown 语法 | -| RULE #6 | 输出格式 - 仅输出续写文本,无解释无注释 | -| RULE #7 | 必须输出 - 始终提供有用的续写建议 | - -### 前端设计 - -#### ProseMirror Mark 系统 - -使用 ProseMirror 的 Mark 系统实现灰色建议文本: - -```typescript -// 定义 ghost mark -export const copilotGhostMark = $markSchema('copilot_ghost', () => ({ - excludes: '_', - inclusive: true, - toDOM: () => ['span', { - 'data-copilot-ghost': '', - class: 'copilot-ghost-text' - }, 0] -})) - -// CSS 样式 -.copilot-ghost-text { - color: #999; - opacity: 0.6; -} -``` - -#### copilotPlugin 核心逻辑 - -```mermaid -flowchart LR - A[用户输入] --> B{文档变化?} - B -->|是| C[清除旧建议] - C --> D[防抖 1000ms] - D --> E[发送 API 请求] - E --> F[收到建议] - F --> G[插入 Ghost Text] - - G --> H{用户操作} - H -->|Tab| I[接受建议
移除 mark] - H -->|Esc| J[拒绝建议
删除文本] - H -->|点击 Ghost| I - H -->|继续输入| J -``` - -#### 关键函数 - -| 函数 | 作用 | -|------|------| -| `scheduleFetch` | 防抖调度 API 请求 | -| `insertGhostText` | 插入带 mark 的建议文本 | -| `acceptSuggestion` | Tab 接受建议 | -| `rejectSuggestion` | Esc 拒绝建议 | -| `clearGhostText` | 清除当前建议 | - -### 数据流 - -```mermaid -sequenceDiagram - participant U as 用户 - participant E as Editor (ProseMirror) - participant P as copilotPlugin - participant A as api.js - participant B as Backend - participant L as LLM - - U->>E: 输入文本 - E->>P: view.update() - P->>P: 清除旧建议 - P->>P: 防抖 1000ms - P->>A: fetchSuggestion(prefix, suffix) - A->>B: POST /v1/completions - B->>B: build_prompt() - B->>L: ollama.chat() - L-->>B: {content, thinking} - B-->>A: SSE stream - A-->>P: suggestion text - P->>E: insertGhostText() - E-->>U: 显示灰色建议 - - alt Tab 键 - U->>P: Tab - P->>E: acceptSuggestion() - E-->>U: 建议变为正常文本 - else Esc 键 - U->>P: Esc - P->>E: rejectSuggestion() - E-->>U: 建议消失 - else 继续输入 - U->>E: 输入其他字符 - E->>P: handleKeyDown() - P->>E: clearGhostText() - end -``` - -## 设计亮点 - -1. **前后端分离**:前端只负责渲染和数据回传,后端负责 LLM 调用、Prompt 构建和数据解析 -2. **低延迟优化**:防抖机制 (1000ms) + SSE 流式响应 + AbortController 取消过期请求 -3. **ProseMirror Mark 系统**:与编辑器状态完美集成,支持 Undo/Redo -4. **多种交互方式**:Tab/Esc/点击/输入,用户体验友好 -5. **智能大小限制**:文档超过 32KB 自动禁用 AI 功能 - -## 许可证 - -MIT License diff --git a/README_clean.md b/README_clean.md deleted file mode 100644 index 20c3741..0000000 Binary files a/README_clean.md and /dev/null differ diff --git a/README_correct.md b/README_correct.md deleted file mode 100644 index 20c3741..0000000 Binary files a/README_correct.md and /dev/null differ diff --git a/README_fixed.md b/README_fixed.md deleted file mode 100644 index 20c3741..0000000 Binary files a/README_fixed.md and /dev/null differ diff --git a/README_original.txt b/README_original.txt deleted file mode 100644 index 405a3f0..0000000 --- a/README_original.txt +++ /dev/null @@ -1,265 +0,0 @@ -# LLM in Text - 智能写作助手 - -基于 Vue3 和 FastAPI 的智能 Markdown 编辑器,集成大语言模型(LLM)实时补全建议功能,提供类似 GitHub Copilot 的 Ghost Text 体验。 - -## 功能特性 - -### Markdown 编辑器 -- 基于 Milkdown Crepe 的所见即所得编辑体验 -- 支持完整 Markdown 语法和 LaTeX 公式 -- 导入/导出 Markdown 文件 - -### AI 智能补全 -- 实时生成文本补全建议(灰色显示) -- 流式响应,低延迟体验 -- 多种交互方式: - - **Tab 键**:接受建议 - - **Esc 键**:拒绝建议 - - **点击灰色文本**:接受建议 - - **继续输入**:自动拒绝建议 - -### AI 开关控制 -- 右下角 AI 开关按钮 -- 白色 = AI 启用,黑色 = AI 禁用 -- 禁用时自动清除灰色文本并停止 API 调用 - -## 技术架构 - -```mermaid -flowchart TB - subgraph Frontend["前端 (Vue3 + Vite)"] - A[App.vue] --> B[MilkdownEditor.vue] - B --> C[Crepe Editor] - C --> D[ProseMirror] - D --> E[copilotPlugin.ts] - E --> F[copilotGhostMark] - E --> G[api.js] - end - - subgraph Backend["后端 (FastAPI + Python)"] - H[main.py
FastAPI Server] --> I[prompt.py
Prompt 构建] - H --> J[llm.py
Ollama 调用] - J --> K[Ollama API] - end - - G -->|POST /v1/completions
SSE 流式响应| H - K -->|LLM 响应| J -``` - -## 项目结构 - -``` -llm-in-text/ -├── src/ -│ ├── components/ -│ │ └── MilkdownEditor.vue # 主编辑器组件 -│ ├── plugins/ -│ │ ├── copilotPlugin.ts # ProseMirror AI 补全插件 -│ │ ├── types.ts # 类型定义 -│ │ └── index.ts # 插件导出 -│ ├── utils/ -│ │ ├── api.js # API 调用封装 -│ │ ├── config.js # 配置文件 -│ │ └── ocrCache.js # OCR 缓存管理 -│ ├── App.vue -│ └── main.js -├── backend/ -│ ├── main.py # FastAPI 服务器 -│ ├── llm.py # LLM API 调用 -│ ├── prompt.py # Prompt 构建 -│ └── requirements.txt -└── README.md -``` - -## 快速开始 - -### 环境要求 -- Node.js 18+ -- Python 3.8+ -- Ollama 服务(或其他兼容 OpenAI API 的服务) - -### 安装 - -```bash -# 前端 -npm install - -# 后端 -cd backend -pip install -r requirements.txt -``` - -### 配置 - -在 `backend/.env` 中配置: - -```env -OLLAMA_MODEL=gpt-oss:20b -OLLAMA_HOST=http://localhost:11434 -``` - -### 启动 - -```bash -# 后端(端口 8000) -cd backend -python main.py - -# 前端(端口 5173) -npm run dev -``` - -访问 http://localhost:5173 - -## API 接口 - -### POST /v1/completions - -流式获取补全建议 - -**请求:** -```json -{ - "prefix": "# Title\n\nContent ", - "suffix": "", - "languageId": "markdown" -} -``` - -**响应(SSE):** -``` -data: {"content": "here"} -data: {"content": "here is"} -data: {"done": true} -``` - -## 核心实现 - -### 后端设计 - -#### main.py - FastAPI 服务器 -- 定义 `/v1/completions` 端点 -- 使用 `StreamingResponse` 返回 SSE 流式响应 -- CORS 配置允许跨域请求 - -#### llm.py - LLM 调用封装 -- 使用 `ollama.AsyncClient` 异步调用 -- 支持 `think='high'` 思考模式 -- 返回 `content` 和 `thinking` 字段 - -#### prompt.py - Prompt 工程 -精心设计的 Prompt 模板,包含 7 条核心规则: - -| 规则 | 说明 | -|------|------| -| RULE #1 | 无缝连接 - 不重复 suffix 内容,避免"复读机"错误 | -| RULE #2 | 空白处理 - 避免双空格,正确对接标点 | -| RULE #3 | 缩进对齐 - 匹配当前缩进级别和类型 | -| RULE #4 | 列表维护 - 识别并继续任务列表、有序列表、无序列表 | -| RULE #5 | 语法闭合 - 自动闭合未完成的 Markdown 语法 | -| RULE #6 | 输出格式 - 仅输出续写文本,无解释无注释 | -| RULE #7 | 必须输出 - 始终提供有用的续写建议 | - -### 前端设计 - -#### ProseMirror Mark 系统 - -使用 ProseMirror 的 Mark 系统实现灰色建议文本: - -```typescript -// 定义 ghost mark -export const copilotGhostMark = $markSchema('copilot_ghost', () => ({ - excludes: '_', - inclusive: true, - toDOM: () => ['span', { - 'data-copilot-ghost': '', - class: 'copilot-ghost-text' - }, 0] -})) - -// CSS 样式 -.copilot-ghost-text { - color: #999; - opacity: 0.6; -} -``` - -#### copilotPlugin 核心逻辑 - -```mermaid -flowchart LR - A[用户输入] --> B{文档变化?} - B -->|是| C[清除旧建议] - C --> D[防抖 1000ms] - D --> E[发送 API 请求] - E --> F[收到建议] - F --> G[插入 Ghost Text] - - G --> H{用户操作} - H -->|Tab| I[接受建议
移除 mark] - H -->|Esc| J[拒绝建议
删除文本] - H -->|点击 Ghost| I - H -->|继续输入| J -``` - -#### 关键函数 - -| 函数 | 作用 | -|------|------| -| `scheduleFetch` | 防抖调度 API 请求 | -| `insertGhostText` | 插入带 mark 的建议文本 | -| `acceptSuggestion` | Tab 接受建议 | -| `rejectSuggestion` | Esc 拒绝建议 | -| `clearGhostText` | 清除当前建议 | - -### 数据流 - -```mermaid -sequenceDiagram - participant U as 用户 - participant E as Editor (ProseMirror) - participant P as copilotPlugin - participant A as api.js - participant B as Backend - participant L as LLM - - U->>E: 输入文本 - E->>P: view.update() - P->>P: 清除旧建议 - P->>P: 防抖 1000ms - P->>A: fetchSuggestion(prefix, suffix) - A->>B: POST /v1/completions - B->>B: build_prompt() - B->>L: ollama.chat() - L-->>B: {content, thinking} - B-->>A: SSE stream - A-->>P: suggestion text - P->>E: insertGhostText() - E-->>U: 显示灰色建议 - - alt Tab 键 - U->>P: Tab - P->>E: acceptSuggestion() - E-->>U: 建议变为正常文本 - else Esc 键 - U->>P: Esc - P->>E: rejectSuggestion() - E-->>U: 建议消失 - else 继续输入 - U->>E: 输入其他字符 - E->>P: handleKeyDown() - P->>E: clearGhostText() - end -``` - -## 设计亮点 - -1. **前后端分离**:前端只负责渲染和数据回传,后端负责 LLM 调用、Prompt 构建和数据解析 -2. **低延迟优化**:防抖机制 (1000ms) + SSE 流式响应 + AbortController 取消过期请求 -3. **ProseMirror Mark 系统**:与编辑器状态完美集成,支持 Undo/Redo -4. **多种交互方式**:Tab/Esc/点击/输入,用户体验友好 -5. **智能大小限制**:文档超过 32KB 自动禁用 AI 功能 - -## 许可证 - -MIT License diff --git a/backend/docx2pdf_bridge.cjs b/backend/docx2pdf_bridge.cjs new file mode 100644 index 0000000..063c6b1 --- /dev/null +++ b/backend/docx2pdf_bridge.cjs @@ -0,0 +1,20 @@ +const path = require('path') +const { convert } = require('docx2pdf-converter') + +function main() { + const inputPath = process.argv[2] + const outputPath = process.argv[3] + + if (!inputPath || !outputPath) { + throw new Error('缺少 DOCX 或 PDF 路径') + } + + convert(path.resolve(inputPath), path.resolve(outputPath)) +} + +try { + main() +} catch (error) { + console.error(error instanceof Error ? error.message : String(error)) + process.exit(1) +} diff --git a/backend/main.py b/backend/main.py index 14e68ac..f3bded9 100644 --- a/backend/main.py +++ b/backend/main.py @@ -3,13 +3,16 @@ import base64 import json import logging import os +import re +import shutil +import subprocess import tempfile import uuid from typing import Optional -from fastapi import FastAPI, HTTPException, Request, Security +from fastapi import FastAPI, HTTPException, Request, Security, File, UploadFile from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse, StreamingResponse +from fastapi.responses import JSONResponse, StreamingResponse, Response from fastapi.security import APIKeyHeader from pydantic import BaseModel @@ -81,6 +84,32 @@ class ConvertRequest(BaseModel): filename: str = "document.pdf" +ALLOWED_CONVERT_EXTENSIONS = {".txt", ".docx", ".pptx", ".pdf"} +IMAGE_MARKDOWN_RE = re.compile(r"!\[[^\]]*]\([^)]+\)") +IMAGE_HTML_RE = re.compile(r"]*>", re.IGNORECASE) + + +def _convert_docx_to_pdf(input_path: str, output_path: str) -> None: + node_executable = shutil.which("node") + if not node_executable: + raise RuntimeError("未找到 Node.js,无法转换 DOCX 为 PDF") + + bridge_path = os.path.join(os.path.dirname(__file__), "docx2pdf_bridge.cjs") + if not os.path.exists(bridge_path): + raise RuntimeError("缺少 DOCX 转 PDF 桥接脚本") + + result = subprocess.run( + [node_executable, bridge_path, input_path, output_path], + cwd=os.path.dirname(os.path.dirname(__file__)), + capture_output=True, + text=True, + ) + + if result.returncode != 0: + error_text = (result.stderr or result.stdout or "DOCX 转 PDF 失败").strip() + raise RuntimeError(error_text) + + def _preview(text: str, limit: int = 80) -> str: value = (text or "").replace("\n", "\\n") if len(value) <= limit: @@ -88,6 +117,14 @@ def _preview(text: str, limit: int = 80) -> str: return value[:limit] + "..." +def _sanitize_converted_markdown(text: str) -> str: + value = (text or "").replace("\r\n", "\n").replace("\r", "\n") + value = IMAGE_MARKDOWN_RE.sub("", value) + value = IMAGE_HTML_RE.sub("", value) + value = re.sub(r"\n{3,}", "\n\n", value) + return value.strip() + + def _sse_payload(payload: dict) -> str: return f"data: {json.dumps(payload)}\n\n" @@ -253,9 +290,9 @@ async def ocr_image(request: OCRRequest, api_key: str = Security(get_api_key)): @app.post("/v1/convert") async def convert_to_markdown(request: ConvertRequest, api_key: str = Security(get_api_key)): - """鐏忓棙鏋冩禒鎯版祮閹诡澀璐烳arkdown閺嶇厧绱?"" + """Convert file to markdown""" request_id = str(uuid.uuid4())[:8] - + try: logger.info( "[%s] /v1/convert filename=%s file_base64_chars=%d", @@ -263,53 +300,106 @@ async def convert_to_markdown(request: ConvertRequest, api_key: str = Security(g request.filename, len(request.file or ""), ) - - # 鐟欙絿鐖淏ase64閺傚洣娆㈤崘鍛啇 + + # Decode base64 file_bytes = base64.b64decode(request.file) logger.info("[%s] /v1/convert decoded file_bytes=%d", request_id, len(file_bytes)) - - # 閼惧嘲褰囬弬鍥︽閹碘晛鐫嶉崥? + + # Get file extension ext = os.path.splitext(request.filename)[1].lower() - - # 閸掓稑缂撴稉瀛樻閺傚洣娆? + + if ext not in ALLOWED_CONVERT_EXTENSIONS: + raise ValueError("仅支持 txt、docx、pptx、pdf 格式") + + if ext == ".txt": + markdown_text = _sanitize_converted_markdown(file_bytes.decode("utf-8", errors="ignore")) + return { + "markdown": markdown_text, + "filename": request.filename + } + + # Create temporary file with tempfile.NamedTemporaryFile(delete=False, suffix=ext) as tmp: tmp.write(file_bytes) tmp_path = tmp.name - + try: - # 娴h法鏁arkItDown鏉烆剚宕叉稉绡梐rkdown + # Convert using MarkItDown md = markitdown.MarkItDown() result = md.convert(tmp_path) - markdown_text = result.text_content - + markdown_text = _sanitize_converted_markdown(result.text_content) + logger.info( "[%s] /v1/convert success text_chars=%d text_preview='%s'", request_id, len(markdown_text or ""), _preview(markdown_text, 120), ) - + return { "markdown": markdown_text, "filename": request.filename } finally: - # 濞撳懐鎮婃稉瀛樻閺傚洣娆? + # Clean up temporary file if os.path.exists(tmp_path): os.unlink(tmp_path) - + except Exception as e: logger.exception("[%s] /v1/convert failed: %s", request_id, e) return JSONResponse(content={"error": str(e)}, status_code=500) + + + +@app.post("/v1/export/pdf") +async def export_pdf(file: UploadFile = File(...), api_key: str = Security(get_api_key)): + request_id = str(uuid.uuid4())[:8] + original_name = file.filename or "document.docx" + base_name = os.path.splitext(original_name)[0] or "document" + + try: + file_bytes = await file.read() + logger.info( + "[%s] /v1/export/pdf filename=%s file_bytes=%d", + request_id, + original_name, + len(file_bytes), + ) + + with tempfile.TemporaryDirectory() as temp_dir: + input_path = os.path.join(temp_dir, f"{base_name}.docx") + output_path = os.path.join(temp_dir, f"{base_name}.pdf") + + with open(input_path, "wb") as tmp_file: + tmp_file.write(file_bytes) + + await asyncio.to_thread(_convert_docx_to_pdf, input_path, output_path) + + if not os.path.exists(output_path): + raise RuntimeError("PDF 转换后未生成输出文件") + + with open(output_path, "rb") as pdf_file: + pdf_bytes = pdf_file.read() + + logger.info("[%s] /v1/export/pdf success pdf_bytes=%d", request_id, len(pdf_bytes)) + headers = { + "Content-Disposition": f'attachment; filename="{base_name}.pdf"', + } + return Response(content=pdf_bytes, media_type="application/pdf", headers=headers) + except Exception as e: + logger.exception("[%s] /v1/export/pdf failed: %s", request_id, e) + return JSONResponse(content={"error": str(e)}, status_code=500) + + if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8001) -# TTS and STT routes +# TTS and ASR routes from tts_asr import register_tts_asr_routes register_tts_asr_routes(app) diff --git a/backend/tts_asr.py b/backend/tts_asr.py new file mode 100644 index 0000000..0acc06f --- /dev/null +++ b/backend/tts_asr.py @@ -0,0 +1,255 @@ +# TTS and ASR API for macOS Silicon with HuggingFace transformers +import asyncio +import base64 +import logging +import os +import platform + +from fastapi import APIRouter, HTTPException, Security +from pydantic import BaseModel +import numpy as np + +router = APIRouter() +logger = logging.getLogger("tts_asr") + +_tts_pipeline = None +_asr_pipeline = None +_device = None + + +def _get_device(): + global _device + if _device is not None: + return _device + + import torch + + if platform.system() == "Darwin" and hasattr(torch.backends, "mps") and torch.backends.mps.is_available(): + _device = "mps" + logger.info("[Device] 使用 MPS 加速") + elif torch.cuda.is_available(): + _device = "cuda" + logger.info("[Device] 使用 CUDA 加速") + else: + _device = "cpu" + logger.info("[Device] 使用 CPU") + return _device + + +def _device_arg(): + device = _get_device() + if device == "cuda": + return "cuda:0" + return device + + +def _get_tts_pipeline(): + global _tts_pipeline + if _tts_pipeline is not None: + return _tts_pipeline + + import torch + from transformers import pipeline + + logger.info("[TTS] 加载 Kokoro-82M 模型...") + _tts_pipeline = pipeline( + "text-to-speech", + model="hexgrad/Kokoro-82M", + trust_remote_code=True, + device=_device_arg(), + torch_dtype=torch.float16 if _get_device() != "cpu" else torch.float32, + ) + logger.info("[TTS] Kokoro-82M 模型加载完成") + return _tts_pipeline + + +def _get_asr_pipeline(): + global _asr_pipeline + if _asr_pipeline is not None: + return _asr_pipeline + + import torch + from transformers import AutoModelForSpeechSeq2Seq, AutoProcessor, pipeline + + logger.info("[ASR] 加载 Whisper large-v3-turbo 模型...") + model_id = "openai/whisper-large-v3-turbo" + model = AutoModelForSpeechSeq2Seq.from_pretrained( + model_id, + torch_dtype=torch.float16 if _get_device() != "cpu" else torch.float32, + low_cpu_mem_usage=True, + use_safetensors=True, + ) + processor = AutoProcessor.from_pretrained(model_id) + _asr_pipeline = pipeline( + "automatic-speech-recognition", + model=model, + tokenizer=processor.tokenizer, + feature_extractor=processor.feature_extractor, + torch_dtype=torch.float16 if _get_device() != "cpu" else torch.float32, + device=_device_arg(), + ) + logger.info("[ASR] Whisper large-v3-turbo 模型加载完成") + return _asr_pipeline + + +def _save_audio_to_wav(audio_data: bytes, sample_rate: int = 16000) -> str: + import tempfile + import wave + + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False, mode="wb") as tmp: + with wave.open(tmp.name, "wb") as wf: + wf.setnchannels(1) + wf.setsampwidth(2) + wf.setframerate(sample_rate) + wf.writeframes(audio_data) + return tmp.name + + +def _tts_sync(text: str, voice: str = "af_bella", rate: float = 1.0) -> tuple[bytes, int]: + tts = _get_tts_pipeline() + result = tts(text, voice=voice) + audio = None + sample_rate = 24000 + if isinstance(result, dict): + audio = result.get("audio") + sample_rate = int(result.get("sampling_rate", sample_rate)) + elif isinstance(result, (list, tuple)) and result: + audio = result[0] + + if audio is None: + raise RuntimeError("Kokoro 未返回音频数据") + + if hasattr(audio, "cpu"): + audio = audio.cpu().numpy() + + duration_ms = int(len(audio) * 1000 / sample_rate) + + if audio.dtype != np.int16: + audio = (audio * 32767).astype(np.int16) + + import tempfile + import wave + + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: + output_path = tmp.name + try: + with wave.open(output_path, "wb") as wf: + wf.setnchannels(1) + wf.setsampwidth(2) + wf.setframerate(sample_rate) + wf.writeframes(audio.tobytes()) + with open(output_path, "rb") as f: + return f.read(), duration_ms + finally: + if os.path.exists(output_path): + os.unlink(output_path) + + +async def _text_to_speech(text: str, voice: str = "af_bella", rate: float = 1.0) -> tuple[bytes, int]: + return await asyncio.to_thread(_tts_sync, text, voice, rate) + + +def _asr_sync(audio_data: bytes, language: str = "zh") -> str: + import soundfile as sf + + asr = _get_asr_pipeline() + audio_path = _save_audio_to_wav(audio_data) + try: + audio_array, sample_rate = sf.read(audio_path) + result = asr( + audio_array, + sampling_rate=sample_rate, + generate_kwargs={"language": language, "task": "transcribe"}, + ) + if isinstance(result, dict): + return result.get("text", "").strip() + return str(result).strip() + finally: + if os.path.exists(audio_path): + os.unlink(audio_path) + + +async def _speech_to_text(audio_data: bytes, language: str = "zh") -> str: + return await asyncio.to_thread(_asr_sync, audio_data, language) + + +class TTSRequest(BaseModel): + text: str + voice: str = "af_bella" + rate: float = 1.0 + format: str = "wav" + + +class TTSResponse(BaseModel): + audio_base64: str + format: str + duration_ms: int + + +class ASRRequest(BaseModel): + audio_base64: str + language: str = "zh-CN" + + +class ASRResponse(BaseModel): + text: str + language: str + + +def get_api_key(api_key: str): + from backend.main import API_KEY + + if api_key != API_KEY: + raise HTTPException(status_code=403, detail="API Key 无效") + return api_key + + +@router.post("/tts", response_model=TTSResponse) +async def text_to_speech(req: TTSRequest, api_key: str = Security(get_api_key)): + request_id = str(hash(req.text))[:8] + try: + logger.info("[TTS][%s] text_chars=%d voice=%s format=%s", request_id, len(req.text), req.voice, req.format) + audio_data, duration_ms = await _text_to_speech(req.text, req.voice, req.rate) + if req.format.lower() == "mp3": + import subprocess + import tempfile + + with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_in: + tmp_in.write(audio_data) + input_path = tmp_in.name + with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as tmp_out: + output_path = tmp_out.name + try: + cmd = ["ffmpeg", "-i", input_path, "-acodec", "libmp3lame", "-ab", "128k", output_path] + result = await asyncio.to_thread(lambda: subprocess.run(cmd, capture_output=True, text=True, timeout=30)) + if result.returncode != 0: + raise RuntimeError(f"MP3 转换失败: {result.stderr}") + with open(output_path, "rb") as f: + audio_data = f.read() + finally: + for path in [input_path, output_path]: + if os.path.exists(path): + os.unlink(path) + logger.info("[TTS][%s] success duration_ms=%d", request_id, duration_ms) + return TTSResponse(audio_base64=base64.b64encode(audio_data).decode(), format=req.format, duration_ms=duration_ms) + except Exception as e: + logger.exception("[TTS] failed: %s", e) + raise HTTPException(status_code=500, detail=str(e)) + + +@router.post("/asr", response_model=ASRResponse) +async def speech_to_text(req: ASRRequest, api_key: str = Security(get_api_key)): + request_id = str(hash(req.audio_base64))[:8] + try: + logger.info("[ASR][%s] audio_base64_chars=%d language=%s", request_id, len(req.audio_base64), req.language) + audio_data = base64.b64decode(req.audio_base64) + text = await _speech_to_text(audio_data, req.language[:2]) + logger.info("[ASR][%s] success text_chars=%d", request_id, len(text)) + return ASRResponse(text=text, language=req.language) + except Exception as e: + logger.exception("[ASR] failed: %s", e) + raise HTTPException(status_code=500, detail=str(e)) + + +def register_tts_asr_routes(app): + app.include_router(router, prefix="/v1/tts-asr") diff --git a/backend/tts_stt.py b/backend/tts_stt.py deleted file mode 100644 index 33b0426..0000000 --- a/backend/tts_stt.py +++ /dev/null @@ -1,141 +0,0 @@ -# TTS and Speech Recognition API for macOS Silicon -import os -import asyncio -import logging -import base64 -from typing import Optional -from fastapi import APIRouter, UploadFile, File, HTTPException, Security -from pydantic import BaseModel -from fastapi.security import APIKeyHeader - -router = APIRouter() -api_key_header = APIKeyHeader(name="X-API-Key") -logger = logging.getLogger("tts_stt") - - -def _speak_text_macos(text: str, voice: str = "meijia", rate: float = 0.5) -> bytes: - import subprocess - import tempfile - with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp: - output_path = tmp.name - try: - cmd = ["say", "-v", voice, "-r", str(rate * 10), "--output-format", "WAVE", "-o", output_path, text] - result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) - if result.returncode != 0: - raise Exception(f"TTS failed: {result.stderr}") - with open(output_path, "rb") as f: - audio_data = f.read() - return audio_data - finally: - if os.path.exists(output_path): - os.unlink(output_path) - - -async def _speak_text_macos_async(text: str, voice: str = "meijia", rate: float = 0.5) -> bytes: - loop = asyncio.get_event_loop() - return await loop.run_in_executor(None, _speak_text_macos, text, voice, rate) - - -def _recognize_speech_macos(audio_data: bytes, language: str = "zh-CN") -> str: - import tempfile - try: - import whisper - model = whisper.load_model("tiny") - with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp2: - tmp2.write(audio_data) - audio_for_whisper = tmp2.name - try: - result = model.transcribe(audio_for_whisper, language=language[:2]) - return result["text"] - finally: - if os.path.exists(audio_for_whisper): - os.unlink(audio_for_whisper) - except ImportError: - raise Exception("Whisper is required for speech recognition on macOS") - - -async def _recognize_speech_macos_async(audio_data: bytes, language: str = "zh-CN") -> str: - loop = asyncio.get_event_loop() - return await loop.run_in_executor(None, _recognize_speech_macos, audio_data, language) - - -class TTSRequest(BaseModel): - text: str - voice: str = "meijia" - rate: float = 0.5 - format: str = "wav" - - -class TTSResponse(BaseModel): - audio_base64: str - format: str - duration_ms: int - - -class STTRequest(BaseModel): - audio_base64: str - language: str = "zh-CN" - - -class STTResponse(BaseModel): - text: str - language: str - - -@router.post("/tts", response_model=TTSResponse) -async def text_to_speech(req: TTSRequest, api_key: str = Security(get_api_key)): - request_id = str(hash(req.text))[:8] - try: - logger.info("[TTS][%s] text_chars=%d voice=%s", request_id, len(req.text), req.voice) - audio_data = await _speak_text_macos_async(req.text, req.voice, req.rate) - if req.format.lower() == "mp3": - import tempfile - import subprocess - with tempfile.NamedTemporaryFile(suffix=".wav", delete=False) as tmp_in: - tmp_in.write(audio_data) - input_path = tmp_in.name - with tempfile.NamedTemporaryFile(suffix=".mp3", delete=False) as tmp_out: - output_path = tmp_out.name - try: - cmd = ["ffmpeg", "-i", input_path, "-acodec", "libmp3lame", output_path] - result = subprocess.run(cmd, capture_output=True, text=True, timeout=30) - if result.returncode != 0: - raise Exception(f"MP3 conversion failed: {result.stderr}") - with open(output_path, "rb") as f: - audio_data = f.read() - finally: - for p in [input_path, output_path]: - if os.path.exists(p): - os.unlink(p) - duration_ms = len(audio_data) * 1000 // 16000 - logger.info("[TTS][%s] success duration_ms=%d", request_id, duration_ms) - return TTSResponse(audio_base64=base64.b64encode(audio_data).decode(), format=req.format, duration_ms=duration_ms) - except Exception as e: - logger.exception("[TTS] failed: %s", e) - raise HTTPException(status_code=500, detail=str(e)) - - -@router.post("/stt", response_model=STTResponse) -async def speech_to_text(req: STTRequest, api_key: str = Security(get_api_key)): - request_id = str(hash(req.audio_base64))[:8] - try: - logger.info("[STT][%s] audio_base64_chars=%d language=%s", request_id, len(req.audio_base64), req.language) - audio_data = base64.b64decode(req.audio_base64) - text = await _recognize_speech_macos_async(audio_data, req.language) - logger.info("[STT][%s] success text_chars=%d", request_id, len(text)) - return STTResponse(text=text, language=req.language) - except Exception as e: - logger.exception("[STT] failed: %s", e) - raise HTTPException(status_code=500, detail=str(e)) - - -def get_api_key(api_key: str): - from backend.main import API_KEY - if api_key != API_KEY: - from fastapi import HTTPException - raise HTTPException(status_code=403, detail="Could not validate credentials") - return api_key - - -def register_tts_stt_routes(app): - app.include_router(router, prefix="/v1/tts-stt") \ No newline at end of file diff --git a/original_readme.md b/original_readme.md deleted file mode 100644 index 20c3741..0000000 Binary files a/original_readme.md and /dev/null differ diff --git a/package-lock.json b/package-lock.json index 7c0df9c..eb1a7f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,17 @@ "name": "llm-in-text", "version": "0.0.0", "dependencies": { + "@blocknote/xl-docx-exporter": "^0.47.3", "@milkdown/core": "^7.18.0", "@milkdown/crepe": "^7.18.0", "@milkdown/kit": "^7.18.0", "@milkdown/theme-nord": "^7.18.0", "@milkdown/vue": "^7.18.0", "docx": "^9.6.0", + "docx-preview": "^0.3.7", + "docx2pdf-converter": "^2.1.1", "html2pdf.js": "^0.14.0", + "jspdf": "^4.2.1", "katex": "^0.16.9", "markdown-it": "^13.0.0", "markdown-it-math": "^3.0.2", @@ -97,6 +101,126 @@ "node": ">=6.9.0" } }, + "node_modules/@blocknote/core": { + "version": "0.47.3", + "resolved": "https://registry.npmjs.org/@blocknote/core/-/core-0.47.3.tgz", + "integrity": "sha512-+YIOEXmmRXzDULYlQaycaFDofwQ/W79CWsU3GwNuRzp62QC+WpRLFLkgkoU4Pb1Vo7RvmzU1+udESdNUGhO2KA==", + "license": "MPL-2.0", + "dependencies": { + "@emoji-mart/data": "^1.2.1", + "@handlewithcare/prosemirror-inputrules": "^0.1.4", + "@shikijs/types": "^3", + "@tanstack/store": "^0.7.7", + "@tiptap/core": "^3.13.0", + "@tiptap/extension-bold": "^3.13.0", + "@tiptap/extension-code": "^3.13.0", + "@tiptap/extension-horizontal-rule": "^3.13.0", + "@tiptap/extension-italic": "^3.13.0", + "@tiptap/extension-link": "^3.13.0", + "@tiptap/extension-paragraph": "^3.13.0", + "@tiptap/extension-strike": "^3.13.0", + "@tiptap/extension-text": "^3.13.0", + "@tiptap/extension-underline": "^3.13.0", + "@tiptap/extensions": "^3.13.0", + "@tiptap/pm": "^3.13.0", + "emoji-mart": "^5.6.0", + "fast-deep-equal": "^3.1.3", + "hast-util-from-dom": "^5.0.1", + "prosemirror-highlight": "^0.13.0", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.3", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4", + "rehype-format": "^5.0.1", + "rehype-parse": "^9.0.1", + "rehype-remark": "^10.0.1", + "rehype-stringify": "^10.0.1", + "remark-gfm": "^4.0.1", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.1.2", + "remark-stringify": "^11.0.0", + "unified": "^11.0.5", + "unist-util-visit": "^5.0.0", + "uuid": "^8.3.2", + "y-prosemirror": "^1.3.7", + "y-protocols": "^1.0.6", + "yjs": "^13.6.27" + } + }, + "node_modules/@blocknote/core/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@blocknote/react": { + "version": "0.47.3", + "resolved": "https://registry.npmjs.org/@blocknote/react/-/react-0.47.3.tgz", + "integrity": "sha512-aWDgfnf/ufgqYfXtIy8QepfrgdVaC5DfU/BAWbazb/O5ucEFfCr6lK0SUVvswK1CkxL/KBpHpdAd+uT+rfgkmg==", + "license": "MPL-2.0", + "dependencies": { + "@blocknote/core": "0.47.3", + "@emoji-mart/data": "^1.2.1", + "@floating-ui/react": "^0.27.18", + "@floating-ui/utils": "^0.2.10", + "@tanstack/react-store": "0.7.7", + "@tiptap/core": "^3.13.0", + "@tiptap/pm": "^3.13.0", + "@tiptap/react": "^3.13.0", + "@types/use-sync-external-store": "1.5.0", + "emoji-mart": "^5.6.0", + "fast-deep-equal": "^3.1.3", + "lodash.merge": "^4.6.2", + "react-icons": "^5.5.0", + "use-sync-external-store": "1.6.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || >= 19.0.0-rc", + "react-dom": "^18.0 || ^19.0 || >= 19.0.0-rc" + } + }, + "node_modules/@blocknote/xl-docx-exporter": { + "version": "0.47.3", + "resolved": "https://registry.npmjs.org/@blocknote/xl-docx-exporter/-/xl-docx-exporter-0.47.3.tgz", + "integrity": "sha512-HE8JkQQ+G7wBiejFmyNlk04uUfSg6WMsKXPIHtrXK38kwXzlRTq/hu0lziZ990xc/G02/yETZNm/K1qpV9xmzA==", + "license": "GPL-3.0 OR PROPRIETARY", + "dependencies": { + "@blocknote/core": "0.47.3", + "@blocknote/xl-multi-column": "0.47.3", + "buffer": "^6.0.3", + "docx": "^9.5.1", + "image-meta": "^0.2.2" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || >= 19.0.0-rc", + "react-dom": "^18.0 || ^19.0 || >= 19.0.0-rc" + } + }, + "node_modules/@blocknote/xl-multi-column": { + "version": "0.47.3", + "resolved": "https://registry.npmjs.org/@blocknote/xl-multi-column/-/xl-multi-column-0.47.3.tgz", + "integrity": "sha512-gK0dOXJFvwVQi3Esee0vNbk+DsSxDub0BKLDwdUBDyfk+OkS6I82MUSAmEyvCiJG0G9Vo+hSaCMduX0YdgzYAQ==", + "license": "GPL-3.0 OR PROPRIETARY", + "dependencies": { + "@blocknote/core": "0.47.3", + "@blocknote/react": "0.47.3", + "@tiptap/core": "^3.13.0", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-tables": "^1.8.3", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4", + "react-icons": "^5.5.0" + }, + "peerDependencies": { + "react": "^18.0 || ^19.0 || >= 19.0.0-rc", + "react-dom": "^18.0 || ^19.0 || >= 19.0.0-rc" + } + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", @@ -567,6 +691,12 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@emoji-mart/data": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emoji-mart/data/-/data-1.2.1.tgz", + "integrity": "sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==", + "license": "MIT" + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.5.tgz", @@ -1023,17 +1153,61 @@ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.6.tgz", "integrity": "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==", "license": "MIT", + "peer": true, "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, + "node_modules/@floating-ui/react": { + "version": "0.27.19", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.27.19.tgz", + "integrity": "sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.8", + "@floating-ui/utils": "^0.2.11", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=17.0.0", + "react-dom": ">=17.0.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.8.tgz", + "integrity": "sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.6" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, "node_modules/@floating-ui/utils": { "version": "0.2.11", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.11.tgz", "integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==", "license": "MIT" }, + "node_modules/@handlewithcare/prosemirror-inputrules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@handlewithcare/prosemirror-inputrules/-/prosemirror-inputrules-0.1.4.tgz", + "integrity": "sha512-GMqlBeG2MKM+tXEFd2N+wIv5z4VvJTg8JtfJUrdjvFq2W6v+AW8oTgiWyFw8L3iEQwvtQcVJxU873iB0LXUNNw==", + "license": "MIT", + "dependencies": { + "prosemirror-history": "^1.4.1", + "prosemirror-transform": "^1.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, "node_modules/@iconify/types": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", @@ -1602,6 +1776,12 @@ "url": "https://github.com/sponsors/ocavue" } }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", + "license": "MIT" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-rc.2", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.2.tgz", @@ -1959,6 +2139,299 @@ "win32" ] }, + "node_modules/@shikijs/types": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.23.0.tgz", + "integrity": "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@tanstack/react-store": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-store/-/react-store-0.7.7.tgz", + "integrity": "sha512-qqT0ufegFRDGSof9D/VqaZgjNgp4tRPHZIJq2+QIHkMUtHjaJ0lYrrXjeIUJvjnTbgPfSD1XgOMEt0lmANn6Zg==", + "license": "MIT", + "dependencies": { + "@tanstack/store": "0.7.7", + "use-sync-external-store": "^1.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/store": { + "version": "0.7.7", + "resolved": "https://registry.npmjs.org/@tanstack/store/-/store-0.7.7.tgz", + "integrity": "sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tiptap/core": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.22.2.tgz", + "integrity": "sha512-atq35NkpeEphH6vNYJ0pTLLBA73FAbvTV9Ovd3AaTC5s99/KF5Q86zVJXvml8xPRcMGM6dLp+eSSd06oTscMSA==", + "license": "MIT", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-bold": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.22.2.tgz", + "integrity": "sha512-bqsPJyKcT/RWse4e16U2EKhraR8a2+98TUuk1amG3yCyFJZStoO/j+pN0IqZdZZjr3WtxFyvwWp7Kc59UN+jUA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.22.2.tgz", + "integrity": "sha512-5hbyDOSkJwA2uh0v9Mm0Dd9bb9inx6tHBEDSH2tCB9Rm23poz3yOreB7SNX8xDMe5L0/PQesfWC14RitcmhKPg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@floating-ui/dom": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2", + "@tiptap/pm": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-code": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.22.2.tgz", + "integrity": "sha512-iYFY+yzfYA9MKt7nupyW/PzqL9XC2D0mC8l1z2Y10i0/fGL8NbqIYjhNUAyXGqH3QWcI+DirI66842y2OadPOg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-floating-menu": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.22.2.tgz", + "integrity": "sha512-r0ZTeh9rNtj9Api+G0YyaB+tAKPDn7aYWg+qSrmAC5EyUPee6Zjn3zlw0q4renCeQflvNRK20xHM8zokC41jOA==", + "license": "MIT", + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "^3.22.2", + "@tiptap/pm": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.22.2.tgz", + "integrity": "sha512-Oz8KN5KJAWV1mFNE9UIWXdMD6xa5zPf/0yLsT8V4sgaRm+VsdFKllN58BY9qCZf/kIZbaOez5KkaoeAcm0MAZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2", + "@tiptap/pm": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-italic": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.22.2.tgz", + "integrity": "sha512-fmtQu2HDnV3sOZPdz0+1lOLI7UtrIhusohJj2UwOLQxG8qqhLwbvWx2OQTlfblgY0z+CjLRr6ANbNDxOTIblfg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-link": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.22.2.tgz", + "integrity": "sha512-TXfSoKmng5pecvQUZqdsx6ICeob5V5hhYOj2vCEtjfcjWsyCndqFIl1w+Nt/yI5ehrFNOVPyj3ZvcELuuAW6pw==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2", + "@tiptap/pm": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.22.2.tgz", + "integrity": "sha512-EHZZzxVhvzEPDPWtRBF1YKhB+WCUjd1C2NhjHfL3Dl71PBqM3ZWA6qN7NDGPyNyGGWauui/NR/4X+5AfPqlHyA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.22.2.tgz", + "integrity": "sha512-YFC3elKU1L8PiGbcB6tqd/7vWPF5IbydJz0POJpHzSjstX+VfT8VsvS7ubxVuSIWQ11kGkH3mzX6LX8JHsHZxg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.22.2.tgz", + "integrity": "sha512-J1w7JwijfSD7ah0WfiwZ/DVWCIGT9x369RM4RJc57i44mIBElj7tl1dh+N5KPGOXKUup4gr7sSJAE38lgeaDMg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.22.2.tgz", + "integrity": "sha512-BaV6WOowxdkGTLWiU7DdZ3Twh633O4RGqwUM5dDas5LvaqL8AMWGTO8Wg9yAaaKXzd9MtKI1ZCqS/+MtzusgkQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2" + } + }, + "node_modules/@tiptap/extensions": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.22.2.tgz", + "integrity": "sha512-s7MZmm2Xdq+8feIXgY3v7gVpQ5ClqBZi20KheouS7KSbBlrY4fu2irYR1EGc6r1UUVaHMxEa+cx5knhx+mIPUw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2", + "@tiptap/pm": "^3.22.2" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.22.2.tgz", + "integrity": "sha512-G2ENwIazoSKkAnN5MN5yN91TIZNFm6TxB74kPf3Empr2k9W51Hkcier70jHGpArhgcEaL4BVreuU1PRDRwCeGw==", + "license": "MIT", + "peer": true, + "dependencies": { + "prosemirror-changeset": "^2.3.0", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.24.1", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.5.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.38.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "3.22.2", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.22.2.tgz", + "integrity": "sha512-tyGKG69e/MkpoD/JTpVPz0XydEHxh1MSAYnLb3gRvyvBDv2r/veLea+cApkmjQaCfkKC/CWwTFXBYlOB0caSBA==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "fast-equals": "^5.3.3", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.22.2", + "@tiptap/extension-floating-menu": "^3.22.2" + }, + "peerDependencies": { + "@tiptap/core": "^3.22.2", + "@tiptap/pm": "^3.22.2", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tiptap/react/node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/d3": { "version": "7.4.3", "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", @@ -2249,6 +2722,12 @@ "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", "license": "MIT" }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "license": "MIT" + }, "node_modules/@types/lodash": { "version": "4.17.24", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", @@ -2264,6 +2743,16 @@ "@types/lodash": "*" } }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -2273,6 +2762,12 @@ "@types/unist": "*" } }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "license": "MIT" + }, "node_modules/@types/ms": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", @@ -2301,6 +2796,26 @@ "license": "MIT", "optional": true }, + "node_modules/@types/react": { + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -2314,6 +2829,18 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, + "node_modules/@types/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-5dyB8nLC/qogMrlCizZnYWQTA4lnb/v+It+sqNl5YnSRAPMlIqY/X0Xn+gZw8vOL+TgTTr28VEbn3uf8fUtAkw==", + "license": "MIT" + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "license": "ISC" + }, "node_modules/@upsetjs/venn.js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", @@ -2471,6 +2998,15 @@ "node": ">=0.4.0" } }, + "node_modules/adm-zip": { + "version": "0.5.17", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.17.tgz", + "integrity": "sha512-+Ut8d9LLqwEvHHJl1+PIHqoyDxFgVN847JTVM3Izi3xHDWPE4UtzzXysMZQs64DMcrJfBeS/uoEP4AD3HQHnQQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, "node_modules/align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", @@ -2950,6 +3486,26 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", @@ -3010,6 +3566,30 @@ "license": "MIT", "optional": true }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -3122,6 +3702,26 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chevrotain": { "version": "11.1.2", "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", @@ -3277,6 +3877,16 @@ "node": ">=0.10.0" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", @@ -4163,6 +4773,24 @@ "node": ">=10" } }, + "node_modules/docx-preview": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/docx-preview/-/docx-preview-0.3.7.tgz", + "integrity": "sha512-Lav69CTA/IYZPJTsKH7oYeoZjyg96N0wEJMNslGJnZJ+dMUZK85Lt5ASC79yUlD48ecWjuv+rkcmFt6EVPV0Xg==", + "license": "Apache-2.0", + "dependencies": { + "jszip": ">=3.0.0" + } + }, + "node_modules/docx2pdf-converter": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/docx2pdf-converter/-/docx2pdf-converter-2.1.1.tgz", + "integrity": "sha512-XUxY4D6HGuWVdOXuEygko1TyA1y2vejqY6iv7KrKT2St99UxreokeP/ic70XcGQuZRXTwWo5GY0haiq9HCHU4Q==", + "license": "ISC", + "dependencies": { + "adm-zip": "^0.5.16" + } + }, "node_modules/dompurify": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.3.tgz", @@ -4172,6 +4800,12 @@ "@types/trusted-types": "^2.0.7" } }, + "node_modules/emoji-mart": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/emoji-mart/-/emoji-mart-5.6.0.tgz", + "integrity": "sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==", + "license": "MIT" + }, "node_modules/entities": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", @@ -4351,6 +4985,21 @@ "node": ">=0.10.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-png": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", @@ -4691,6 +5340,273 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-format": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hast-util-format/-/hast-util-format-1.1.0.tgz", + "integrity": "sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "html-whitespace-sensitive-tag-names": "^3.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", + "integrity": "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-mdast": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-mdast/-/hast-util-to-mdast-10.1.2.tgz", + "integrity": "sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "hast-util-to-text": "^4.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-minify-whitespace": "^6.0.0", + "trim-trailing-lines": "^2.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/home-or-tmp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-1.0.0.tgz", @@ -4705,6 +5621,26 @@ "node": ">=0.10.0" } }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/html-whitespace-sensitive-tag-names": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz", + "integrity": "sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/html2canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", @@ -4741,6 +5677,32 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/image-meta": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/image-meta/-/image-meta-0.2.2.tgz", + "integrity": "sha512-3MOLanc3sb3LNGWQl1RlQlNWURE5g32aUphrDyFeCsxBTk08iE3VNe4CwsUZ0Qs1X+EfX0+r29Sxdpza4B+yRA==", + "license": "MIT" + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -5042,6 +6004,16 @@ "node": ">=0.10.0" } }, + "node_modules/isomorphic.js": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", + "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", + "license": "MIT", + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/js-tokens": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.1.tgz", @@ -5196,6 +6168,27 @@ "node": ">=0.10.0" } }, + "node_modules/lib0": { + "version": "0.2.117", + "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.117.tgz", + "integrity": "sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==", + "license": "MIT", + "dependencies": { + "isomorphic.js": "^0.2.4" + }, + "bin": { + "0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js", + "0gentesthtml": "bin/gentesthtml.js", + "0serve": "bin/0serve.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -5214,6 +6207,12 @@ "uc.micro": "^1.0.1" } }, + "node_modules/linkifyjs": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", + "license": "MIT" + }, "node_modules/lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", @@ -5227,6 +6226,12 @@ "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "license": "MIT" + }, "node_modules/longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", @@ -5534,6 +6539,27 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-to-markdown": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", @@ -6604,6 +7630,30 @@ "node": ">=0.10.0" } }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", @@ -6820,6 +7870,16 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/prosemirror-changeset": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", @@ -6829,6 +7889,15 @@ "prosemirror-transform": "^1.0.0" } }, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, "node_modules/prosemirror-commands": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", @@ -6878,6 +7947,59 @@ "prosemirror-view": "^1.0.0" } }, + "node_modules/prosemirror-highlight": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/prosemirror-highlight/-/prosemirror-highlight-0.13.1.tgz", + "integrity": "sha512-41EwMJDUeFBxizPP1/msQBjDke1YyaTy40w3CGoc7fjXboDBgyhz2LWThwaygL9LkDvBqn4pwBJg1PNtrNilGg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ocavue" + }, + "peerDependencies": { + "@shikijs/types": "^1.29.2 || ^2.0.0 || ^3.0.0", + "@types/hast": "^3.0.0", + "highlight.js": "^11.9.0", + "lowlight": "^3.1.0", + "prosemirror-model": "^1.19.3", + "prosemirror-state": "^1.4.3", + "prosemirror-transform": "^1.8.0", + "prosemirror-view": "^1.32.4", + "refractor": "^5.0.0", + "sugar-high": "^0.6.1 || ^0.7.0 || ^0.8.0 || ^0.9.0" + }, + "peerDependenciesMeta": { + "@shikijs/types": { + "optional": true + }, + "@types/hast": { + "optional": true + }, + "highlight.js": { + "optional": true + }, + "lowlight": { + "optional": true + }, + "prosemirror-model": { + "optional": true + }, + "prosemirror-state": { + "optional": true + }, + "prosemirror-transform": { + "optional": true + }, + "prosemirror-view": { + "optional": true + }, + "refractor": { + "optional": true + }, + "sugar-high": { + "optional": true + } + } + }, "node_modules/prosemirror-history": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", @@ -6910,6 +8032,79 @@ "w3c-keyname": "^2.2.0" } }, + "node_modules/prosemirror-markdown": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz", + "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-markdown/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/prosemirror-markdown/node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/prosemirror-markdown/node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/prosemirror-markdown/node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "license": "MIT" + }, + "node_modules/prosemirror-markdown/node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/prosemirror-menu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz", + "integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==", + "license": "MIT", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, "node_modules/prosemirror-model": { "version": "1.25.4", "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", @@ -6933,6 +8128,15 @@ "url": "https://github.com/sponsors/ocavue" } }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, "node_modules/prosemirror-schema-list": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", @@ -6969,6 +8173,33 @@ "prosemirror-view": "^1.41.4" } }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "license": "MIT", + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-trailing-node/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/prosemirror-transform": { "version": "1.12.0", "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.0.tgz", @@ -7015,6 +8246,15 @@ } } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -7072,6 +8312,38 @@ "node": ">=0.10.0" } }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-icons": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.6.0.tgz", + "integrity": "sha512-RH93p5ki6LfOiIt0UtDyNg/cee+HLVR6cHHtW3wALfo+eOHTp8RnU2kRkI6E+H19zMIs03DyxUG/GfZMOGvmiA==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -7477,6 +8749,81 @@ "regjsparser": "bin/parser" } }, + "node_modules/rehype-format": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/rehype-format/-/rehype-format-5.0.1.tgz", + "integrity": "sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-format": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-minify-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/rehype-minify-whitespace/-/rehype-minify-whitespace-6.0.2.tgz", + "integrity": "sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-parse": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", + "integrity": "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-html": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-remark": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-remark/-/rehype-remark-10.0.1.tgz", + "integrity": "sha512-EmDndlb5NVwXGfUa4c9GPK+lXeItTilLhE6ADSaQuHr4JUlKw9MidzGzx4HpqZrNCt6vnHmEifXQiiA+CEnjYQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "hast-util-to-mdast": "^10.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-stringify": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-stringify/-/rehype-stringify-10.0.1.tgz", + "integrity": "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark": { "version": "15.0.1", "resolved": "https://registry.npmjs.org/remark/-/remark-15.0.1.tgz", @@ -7558,6 +8905,23 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "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" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-stringify": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", @@ -7799,6 +9163,12 @@ "node": ">=11.0.0" } }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -8042,6 +9412,16 @@ "license": "MIT", "optional": true }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -8123,6 +9503,20 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stringmap": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz", @@ -8195,6 +9589,12 @@ "node": ">=12.0.0" } }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, "node_modules/text-segmentation": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", @@ -8303,6 +9703,16 @@ "node": ">=0.10.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", @@ -8313,6 +9723,16 @@ "node": ">=0.10.0" } }, + "node_modules/trim-trailing-lines": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-2.1.0.tgz", + "integrity": "sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -8400,6 +9820,20 @@ "node": ">=0.10.0" } }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", @@ -8413,6 +9847,19 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-remove-position": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz", @@ -8549,6 +9996,15 @@ "node": ">=0.10.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/user-home": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", @@ -8604,6 +10060,20 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vfile-message": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", @@ -8827,6 +10297,16 @@ "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", "license": "MIT" }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/window-size": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", @@ -8875,6 +10355,51 @@ "xml-js": "bin/cli.js" } }, + "node_modules/y-prosemirror": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/y-prosemirror/-/y-prosemirror-1.3.7.tgz", + "integrity": "sha512-NpM99WSdD4Fx4if5xOMDpPtU3oAmTSjlzh5U4353ABbRHl1HtAFUx6HlebLZfyFxXN9jzKMDkVbcRjqOZVkYQg==", + "license": "MIT", + "dependencies": { + "lib0": "^0.2.109" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "prosemirror-model": "^1.7.1", + "prosemirror-state": "^1.2.3", + "prosemirror-view": "^1.9.10", + "y-protocols": "^1.0.1", + "yjs": "^13.5.38" + } + }, + "node_modules/y-protocols": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-1.0.7.tgz", + "integrity": "sha512-YSVsLoXxO67J6eE/nV4AtFtT3QEotZf5sK5BHxFBXso7VDUT3Tx07IfA6hsu5Q5OmBdMkQVmFZ9QOA7fikWvnw==", + "license": "MIT", + "peer": true, + "dependencies": { + "lib0": "^0.2.85" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + }, + "peerDependencies": { + "yjs": "^13.0.0" + } + }, "node_modules/y18n": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", @@ -8897,6 +10422,24 @@ "y18n": "^3.2.0" } }, + "node_modules/yjs": { + "version": "13.6.30", + "resolved": "https://registry.npmjs.org/yjs/-/yjs-13.6.30.tgz", + "integrity": "sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "lib0": "^0.2.99" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "funding": { + "type": "GitHub Sponsors ❤", + "url": "https://github.com/sponsors/dmonad" + } + }, "node_modules/zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index e8f7817..a7dd887 100644 --- a/package.json +++ b/package.json @@ -11,13 +11,17 @@ "check": "npm run build" }, "dependencies": { + "@blocknote/xl-docx-exporter": "^0.47.3", "@milkdown/core": "^7.18.0", "@milkdown/crepe": "^7.18.0", "@milkdown/kit": "^7.18.0", "@milkdown/theme-nord": "^7.18.0", "@milkdown/vue": "^7.18.0", "docx": "^9.6.0", + "docx-preview": "^0.3.7", + "docx2pdf-converter": "^2.1.1", "html2pdf.js": "^0.14.0", + "jspdf": "^4.2.1", "katex": "^0.16.9", "markdown-it": "^13.0.0", "markdown-it-math": "^3.0.2", diff --git a/src/components/DocBlockCrepe.vue b/src/components/DocBlockCrepe.vue index 88e9583..e1e7622 100644 --- a/src/components/DocBlockCrepe.vue +++ b/src/components/DocBlockCrepe.vue @@ -1,250 +1,329 @@ diff --git a/src/components/MilkdownEditor.vue b/src/components/MilkdownEditor.vue index 794f912..91e1124 100644 --- a/src/components/MilkdownEditor.vue +++ b/src/components/MilkdownEditor.vue @@ -35,8 +35,9 @@ - + - +
+ +
+ + + +
+