diff --git a/.gitignore b/.gitignore index 98186f7..a06f80f 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,7 @@ env/ .mypy_cache/ .ruff_cache/ htmlcov/ +.coverage api_performance_report.md # Env files @@ -46,4 +47,9 @@ api_performance_report.md # IDE directories .kilocode/ .kilo/ -.codex/ \ No newline at end of file +.codex/ + +# Agent/runtime state and local verification artifacts +.omx/ +.tmp-*.png +tmp-*.txt diff --git a/backend/llm.py b/backend/llm.py index 90886ed..391b6f0 100644 --- a/backend/llm.py +++ b/backend/llm.py @@ -3,6 +3,7 @@ import time import logging import asyncio from datetime import datetime +from typing import AsyncIterator import ollama from dotenv import load_dotenv @@ -11,6 +12,7 @@ from prompts import get_vlm_ocr_prompt load_dotenv() OLLAMA_MODEL = os.getenv('OLLAMA_MODEL', 'gpt-oss:20b') +PRO_OLLAMA_MODEL = os.getenv('PRO_OLLAMA_MODEL', OLLAMA_MODEL) OLLAMA_HOST = os.getenv('OLLAMA_HOST', 'http://localhost:11434') VLM_MODEL = os.getenv('VLM_MODEL', 'qwen3-vl:30b') @@ -29,14 +31,73 @@ def _extract_message(response) -> tuple[str, str]: if hasattr(response, 'message') and response.message: content = response.message.content or "" thinking = getattr(response.message, 'thinking', '') or "" - elif isinstance(response, dict): + elif isinstance(response, dict) and 'message' in response: msg = response.get('message', {}) content = msg.get('content', '') or "" thinking = msg.get('thinking', '') or "" + # fallback for generate + if not content: + if hasattr(response, 'response'): + content = getattr(response, 'response', '') or "" + elif isinstance(response, dict) and 'response' in response: + content = response.get('response', '') or "" + return content, thinking +def _build_prompt(prompt: str, system_prompt: str | None = None) -> str: + if system_prompt and system_prompt.strip(): + return f"{system_prompt}\n\n{prompt}" + return prompt + + +def _resolve_model_name(model: str | None = None, *, use_pro_model: bool = False) -> str: + candidate = (model or '').strip() + if candidate: + return candidate + return PRO_OLLAMA_MODEL if use_pro_model else OLLAMA_MODEL + + +def _build_generate_kwargs( + prompt: str, + *, + system_prompt: str | None = None, + temperature: float = 0.7, + thinking: str | None = None, + model: str | None = None, + use_pro_model: bool = False, + stream: bool = False, +) -> dict: + kwargs = { + "model": _resolve_model_name(model, use_pro_model=use_pro_model), + "prompt": _build_prompt(prompt, system_prompt), + "stream": stream, + "raw": True, + "options": { + 'temperature': temperature, + 'repeat_penalty': 1.1, + }, + } + if thinking: + kwargs["think"] = thinking + return kwargs + + +def _extract_stream_text(chunk) -> str: + content, _ = _extract_message(chunk) + if content: + return content + + if isinstance(chunk, dict): + return chunk.get('response', '') or '' + + if hasattr(chunk, 'response'): + return getattr(chunk, 'response', '') or '' + + return '' + + async def call_ollama( prompt: str, *, @@ -44,16 +105,19 @@ async def call_ollama( tag: str = "default", temperature: float = 0.7, thinking: str | None = None, + model: str | None = None, + use_pro_model: bool = False, ) -> dict: """ 调用 Ollama API 并返回 content 和 thinking。 """ start = time.perf_counter() start_dt = datetime.now() + model_name = _resolve_model_name(model, use_pro_model=use_pro_model) logger.info( "[LLM][%s] request model=%s host=%s prompt_chars=%d system_chars=%d temp=%.2f thinking=%s", tag, - OLLAMA_MODEL, + model_name, OLLAMA_HOST, len(prompt), len(system_prompt or ""), @@ -62,24 +126,17 @@ async def call_ollama( ) try: - messages = [] - if system_prompt and system_prompt.strip(): - messages.append({"role": "system", "content": system_prompt}) - messages.append({"role": "user", "content": prompt}) + kwargs = _build_generate_kwargs( + prompt, + system_prompt=system_prompt, + temperature=temperature, + thinking=thinking, + model=model, + use_pro_model=use_pro_model, + stream=False, + ) - kwargs = { - "model": OLLAMA_MODEL, - "messages": messages, - "stream": False, - "options": { - 'temperature': temperature, - 'repeat_penalty': 1.1, - }, - } - if thinking: - kwargs["think"] = thinking - - response = await asyncio.wait_for(client.chat(**kwargs), timeout=COMPLETION_TIMEOUT) + response = await asyncio.wait_for(client.generate(**kwargs), timeout=COMPLETION_TIMEOUT) except asyncio.CancelledError: elapsed_ms = (time.perf_counter() - start) * 1000 end_dt = datetime.now() @@ -126,6 +183,101 @@ async def call_ollama( return {"content": content, "think": thinking} + +async def stream_ollama( + prompt: str, + *, + system_prompt: str | None = None, + tag: str = "default-stream", + temperature: float = 0.7, + thinking: str | None = None, + model: str | None = None, + use_pro_model: bool = False, +) -> AsyncIterator[str]: + start = time.perf_counter() + start_dt = datetime.now() + model_name = _resolve_model_name(model, use_pro_model=use_pro_model) + yielded_chars = 0 + + logger.info( + "[LLM][%s] stream request model=%s host=%s prompt_chars=%d system_chars=%d temp=%.2f thinking=%s", + tag, + model_name, + OLLAMA_HOST, + len(prompt), + len(system_prompt or ""), + temperature, + thinking, + ) + + try: + kwargs = _build_generate_kwargs( + prompt, + system_prompt=system_prompt, + temperature=temperature, + thinking=thinking, + model=model, + use_pro_model=use_pro_model, + stream=True, + ) + stream = await client.generate(**kwargs) + iterator = stream.__aiter__() + deadline = time.perf_counter() + COMPLETION_TIMEOUT + + while True: + remaining = deadline - time.perf_counter() + if remaining <= 0: + raise TimeoutError("LLM stream timed out") + + try: + chunk = await asyncio.wait_for(iterator.__anext__(), timeout=remaining) + except StopAsyncIteration: + break + + text = _extract_stream_text(chunk) + if not text: + continue + + yielded_chars += len(text) + yield text + except asyncio.CancelledError: + elapsed_ms = (time.perf_counter() - start) * 1000 + end_dt = datetime.now() + logger.info( + "[LLM][%s] stream_time [%s --> %s]", + tag, + start_dt.strftime("%H:%M:%S"), + end_dt.strftime("%H:%M:%S"), + ) + logger.warning("[LLM][%s] stream cancelled after %.1fms", tag, elapsed_ms) + raise + except Exception: + elapsed_ms = (time.perf_counter() - start) * 1000 + end_dt = datetime.now() + logger.info( + "[LLM][%s] stream_time [%s --> %s]", + tag, + start_dt.strftime("%H:%M:%S"), + end_dt.strftime("%H:%M:%S"), + ) + logger.exception("[LLM][%s] stream failed after %.1fms", tag, elapsed_ms) + raise + + elapsed_ms = (time.perf_counter() - start) * 1000 + end_dt = datetime.now() + logger.info( + "[LLM][%s] stream_time [%s --> %s]", + tag, + start_dt.strftime("%H:%M:%S"), + end_dt.strftime("%H:%M:%S"), + ) + logger.info( + "[LLM][%s] stream finished in %.1fms yielded_chars=%d", + tag, + elapsed_ms, + yielded_chars, + ) + async def call_vlm_ocr(image_bytes: bytes, language: str = 'auto') -> str: start = time.perf_counter() start_dt = datetime.now() diff --git a/backend/main.py b/backend/main.py index c8deae2..136b78f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,5 +1,6 @@ import asyncio import base64 +import json import logging import os import re @@ -11,12 +12,12 @@ from typing import Optional from fastapi import FastAPI, HTTPException, Request, Security from fastapi.middleware.cors import CORSMiddleware -from fastapi.responses import JSONResponse +from fastapi.responses import JSONResponse, StreamingResponse from fastapi.security import APIKeyHeader from pydantic import BaseModel from geoip import get_ip_location_text -from llm import call_ollama, call_vlm_ocr +from llm import call_ollama, call_vlm_ocr, stream_ollama from models import UserPreferences from prompt import build_completion_prompts, prepare_prompt_context import markitdown @@ -78,6 +79,8 @@ class CompletionRequest(BaseModel): model_thinking: str = "low" privacy_mode: bool = False user_preferences: Optional[UserPreferences] = None + model: Optional[str] = None + temperature: float = 0.7 class CancelCompletionRequest(BaseModel): @@ -143,6 +146,14 @@ def get_client_ip(request: Request) -> str: return request.headers.get("X-Client-IP") or "unknown" +def _clamp_temperature(value: float, default: float = 0.7) -> float: + try: + numeric = float(value) + except (TypeError, ValueError): + return default + return max(0.0, min(numeric, 1.2)) + + @app.post("/v1/completions") async def create_completion(request: Request, req: CompletionRequest, api_key: str = Security(get_api_key)): request_id = request.headers.get("X-Request-Id") or str(uuid.uuid4()) @@ -189,8 +200,9 @@ async def create_completion(request: Request, req: CompletionRequest, api_key: s user_prompt, system_prompt=system_prompt, tag=f"{request_tag}-primary", - temperature=0.7, + temperature=_clamp_temperature(req.temperature, 0.7), thinking=req.model_thinking if req.model_thinking != "none" else None, + model=req.model, ) ) @@ -224,6 +236,124 @@ async def create_completion(request: Request, req: CompletionRequest, api_key: s ACTIVE_COMPLETIONS.pop(request_id, None) +@app.post("/v1/pro/completions/stream") +async def create_pro_completion_stream(request: Request, req: CompletionRequest, api_key: str = Security(get_api_key)): + request_id = request.headers.get("X-Request-Id") or str(uuid.uuid4()) + request_tag = request_id[:8] + queue: asyncio.Queue[Optional[tuple[str, str]]] = asyncio.Queue() + + client_ip = "hidden" + location = "" + + if not req.privacy_mode: # pragma: no cover + client_ip = get_client_ip(request) + location = get_ip_location_text(client_ip) + if location: + logger.info("[%s] client_location=%s", request_tag, location) + + logger.info( + "[%s] /v1/pro/completions/stream request_id=%s client_ip=%s prefix_chars=%d suffix_chars=%d lang=%s thinking=%s privacy=%s model=%s temp=%.2f", + request_tag, + request_id, + client_ip, + len(req.prefix or ""), + len(req.suffix or ""), + req.languageId, + req.model_thinking, + req.privacy_mode, + req.model or "", + _clamp_temperature(req.temperature, 0.7), + ) + + llm_prefix, llm_suffix = prepare_prompt_context(req.prefix or "", req.suffix or "") + logger.info("[%s] pro_llm_input_prefix=%r", request_tag, llm_prefix) + logger.info("[%s] pro_llm_input_suffix=%r", request_tag, llm_suffix) + + system_prompt, user_prompt = build_completion_prompts( + req.prefix, + req.suffix, + req.languageId, + location=location, + thinking_level=req.model_thinking, + preferences=req.user_preferences, + ) + + async def producer() -> None: + chunks: list[str] = [] + try: + async for delta in stream_ollama( + user_prompt, + system_prompt=system_prompt, + tag=f"{request_tag}-pro", + temperature=_clamp_temperature(req.temperature, 0.7), + thinking=req.model_thinking if req.model_thinking != "none" else None, + model=req.model, + use_pro_model=True, + ): + chunks.append(delta) + await queue.put(("chunk", json.dumps({"delta": delta}, ensure_ascii=False))) + + content = "".join(chunks) + logger.info( + "[%s] pro stream resolved request_id=%s content_chars=%d content_preview='%s'", + request_tag, + request_id, + len(content), + _preview(content, 120), + ) + await queue.put(( + "done", + json.dumps({"content": content, "request_id": request_id}, ensure_ascii=False), + )) + except asyncio.CancelledError: + logger.info("[%s] /v1/pro/completions/stream cancelled request_id=%s", request_tag, request_id) + await queue.put(( + "cancelled", + json.dumps({"cancelled": True, "request_id": request_id}, ensure_ascii=False), + )) + raise + except Exception as e: + logger.exception("[%s] /v1/pro/completions/stream failed request_id=%s: %s", request_tag, request_id, e) + await queue.put(( + "error", + json.dumps({"error": str(e), "request_id": request_id}, ensure_ascii=False), + )) + finally: + await queue.put(None) + + producer_task = asyncio.create_task(producer()) + existing = ACTIVE_COMPLETIONS.get(request_id) + if existing and not existing.done(): + existing.cancel() + ACTIVE_COMPLETIONS[request_id] = producer_task + + async def event_stream(): + try: + while True: + item = await queue.get() + if item is None: + break + + event_name, data = item + yield f"event: {event_name}\ndata: {data}\n\n" + except asyncio.CancelledError: + producer_task.cancel() + raise + finally: + active = ACTIVE_COMPLETIONS.get(request_id) + if active is producer_task: + ACTIVE_COMPLETIONS.pop(request_id, None) + + return StreamingResponse( + event_stream(), + media_type="text/event-stream", + headers={ + "Cache-Control": "no-cache", + "X-Accel-Buffering": "no", + }, + ) + + @app.post("/v1/completions/cancel") async def cancel_completion(req: CancelCompletionRequest, api_key: str = Security(get_api_key)): request_tag = str(uuid.uuid4())[:8] @@ -349,8 +479,19 @@ async def convert_to_markdown(request: ConvertRequest, api_key: str = Security(g # TTS and ASR routes (lazy loaded to avoid heavy import on startup) def _register_tts_asr_routes(): - from tts_asr import register_tts_asr_routes - register_tts_asr_routes(app) + try: + from tts_asr import register_tts_asr_routes + except ModuleNotFoundError as exc: + logger.warning("Skipping TTS/ASR route registration because a dependency is missing: %s", exc) + return + except Exception as exc: + logger.warning("Skipping TTS/ASR route registration because import failed: %s", exc) + return + + try: + register_tts_asr_routes(app) + except Exception as exc: + logger.warning("Failed to register TTS/ASR routes: %s", exc) _register_tts_asr_routes() diff --git a/backend/prompt.py b/backend/prompt.py index 8c90936..b1673e6 100644 --- a/backend/prompt.py +++ b/backend/prompt.py @@ -331,15 +331,7 @@ Step 3: Choose newline type === NOW COMPLETE THE TASK === - -{recent_prefix} - - - -{recent_suffix} - - -Output:""" +<|fim_prefix|>{recent_prefix}<|fim_suffix|>{recent_suffix}<|fim_middle|>""" system_prompt = build_inline_system_prompt(safe_language_id) return system_prompt.strip(), user_prompt.strip() diff --git a/backend/tests/test_llm.py b/backend/tests/test_llm.py index 59c713d..dd627b8 100644 --- a/backend/tests/test_llm.py +++ b/backend/tests/test_llm.py @@ -19,11 +19,11 @@ except ModuleNotFoundError: def test_call_ollama_messages_roles_with_system(monkeypatch): captured = {} - async def fake_chat(**kwargs): - captured["messages"] = kwargs["messages"] - return {"message": {"content": "ok", "thinking": ""}} + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return {"response": "ok"} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) result = asyncio.run( llm.call_ollama( @@ -35,20 +35,18 @@ def test_call_ollama_messages_roles_with_system(monkeypatch): ) assert result["content"] == "ok" - assert captured["messages"][0]["role"] == "system" - assert captured["messages"][0]["content"] == "system prompt body" - assert captured["messages"][1]["role"] == "user" - assert captured["messages"][1]["content"] == "user prompt body" + assert captured["kwargs"]["prompt"] == "system prompt body\n\nuser prompt body" + assert captured["kwargs"]["raw"] is True def test_call_ollama_messages_roles_without_system(monkeypatch): captured = {} - async def fake_chat(**kwargs): - captured["messages"] = kwargs["messages"] - return {"message": {"content": "ok", "thinking": ""}} + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return {"response": "ok"} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) result = asyncio.run( llm.call_ollama( @@ -60,6 +58,5 @@ def test_call_ollama_messages_roles_without_system(monkeypatch): ) assert result["content"] == "ok" - assert len(captured["messages"]) == 1 - assert captured["messages"][0]["role"] == "user" - assert captured["messages"][0]["content"] == "user prompt only" + assert captured["kwargs"]["prompt"] == "user prompt only" + assert captured["kwargs"]["raw"] is True diff --git a/backend/tests/test_llm_extended.py b/backend/tests/test_llm_extended.py index 2cce77c..cc51dda 100644 --- a/backend/tests/test_llm_extended.py +++ b/backend/tests/test_llm_extended.py @@ -85,46 +85,45 @@ def test_extract_message_empty_dict(): def test_call_ollama_no_system_message(monkeypatch): captured = {} - async def fake_chat(**kwargs): - captured["messages"] = kwargs.get("messages", []) - return {"message": {"content": "ok", "thinking": ""}} + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return {"response": "ok"} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) result = asyncio.run( llm.call_ollama("user prompt body", system_prompt=None, tag="no-system", temperature=0.1) ) assert result["content"] == "ok" - assert len(captured["messages"]) == 1 - assert captured["messages"][0]["role"] == "user" - assert captured["messages"][0]["content"] == "user prompt body" + assert captured["kwargs"]["prompt"] == "user prompt body" + assert captured["kwargs"]["raw"] is True def test_call_ollama_whitespace_system_message(monkeypatch): captured = {} - async def fake_chat(**kwargs): - captured["messages"] = kwargs.get("messages", []) - return {"message": {"content": "ok", "thinking": ""}} + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return {"response": "ok"} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) result = asyncio.run( llm.call_ollama("user prompt", system_prompt=" ", tag="whitespace-system", temperature=0.1) ) assert result["content"] == "ok" - assert len(captured["messages"]) == 1 - assert captured["messages"][0]["role"] == "user" + assert captured["kwargs"]["prompt"] == "user prompt" + assert captured["kwargs"]["raw"] is True def test_call_ollama_thinking_in_kwargs(monkeypatch): captured = {} - async def fake_chat(**kwargs): + async def fake_generate(**kwargs): captured.update(kwargs) - return {"message": {"content": "ok", "thinking": "boom"}} + return {"response": "ok", "message": {"thinking": "boom"}} # keep thinking for backward test though it might not be perfect - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) res = asyncio.run( llm.call_ollama("prompt", thinking="boom", tag="think-flag", temperature=0.7) @@ -134,10 +133,10 @@ def test_call_ollama_thinking_in_kwargs(monkeypatch): def test_call_ollama_cancelled_reraises(monkeypatch): - async def fake_chat(**kwargs): + async def fake_generate(**kwargs): raise asyncio.CancelledError - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) with pytest.raises(asyncio.CancelledError): asyncio.run( @@ -146,10 +145,10 @@ def test_call_ollama_cancelled_reraises(monkeypatch): def test_call_ollama_chat_raises_rethrows(monkeypatch): - async def fake_chat(**kwargs): + async def fake_generate(**kwargs): raise ValueError("boom") - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) with pytest.raises(ValueError): asyncio.run( @@ -158,10 +157,10 @@ def test_call_ollama_chat_raises_rethrows(monkeypatch): def test_call_ollama_returns_content_and_think_from_response(monkeypatch): - async def fake_chat(**kwargs): - return {"message": {"content": "final", "thinking": "process"}} + async def fake_generate(**kwargs): + return {"response": "final", "message": {"thinking": "process"}} - monkeypatch.setattr(llm.client, "chat", fake_chat) + monkeypatch.setattr(llm.client, "generate", fake_generate) res = asyncio.run( llm.call_ollama("prompt", system_prompt=None, tag="return", temperature=0.7) @@ -169,6 +168,51 @@ def test_call_ollama_returns_content_and_think_from_response(monkeypatch): assert res["content"] == "final" and res["think"] == "process" +def test_stream_ollama_uses_requested_model_and_yields_chunks(monkeypatch): + captured = {} + + class FakeStream: + def __init__(self, chunks): + self._chunks = iter(chunks) + + def __aiter__(self): + return self + + async def __anext__(self): + try: + return next(self._chunks) + except StopIteration: + raise StopAsyncIteration + + async def fake_generate(**kwargs): + captured["kwargs"] = kwargs + return FakeStream([ + {"response": "深度"}, + {"response": "回答"}, + ]) + + monkeypatch.setattr(llm.client, "generate", fake_generate) + + chunks = [] + + async def collect_stream(): + async for chunk in llm.stream_ollama( + "prompt", + system_prompt="system", + tag="stream", + temperature=0.8, + model="pro-model", + use_pro_model=True, + ): + chunks.append(chunk) + + asyncio.run(collect_stream()) + + assert "".join(chunks) == "深度回答" + assert captured["kwargs"]["model"] == "pro-model" + assert captured["kwargs"]["stream"] is True + + def test_call_vlm_ocr_passes_image_and_prompt(monkeypatch): image_bytes = b"image-bytes" called = {} diff --git a/backend/tests/test_main_endpoints.py b/backend/tests/test_main_endpoints.py index 031927c..75eb3a6 100644 --- a/backend/tests/test_main_endpoints.py +++ b/backend/tests/test_main_endpoints.py @@ -1,6 +1,7 @@ import os import sys import base64 +import types import pytest from unittest.mock import MagicMock from fastapi.testclient import TestClient @@ -10,6 +11,11 @@ BACKEND_DIR = os.path.abspath(os.path.join(CURRENT_DIR, "..")) if BACKEND_DIR not in sys.path: sys.path.insert(0, BACKEND_DIR) +if "tts_asr" not in sys.modules: + fake_tts_asr = types.ModuleType("tts_asr") + fake_tts_asr.register_tts_asr_routes = lambda app: None + sys.modules["tts_asr"] = fake_tts_asr + import main # type: ignore API_KEY = main.API_KEY @@ -112,6 +118,40 @@ def test_post_completions_privacy_mode(monkeypatch): assert data.get("content") == "done" +def test_post_pro_stream_returns_sse(monkeypatch): + captured = {} + + async def fake_stream(*args, **kwargs): + captured["kwargs"] = kwargs + yield "深度" + yield "回答" + + monkeypatch.setattr(main, "stream_ollama", fake_stream) + monkeypatch.setattr(main, "build_completion_prompts", lambda *a, **k: ("sys", "user")) + monkeypatch.setattr(main, "prepare_prompt_context", lambda *a, **k: ("p", "s")) + + client = TestClient(main.app) + with client.stream("POST", "/v1/pro/completions/stream", headers=HEADERS, json={ + "prefix": "hello", + "suffix": "", + "languageId": "markdown", + "model_thinking": "high", + "privacy_mode": True, + "model": "pro-model", + "temperature": 0.95, + }) as resp: + assert resp.status_code == 200 + body = "".join(resp.iter_text()) + + assert "event: chunk" in body + assert "event: done" in body + assert "深度" in body + assert "回答" in body + assert captured["kwargs"]["model"] == "pro-model" + assert captured["kwargs"]["use_pro_model"] is True + assert main.ACTIVE_COMPLETIONS == {} + + def test_post_ocr_mocked(monkeypatch): async def fake_ocr(*args, **kwargs): return "OCR result text" diff --git a/package-lock.json b/package-lock.json index af0d6f4..cdd1502 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,11 +14,6 @@ "@milkdown/kit": "^7.18.0", "@milkdown/theme-nord": "^7.18.0", "@milkdown/vue": "^7.18.0", - "@univerjs/preset-docs-core": "^0.20.0", - "@univerjs/preset-sheets-core": "^0.20.0", - "@univerjs/presets": "^0.20.0", - "@univerjs/slides": "^0.20.0", - "@univerjs/slides-ui": "^0.20.0", "docx": "^9.6.0", "docx-preview": "^0.3.7", "docx2pdf-converter": "^2.1.1", @@ -1209,12 +1204,6 @@ "node": ">=18" } }, - "node_modules/@flatten-js/interval-tree": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@flatten-js/interval-tree/-/interval-tree-1.1.3.tgz", - "integrity": "sha512-xhFWUBoHJFF77cJO1D6REjdgJEMRf2Y2Z+eKEPav8evGKcLSnj1ud5pLXQSbGuxF3VSvT1rWhMfVpXEKJLTL+A==", - "license": "MIT" - }, "node_modules/@floating-ui/core": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", @@ -1871,27 +1860,6 @@ "vue": "^3.0.0" } }, - "node_modules/@noble/ed25519": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-2.3.0.tgz", - "integrity": "sha512-M7dvXL2B92/M7dw9+gzuydL8qn/jiqNHaoR3Q+cb1q1GHV7uwE17WCyFMG+Y+TZb5izcaXk5TdJRrDUxHXL78A==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@ocavue/utils": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/@ocavue/utils/-/utils-1.6.0.tgz", @@ -1901,779 +1869,6 @@ "url": "https://github.com/sponsors/ocavue" } }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-arrow": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", - "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dismissable-layer": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-escape-keydown": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-dropdown-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", - "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-menu": "2.1.16", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-guards": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-focus-scope": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-hover-card": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.1.15.tgz", - "integrity": "sha512-qgTkjNT1CfKMoP0rcasmlH2r1DAiYicWsDsufxl940sT2wHNEWWv6FMWIQXWhVdmC1d/HYfbhQx60KYyAtKxjg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", - "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", - "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-dismissable-layer": "1.1.11", - "@radix-ui/react-focus-guards": "1.1.3", - "@radix-ui/react-focus-scope": "1.1.7", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-popper": "1.2.8", - "@radix-ui/react-portal": "1.1.9", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "aria-hidden": "^1.2.4", - "react-remove-scroll": "^2.6.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-popper": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", - "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", - "license": "MIT", - "dependencies": { - "@floating-ui/react-dom": "^2.0.0", - "@radix-ui/react-arrow": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-layout-effect": "1.1.1", - "@radix-ui/react-use-rect": "1.1.1", - "@radix-ui/react-use-size": "1.1.1", - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-portal": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", - "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-primitive": "2.1.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", - "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.4" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", - "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-escape-keydown": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-callback-ref": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", - "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", - "license": "MIT", - "dependencies": { - "@radix-ui/rect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/rect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", - "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", - "license": "MIT" - }, "node_modules/@remirror/core-constants": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", @@ -3739,2165 +2934,6 @@ "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, - "node_modules/@univerjs-pro/collaboration": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/collaboration/-/collaboration-0.20.0.tgz", - "integrity": "sha512-iCsyTAArvtvh6e4a2xIt6AvA5uHFUkh/t6mCXHR2AuBo1ZZud5jZTPnV9HkdLpf7VIV3qe+6KI7/N8MTOdemjA==", - "dependencies": { - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/thread-comment": "0.20.0", - "uuid": "^13.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/collaboration-client": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/collaboration-client/-/collaboration-client-0.20.0.tgz", - "integrity": "sha512-dcBuRj5IodiIgGm6fWKW49CSvAqNJf6SjupSJaRDDisRF546hsS4IlulFEhlRaARgIlstqoM9aRDCjN4CPlfDw==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/telemetry": "0.20.0", - "crypto-js": "4.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/collaboration-client-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/collaboration-client-ui/-/collaboration-client-ui-0.20.0.tgz", - "integrity": "sha512-7FFM/9VdeFHioxSJ6r+t67kurzLA1w57O/J71ZC9goHxCf/rC61p/iwNKCSwlnrc73DCey39TFcZ2QdC/I9g2g==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0", - "crypto-js": "4.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/collaboration/node_modules/uuid": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-13.0.0.tgz", - "integrity": "sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist-node/bin/uuid" - } - }, - "node_modules/@univerjs-pro/docs-exchange-client": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/docs-exchange-client/-/docs-exchange-client-0.20.0.tgz", - "integrity": "sha512-/Ioyox2kFk6S8kwvmgPx5vscuccoee/8mpK4As7DAMxibFGbGvfWUfIbgnFm9k7FFNrwX4c/K6o30EekxTy77A==", - "dependencies": { - "@univerjs-pro/exchange-client": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/docs-print": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/docs-print/-/docs-print-0.20.0.tgz", - "integrity": "sha512-CdcBxekBgLMJyMVq2kz523lbfxDXca/4j8oycIx8zI+RBDCRW2M+RDy2OcHaJ8Djhy9pnpCDOdD2k/FRvaJHlA==", - "dependencies": { - "@univerjs-pro/license": "0.20.0", - "@univerjs-pro/print": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/edit-history-loader": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/edit-history-loader/-/edit-history-loader-0.20.0.tgz", - "integrity": "sha512-0OvhK/z+lpSB4pvPhmzVLHEIva3tJ2y8QwsxEfTJ//V/cTPQFyLc3Iv9oLPlK/EeHurJq0eFiRskvtCfgaF5Jg==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/collaboration-client-ui": "0.20.0", - "@univerjs-pro/edit-history-viewer": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs-pro/sheets-chart": "0.20.0", - "@univerjs-pro/sheets-chart-ui": "0.20.0", - "@univerjs-pro/sheets-pivot": "0.20.0", - "@univerjs-pro/sheets-shape": "0.20.0", - "@univerjs-pro/sheets-shape-ui": "0.20.0", - "@univerjs-pro/sheets-sparkline": "0.20.0", - "@univerjs-pro/sheets-sparkline-ui": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-conditional-formatting-ui": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-data-validation-ui": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-drawing-ui": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-filter-ui": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/sheets-hyper-link-ui": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-table": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/edit-history-viewer": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/edit-history-viewer/-/edit-history-viewer-0.20.0.tgz", - "integrity": "sha512-tfZqyY3Sin0XwCHYmbza8w7bWhySUWc78RPMElzqgoQtJ5wHw8yQiKsK3kDj1piBTOkV1vzKpO116izXvqJaBQ==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/collaboration-client-ui": "0.20.0", - "@univerjs-pro/sheets-chart": "0.20.0", - "@univerjs-pro/sheets-pivot": "0.20.0", - "@univerjs-pro/sheets-shape": "0.20.0", - "@univerjs-pro/sheets-sparkline": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-table": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/engine-chart": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/engine-chart/-/engine-chart-0.20.0.tgz", - "integrity": "sha512-PmBPBYe7MdTTi53oxiKxUur08wK1kKdMlpmAfbRKHM8iQ1jKiX+jppsiWW6DG4Zc/FUWhgTSo5Mvqp+EiX/wUA==", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/engine-formula": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/engine-formula/-/engine-formula-0.20.0.tgz", - "integrity": "sha512-iHbQPkUsweym+APtkKR9Tvg6x9bkQMH1vWtEo2IBZRykG6Cg2ZH+m2mBjfGd+yar7hgsNybAvcGuST1NaIcKYA==", - "dependencies": { - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/engine-pivot": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/engine-pivot/-/engine-pivot-0.20.0.tgz", - "integrity": "sha512-6eEceMhiMKkAwujDvcSjkgg/V513TpitItvvodfYk8RzCaS12XgCBtXcG4Hw4ckyzulFB4BgHZd5FjKH0sy6yg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/engine-shape": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/engine-shape/-/engine-shape-0.20.0.tgz", - "integrity": "sha512-4fivQmgiAsc5UoAK9ZQSlRW+KtRztr1+NDDuz3WrXxmsIIcLEBRAv6aQbXOGA7TNOJitipnyIT3m3P/o2lL+6Q==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/exchange-client": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/exchange-client/-/exchange-client-0.20.0.tgz", - "integrity": "sha512-sLSd/DX7InjDy5psH9zNzCxI5eTuaH6JkEECaf7L10Prmur7ug74FDZdV5ulfB7+xOcqVqT4Yd4UKVEIZYcfMQ==", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/ui": "0.20.0", - "pako": "^2.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/license": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/license/-/license-0.20.0.tgz", - "integrity": "sha512-JoppYlkQUuxhvuWWuGrGruOZTOI+ur08GNIFika5OhcSZBz8PTuvMNhSSxIPsSzv+ndK4y0uMUt9YYmRi6np2A==", - "dependencies": { - "@noble/ed25519": "2.3.0", - "@noble/hashes": "1.8.0", - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/print": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/print/-/print-0.20.0.tgz", - "integrity": "sha512-4Yd+auUSgLQ3QpVaHF1gAzrwjAp/lQD4u3vEP6lkPLBB4/rAhQchTq7LMg4fjALUFewYDJ8eCpg+dtpl7RE9IQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/sheets-chart": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-chart/-/sheets-chart-0.20.0.tgz", - "integrity": "sha512-AXTuqzfycKh0MJ05glVlD1uYmO3zPptkzu1VuPHlhFH12xdM+R1TK6f963K7apsZLuBWe3ZMA0pyQiiql5MQvQ==", - "dependencies": { - "@univerjs-pro/engine-chart": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-chart-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-chart-ui/-/sheets-chart-ui-0.20.0.tgz", - "integrity": "sha512-MlzmLm49u4wLpghsLFmDw7mHygeLZZh95AsMmdK5mU7SXv5Tles6b1pw2X3uJilX9vobLcLXuYVPcDzSZiqCuQ==", - "dependencies": { - "@univerjs-pro/engine-chart": "0.20.0", - "@univerjs-pro/sheets-chart": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-drawing-ui": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-exchange-client": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-exchange-client/-/sheets-exchange-client-0.20.0.tgz", - "integrity": "sha512-OqsqDnAKgOTLfqZpPfWAcf5+qTSfIN1He5Jk5ItqRFYZlok8aWwbH8fhsDPbfrg1Wq9e7Y/rljkvHFblXbFP1w==", - "dependencies": { - "@univerjs-pro/exchange-client": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/sheets-pivot": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-pivot/-/sheets-pivot-0.20.0.tgz", - "integrity": "sha512-P71zkINalbuzVoYsfOgBBoG7sJeeAZO96sAAfBPzh3wuPGIc3U2sSS72ZuNc1yJOEab9WWZk6vJ6e2r8YthX3w==", - "dependencies": { - "@univerjs-pro/engine-pivot": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-filter": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-pivot-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-pivot-ui/-/sheets-pivot-ui-0.20.0.tgz", - "integrity": "sha512-MfaJFY2hl9j3aiZaQmoTS7pnA/HtjqOcUnck26fQyMnA1GBkGFtNI4GXIXFCZRNukn16pIeSHPg+x5YBLNCw5A==", - "dependencies": { - "@univerjs-pro/engine-pivot": "0.20.0", - "@univerjs-pro/sheets-pivot": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-print": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-print/-/sheets-print-0.20.0.tgz", - "integrity": "sha512-I014PqYG6a15hpLql7tPJOPP6A+Kf6BHgS4K4a5t5mk49f88mukpOxrA+vCFAl9UMa5xx9xF839mCJpc6c4eDw==", - "dependencies": { - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs-pro/print": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/network": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-shape": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-shape/-/sheets-shape-0.20.0.tgz", - "integrity": "sha512-dSrxLta1r18IkfD+iAOHgdzY34F3xXPc+k3p/HsT//GMMh9o+KNYX/L0WURwgCgg7u9oJnKHec4HQx6Od8sJ3A==", - "dependencies": { - "@univerjs-pro/engine-shape": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs-pro/sheets-shape-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-shape-ui/-/sheets-shape-ui-0.20.0.tgz", - "integrity": "sha512-rwOhfxBPdI3cuArXebDJI63/6o3iZNNatiNJBweZtLBx+x/dljc9CsLq1fitfxtnre0Tu4hS3ZvgRM9nOzHkdA==", - "dependencies": { - "@univerjs-pro/engine-shape": "0.20.0", - "@univerjs-pro/sheets-shape": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-drawing-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-sparkline": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-sparkline/-/sheets-sparkline-0.20.0.tgz", - "integrity": "sha512-A86oJHWgLWRZz86PbIH73wkt6vblWY6bFkutcrAkNpMOahveVIsfbdBZHPPoUrzF1dZKnVwvfMg66qIWVqdJcQ==", - "dependencies": { - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/sheets-sparkline-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/sheets-sparkline-ui/-/sheets-sparkline-ui-0.20.0.tgz", - "integrity": "sha512-lFEcIRkWaMdTAojNJpepavP1eutH2cKCHmMyvBsyRKxLJPiShb8qGIsQGlAfVLtJJM7ms/ZrvQZpDRrf/C1v4Q==", - "dependencies": { - "@univerjs-pro/sheets-sparkline": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-graphics": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "engines": { - "node": ">=16.0.0", - "npm": ">=8.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs-pro/thread-comment-datasource": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs-pro/thread-comment-datasource/-/thread-comment-datasource-0.20.0.tgz", - "integrity": "sha512-dWAKNE9wtKiaWArclHS4Cxd1XEEEqR09eo4Vs4yYTcQuv4xY+2Ha9sRbyc20eUWswtUO4hX+NE3b/9oalYT4ww==", - "dependencies": { - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs/core": "0.20.0", - "@univerjs/network": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/core/-/core-0.20.0.tgz", - "integrity": "sha512-sXz0qf0aWI9rEBZ24/+vUw/uMMwMQhKbBQz9YDafZF8MXl2ey1/n3GqXNBHrCKSpo25vB8rGT3sPk3PvZ7jWeQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/protocol": "0.1.48", - "@univerjs/themes": "0.20.0", - "@wendellhu/redi": "1.1.1", - "async-lock": "^1.4.1", - "dayjs": "^1.11.20", - "fast-diff": "1.3.0", - "kdbush": "^4.0.2", - "lodash-es": "^4.17.23", - "nanoid": "5.1.7", - "numfmt": "^3.2.3", - "ot-json1": "^1.0.2", - "rbush": "^4.0.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/data-validation": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/data-validation/-/data-validation-0.20.0.tgz", - "integrity": "sha512-OZjXC+nsrnmlPXSrCsYQfgJjuAmZdzPfunx1zExSKrAcsvCdJHjvEAZZhURcGbJcPG4CRJiGafcm/htjxxwFEA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/design": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/design/-/design-0.20.0.tgz", - "integrity": "sha512-wXRi5Lnafnpw1SDij3zGjqmV+gwL3qj7+LOWXB9QQ6WTl0Tk6IILz/KMyUuEyPCOTryLQruJ3FlE7++y0ocALw==", - "license": "Apache-2.0", - "dependencies": { - "@radix-ui/react-dialog": "^1.1.15", - "@radix-ui/react-dropdown-menu": "^2.1.16", - "@radix-ui/react-hover-card": "^1.1.15", - "@radix-ui/react-popover": "^1.1.15", - "@radix-ui/react-separator": "^1.1.8", - "@radix-ui/react-slot": "^1.2.4", - "@univerjs/icons": "^1.1.1", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "dayjs": "^1.11.20", - "react-transition-group": "^4.4.5", - "sonner": "^2.0.7", - "tailwind-merge": "2.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, - "node_modules/@univerjs/docs": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs/-/docs-0.20.0.tgz", - "integrity": "sha512-mLGOQEj7MUzWc4gDoH/l2phFyYjOHPCf9Jj8KSlQAOw97wvjSnXHXaRdpr7e6IM22481XVIR8kLW0j3NRmkUjQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/docs-drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-drawing/-/docs-drawing-0.20.0.tgz", - "integrity": "sha512-U1AeJFuPktZTWdT41gKMdmLfNxEBe4SvTnCsubl2nuwHxYud0RnXAywqD596MP04uDRjYknOh1vZ4i1Ba0pJcQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/drawing": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/docs-drawing-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-drawing-ui/-/docs-drawing-ui-0.20.0.tgz", - "integrity": "sha512-kjNZWW1LklzEKM1BaCWTjH8wv8cqdxSiOBcBsr+SlsOmkqEUCh7EiLSoShIzKBNrGbz/fjEBCXh76upy0+jjpw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/docs-hyper-link": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-hyper-link/-/docs-hyper-link-0.20.0.tgz", - "integrity": "sha512-YrKFbL80evv9oeIjIFPkbYJKj3i5/V3/WB/rAQd2jQqqJ8sMt+8bPEv9y0s2n3ULoLEQoKwdQZ3Pqml/CVgTPA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/docs-hyper-link-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-hyper-link-ui/-/docs-hyper-link-ui-0.20.0.tgz", - "integrity": "sha512-0jLJrssCuYZenCOFDJ1eMOgssU7HGDpjR3PByIfUca2nFSuAnLn5E/iSvYT+EZzUyB99gwiW/Tc5TS2Yj0IY+A==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-hyper-link": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/docs-thread-comment-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-thread-comment-ui/-/docs-thread-comment-ui-0.20.0.tgz", - "integrity": "sha512-xW1vqIbmfLa74anstw55wdcZ7jzuKt9wR0a/1TZiECXRt9oY6QzVFNLJ7xA1cINGFbjRIiGfeG2vtvDAafmkIA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/docs-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/docs-ui/-/docs-ui-0.20.0.tgz", - "integrity": "sha512-CAkmK9BUt6ZNXpqSRvqbXX2rJ3jQLhQM38WvrMhebpmaHxi0eFMRGOJ3AHTwUne03weLLSK59JkyAl5CXa60yA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/drawing/-/drawing-0.20.0.tgz", - "integrity": "sha512-Uto+4m8WLCp3OaTrXObNG6SoTymgS1nz5LCwUk23FBrcd2JCfRiTBZ7wXEYbf90P8KSx32XBKUDtmyRQNR5bNw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "ot-json1": "^1.0.2" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/drawing-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/drawing-ui/-/drawing-ui-0.20.0.tgz", - "integrity": "sha512-KrIGOeYYUbO5wXe7NLYg0RNW4Bi+Zwj2+xxiLjNd+KPvfmxOMnVZ0qy8T7La6ivPvXf255je4aLKNBir+RStXw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/engine-formula": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/engine-formula/-/engine-formula-0.20.0.tgz", - "integrity": "sha512-2ZCz9lNIFie1ZXMTI6UWqwHQicjv8t/ixruSdXbecqW8+zDIktoI999Sm7EFEgK5KX3bUyhWI/NcEK4OWHERLw==", - "license": "Apache-2.0", - "dependencies": { - "@flatten-js/interval-tree": "1.1.3", - "@univerjs/core": "0.20.0", - "@univerjs/rpc": "0.20.0", - "decimal.js": "^10.6.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/engine-render": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/engine-render/-/engine-render-0.20.0.tgz", - "integrity": "sha512-gIuYe41N/YIXwr6zvYn4+WIryeE37Xu0gUiopP5dbB2ls3fZX3mdBdw9VvP1DsZ/GE8Cz19OmtOePKnZbMIqjQ==", - "license": "Apache-2.0", - "dependencies": { - "@floating-ui/dom": "^1.7.4", - "@floating-ui/utils": "^0.2.10", - "@univerjs/core": "0.20.0", - "cjk-regex": "^3.4.0", - "franc-min": "^6.2.0", - "opentype.js": "^1.3.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/find-replace": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/find-replace/-/find-replace-0.20.0.tgz", - "integrity": "sha512-PeG4FwTKzVq4ybZUqXHr3APuYHxRcBC1KTdlacAwbpzIW7nrTfkXP7+8ZK64VyL5pdfVi3wMOv7Usrra8XQk3Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/icons": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@univerjs/icons/-/icons-1.1.1.tgz", - "integrity": "sha512-3agWxYwNEyfpiCerajLZvZWfa+Fsx2LhGY9EeacSiPk+32BX9NwmCa9uTpDVe0F94iEO+fGfkTB8pCeIFU4l8w==", - "license": "MIT", - "peerDependencies": { - "react": "*", - "react-dom": "*" - } - }, - "node_modules/@univerjs/network": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/network/-/network-0.20.0.tgz", - "integrity": "sha512-OKdkMmRTorIunrlijkWWkRfSHP1exy6JR5KLJDmZBG1x1xdTaSRtFb2Jo7bUQpZUP41yMnnEZDDHiV1ub+fnJg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-advanced": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-advanced/-/preset-docs-advanced-0.20.0.tgz", - "integrity": "sha512-wrfOU8/iEi2IlJ3uEUybwVtDQVE+6VqBw2lg4BlaYkUs7GyAe12ou0eBjrnElN86C3GdVQPFUhST7IEDkcZtJw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs-pro/docs-exchange-client": "0.20.0", - "@univerjs-pro/docs-print": "0.20.0", - "@univerjs-pro/exchange-client": "0.20.0", - "@univerjs-pro/license": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-collaboration": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-collaboration/-/preset-docs-collaboration-0.20.0.tgz", - "integrity": "sha512-GqqYfGmTpH1Z5q2won80aAVBG7VXMtLUa0GCSj5jmSN/Z4uOXPy432waymEz2+/4FxPWWWup/hmpBqLDKnsg0Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/collaboration-client-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-core/-/preset-docs-core-0.20.0.tgz", - "integrity": "sha512-IVEwvZE9LmoRn+nLhJPolAId/PqgyxBCPOoD5u5dySQMY0aHUSGo+s2gEolK2neqSoF/PLQZZ495rqD/JrPCiA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/network": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-drawing/-/preset-docs-drawing-0.20.0.tgz", - "integrity": "sha512-b+WTsR2Eh0L8t7cpFngUwfnapMg4NvEZ/K8m8Bmyq4WB1kkExvTUuHqcpHP3IlVLRSFYyuMAQme7JbO3rC2MnA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/docs-drawing-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-hyper-link": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-hyper-link/-/preset-docs-hyper-link-0.20.0.tgz", - "integrity": "sha512-8gA4BCqImWQ/FLzdnpc5OeHh6DKuP1FPgqyYufQ0XfT7QXDn2YGy5tgMjo6wpNxOl9ztZ4PRXyGG05tVq84ywA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs-hyper-link": "0.20.0", - "@univerjs/docs-hyper-link-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-node-core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-node-core/-/preset-docs-node-core-0.20.0.tgz", - "integrity": "sha512-unsiv8aMsr+MF0uPwH0vwuRWeNlhnm58TFTmrRrNcjyV/jloxdQHFXu72kWJsmOOxDmkTgZW3Hb218dfGAtUWA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs": "0.20.0", - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/docs-hyper-link": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/rpc-node": "0.20.0", - "@univerjs/thread-comment": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-docs-thread-comment": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-docs-thread-comment/-/preset-docs-thread-comment-0.20.0.tgz", - "integrity": "sha512-zNrH1NNMBmM8Hh1Se6kERQF+m8QcmSuwafxSLt35LZE8SWD4QiDkGLTqmXD86Q8C0q8R+Js2ubQAy38Y/S1XRw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs-thread-comment-ui": "0.20.0", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-advanced": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-advanced/-/preset-sheets-advanced-0.20.0.tgz", - "integrity": "sha512-5+spZkgX6poD425SzLvIq9zIjCCAVkNqdJNPTY0VBf2JAT673VeFfclUiPlPUSi96OC1CgSJkOrab1c0zz0R6g==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs-pro/engine-chart": "0.20.0", - "@univerjs-pro/engine-formula": "0.20.0", - "@univerjs-pro/engine-shape": "0.20.0", - "@univerjs-pro/exchange-client": "0.20.0", - "@univerjs-pro/license": "0.20.0", - "@univerjs-pro/sheets-chart": "0.20.0", - "@univerjs-pro/sheets-chart-ui": "0.20.0", - "@univerjs-pro/sheets-exchange-client": "0.20.0", - "@univerjs-pro/sheets-pivot": "0.20.0", - "@univerjs-pro/sheets-pivot-ui": "0.20.0", - "@univerjs-pro/sheets-print": "0.20.0", - "@univerjs-pro/sheets-shape": "0.20.0", - "@univerjs-pro/sheets-shape-ui": "0.20.0", - "@univerjs-pro/sheets-sparkline": "0.20.0", - "@univerjs-pro/sheets-sparkline-ui": "0.20.0", - "@univerjs/sheets-graphics": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-collaboration": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-collaboration/-/preset-sheets-collaboration-0.20.0.tgz", - "integrity": "sha512-kGtr9BFnuqUhuPb8hbb+9rtGF7c5Yxnaz+qcVLpcG1SA/Hqj83Zyp8x+JRElS4FpcJnHSLx9OD74QhQgo93vMA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs-pro/collaboration": "0.20.0", - "@univerjs-pro/collaboration-client": "0.20.0", - "@univerjs-pro/collaboration-client-ui": "0.20.0", - "@univerjs-pro/edit-history-loader": "0.20.0", - "@univerjs-pro/edit-history-viewer": "0.20.0", - "@univerjs-pro/thread-comment-datasource": "0.20.0", - "@univerjs/preset-sheets-advanced": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-conditional-formatting": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-conditional-formatting/-/preset-sheets-conditional-formatting-0.20.0.tgz", - "integrity": "sha512-vpT2KGFKRZEPHJ7mtSFNm/AaW3lyr7jvmOG9XnH8/rzhTmzE+XrN/uy8N+5SPRgl6MTg/czUlE4YaeZs4UwE/w==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-conditional-formatting-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-core/-/preset-sheets-core-0.20.0.tgz", - "integrity": "sha512-UkVhdOyswfDfkB5WyvGocVgUfAvWrvcC8JLgw1VBV04ck43g5RzXQGy2h/Fyxztl+6FaznimPH3QuPuVYjEN3Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/network": "0.20.0", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-numfmt-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-data-validation": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-data-validation/-/preset-sheets-data-validation-0.20.0.tgz", - "integrity": "sha512-qGQUnhveNgzWvk0PFS+bbv8iz0EfQGMiNUXBfCwUcH1jnsYZlPPHy+BBMxexEh5mnz2heHTDR24tbBk3oeG2yA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/data-validation": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-data-validation-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-drawing/-/preset-sheets-drawing-0.20.0.tgz", - "integrity": "sha512-4duVTQkYbvh+En+89cE3Ip00OuXhemIsglEM7TBPywUg1v3F4zNBAgzS64SWf1CLZMo1UYjouemCPOuNWalnJw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-drawing-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-filter": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-filter/-/preset-sheets-filter-0.20.0.tgz", - "integrity": "sha512-WqqxuRhiIFE2pbsf43SvIddUzt9riRFS74/Lf4UOqXrm/SYa6BtwM+cFfLnxr5fZxBRl6iOFUNBByHDclcIbxQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-filter-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-find-replace": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-find-replace/-/preset-sheets-find-replace-0.20.0.tgz", - "integrity": "sha512-pRNe7RxiijgAgyqEs2eis7UpMRm7vEWIJeA2F5ZeVvNh7Po+RWgpZR4ZBX939nampc/AtK+zD0Pt4/L1Op4X7w==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/find-replace": "0.20.0", - "@univerjs/sheets-find-replace": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-hyper-link": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-hyper-link/-/preset-sheets-hyper-link-0.20.0.tgz", - "integrity": "sha512-z2r/ffTjetkAH6H6slWoq5YJ6iivT4fEJ8w7kdoXLWUVCBaCOocfVj/oKoWsQ6A63yAKzoQcKWOBstKAY/knEQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/sheets-hyper-link-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-node-core": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-node-core/-/preset-sheets-node-core-0.20.0.tgz", - "integrity": "sha512-sJVOc7jTUAPSQgpLCBjek4DlNwblSWGrtGUS/aeprKtNIYXnykXvSpHAXm6Y56VEM1RunTev/kqXQtzVnw8trg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/docs": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/rpc-node": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-sort": "0.20.0", - "@univerjs/sheets-thread-comment": "0.20.0", - "@univerjs/thread-comment": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-note": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-note/-/preset-sheets-note-0.20.0.tgz", - "integrity": "sha512-zFNLPSoHdMQvn2Z+s5c9YTMtMagxtbas7yF/fRuA0AQZdkIzuM+OdPwV1TztWcGKhAmFfhaabDW9eTofwCBENw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-note": "0.20.0", - "@univerjs/sheets-note-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-sort": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-sort/-/preset-sheets-sort-0.20.0.tgz", - "integrity": "sha512-SmtU01v6XLYukhJdiAwJ4ubDy7fcCC7dWGcLoxtpZwqo7KegsQnICW+2ZqpvyWqgZwSFyG7s+st6Tp7NI0+3BQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-sort": "0.20.0", - "@univerjs/sheets-sort-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-table": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-table/-/preset-sheets-table-0.20.0.tgz", - "integrity": "sha512-seFq1ptOzPuu05y1JXBysGjNHzpfuAUA2v3zquEVlC1QAc7gE6xRyq7tDxlkF9UKd1RSl0ZBJHKHEeIkVuBGYw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-table": "0.20.0", - "@univerjs/sheets-table-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/preset-sheets-thread-comment": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/preset-sheets-thread-comment/-/preset-sheets-thread-comment-0.20.0.tgz", - "integrity": "sha512-dDr18hv+e00lkiSMKbeisKa4qCTkFI1b6uvGKAfpjem3Td75AqCo0aldliu9bdQGNMDFhTyz3n776rF/9+zIIA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/sheets-thread-comment": "0.20.0", - "@univerjs/sheets-thread-comment-ui": "0.20.0", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/presets": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/presets/-/presets-0.20.0.tgz", - "integrity": "sha512-nwry3E0W/rz4z+Q4QXHm+f0om+wo5xK+gd6cw/hSMF+cFzQfPwW1FLx20wxHvb1MlgIYF73cMgan93VhpJ2/dQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/preset-docs-advanced": "0.20.0", - "@univerjs/preset-docs-collaboration": "0.20.0", - "@univerjs/preset-docs-core": "0.20.0", - "@univerjs/preset-docs-drawing": "0.20.0", - "@univerjs/preset-docs-hyper-link": "0.20.0", - "@univerjs/preset-docs-node-core": "0.20.0", - "@univerjs/preset-docs-thread-comment": "0.20.0", - "@univerjs/preset-sheets-advanced": "0.20.0", - "@univerjs/preset-sheets-collaboration": "0.20.0", - "@univerjs/preset-sheets-conditional-formatting": "0.20.0", - "@univerjs/preset-sheets-core": "0.20.0", - "@univerjs/preset-sheets-data-validation": "0.20.0", - "@univerjs/preset-sheets-drawing": "0.20.0", - "@univerjs/preset-sheets-filter": "0.20.0", - "@univerjs/preset-sheets-find-replace": "0.20.0", - "@univerjs/preset-sheets-hyper-link": "0.20.0", - "@univerjs/preset-sheets-node-core": "0.20.0", - "@univerjs/preset-sheets-note": "0.20.0", - "@univerjs/preset-sheets-sort": "0.20.0", - "@univerjs/preset-sheets-table": "0.20.0", - "@univerjs/preset-sheets-thread-comment": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/protocol": { - "version": "0.1.48", - "resolved": "https://registry.npmjs.org/@univerjs/protocol/-/protocol-0.1.48.tgz", - "integrity": "sha512-nFHNtGAWOV0u1+IqoznH9K7hV/M9OZ61Vqwy8JMWKlgLLsx12m3vJqodkrVlLkI2YU5WuwjaUT1+J8/nM+kcUg==" - }, - "node_modules/@univerjs/rpc": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/rpc/-/rpc-0.20.0.tgz", - "integrity": "sha512-9sLzahk/zSlS5ajJjOMD5T/f6IeUUOiR4IZiHfLWFYRBr0EUHr+SxnnveiPwc5kWCER39Z7DGyqYCC7Z0c0NBA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/rpc-node": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/rpc-node/-/rpc-node-0.20.0.tgz", - "integrity": "sha512-ZZJkkpLg+lZbloxY00oOCwHTdgnXMJU56OMMDcbYXjhMtPTgUvlOhU7IdaYoqj9aFAe8VDoCQERo4ypwuUlImg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/rpc": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets/-/sheets-0.20.0.tgz", - "integrity": "sha512-1Z02wg0zhigqwvJ0FWDnH55jFpOo04JrTUZQckYymgLyHazLHkJZAy/+ixyVcAj83xNKPYMaj1Q+Y3iHG+V35Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/rpc": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-conditional-formatting": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-conditional-formatting/-/sheets-conditional-formatting-0.20.0.tgz", - "integrity": "sha512-BqgAi/H8iKME6G6geRItaUUEoGliafN7tJAyOAx7pjjJlfDsZ0gI9crTnttUDvqOFcaJcWZZz8fWL3HCIlQigQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-conditional-formatting-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-conditional-formatting-ui/-/sheets-conditional-formatting-ui-0.20.0.tgz", - "integrity": "sha512-fUeL8u9SspV/K+ZXxLojS5jvDbpYa53uvyytoX9sZeHm+UUTQ3k5omW4JGmzW1lTmu+VWVZPFIsPgDN+CYOOvg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-conditional-formatting": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-data-validation": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-data-validation/-/sheets-data-validation-0.20.0.tgz", - "integrity": "sha512-w3rRKLhXr3Ce9UAy/WenCmVeN3Jg6OgayNYORq0SCCppoqJbLMzjWHz0mU0T2ZPrgioalfxdbKYPGlCIxbDluA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-data-validation-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-data-validation-ui/-/sheets-data-validation-ui-0.20.0.tgz", - "integrity": "sha512-b42nbURw/CrRSQWmxL0/FkJ1eQl/FAb4IM5bwRVcBV3al1ucD/qKjKa3S/zbdeSNv/uk9ymu1syipUGJYCbikA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/data-validation": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-drawing": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-drawing/-/sheets-drawing-0.20.0.tgz", - "integrity": "sha512-/8/P+hayAL4Yp6CsPwKv3OiyD/tBbWHIE122qbU4QoOZgyscD9DVzV+K0M7940T7EcJ0xZbxZHlGzHhl54MmOA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/sheets-drawing-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-drawing-ui/-/sheets-drawing-ui-0.20.0.tgz", - "integrity": "sha512-c3vnt5cNm8ZL7UjZOyYawF82oCm9pQG1ld8g9j+NgP7hbsiJlsJRyNk0rHKfxBbsvADC96RXy7KJDL0yxJTx1Q==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs-drawing": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/drawing-ui": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-drawing": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-filter": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-filter/-/sheets-filter-0.20.0.tgz", - "integrity": "sha512-WJKOjHz/3Ol/j5mkM/h475wWzWqfY5mVo8q0y0OzO7aOCu4mjHF4xOPr+X6Q4m7CTnK7j++UMWEF6XIBPSPEGg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-filter-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-filter-ui/-/sheets-filter-ui-0.20.0.tgz", - "integrity": "sha512-hgdhdBSe2W5H08tef/2Tgr0m+VrtNtHbiCWNacJf6z6p9iZ1t1rWMI3XheijqH/MX6W6/FidLJxK3i9iC51Cvw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-filter": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-find-replace": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-find-replace/-/sheets-find-replace-0.20.0.tgz", - "integrity": "sha512-foz/7cRPvTmu5EwALfIecDAyYVMxmEdBe+iHruRc0WCcnRuSdi1l44g0/GPdzJbZalWejGw0ocHnKgYVPptgHg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/find-replace": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-formula": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-formula/-/sheets-formula-0.20.0.tgz", - "integrity": "sha512-ERF8O9itqpEcVuz5hH0HHe/UQP0o12zJptk5TgWN7QaZottcZ/mZ4iJjo1FPI1Rv/MsfZtWEFpooUjaPRqXMQw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/rpc": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-formula-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-formula-ui/-/sheets-formula-ui-0.20.0.tgz", - "integrity": "sha512-2085X4qBcMWm3DQxJDzVUHWBJotPg1aacGPg97MBIRjfk62Afc+Szj60r4n5xOMt3EwSMnipyuidzkgLqWwXag==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-graphics": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-graphics/-/sheets-graphics-0.20.0.tgz", - "integrity": "sha512-oDR/4pTQ/eTW/uKSmkppZOeXgBVN44D1pX6c7F7YSXAlTV3JU3HZ3FeGEB/fpxmjUV512q+HiCoG7VQnyYZcEA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/sheets-ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/sheets-hyper-link": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-hyper-link/-/sheets-hyper-link-0.20.0.tgz", - "integrity": "sha512-VidctE2XcmgEYxzjn0piVtlhnbJzdZSZa3Y1Jb1XnusDh2Kv6RukYma3vyact1gBlWg9QX08kaZzQhJqJ5LlVg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-hyper-link-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-hyper-link-ui/-/sheets-hyper-link-ui-0.20.0.tgz", - "integrity": "sha512-9RqnKqbQnI8F1hrkdD2IIw4zm2IpzNqROSFTbZub6puhNWBgffF5MlK2mk1tG9wiEBScBxfwVEa2X8xdYriUqA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-data-validation": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-hyper-link": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-note": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-note/-/sheets-note-0.20.0.tgz", - "integrity": "sha512-PsjUtaw/69wlKNcSUdDmZhQp1NIXMwQbwyP6fl1MyvZRlj6t9YS0wfyIt1Duy3L4e+AvPwL5E3hNdyTIyl+u6g==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-note-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-note-ui/-/sheets-note-ui-0.20.0.tgz", - "integrity": "sha512-H9ez9hpRL0tOycZDPnZF7195TmSMFi3YNrZymc/VtqLPsYOMl4tfHub4VVDQ97EWbKpgh5nLgVDD17Y29kHsbA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-note": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-numfmt": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-numfmt/-/sheets-numfmt-0.20.0.tgz", - "integrity": "sha512-3USGuBFf/IS4Ui7IpFmStcj89Jnv7N37d2dhzzYQWADS0xHn1FzHbIZUizQ/kLMMacE3758ZlhEwYnkcKWHJ7A==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-numfmt-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-numfmt-ui/-/sheets-numfmt-ui-0.20.0.tgz", - "integrity": "sha512-0ClpQx9jM2k3Yk+hDu6RXVpDQ+cXT64jis0dfCazblBnwNPVzKHk+wMYFQGT1sdJTVeD5/VzsPVgQVxhpsfloQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-numfmt": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-sort": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-sort/-/sheets-sort-0.20.0.tgz", - "integrity": "sha512-Z6eeGRhQZyNfnQ2sfdV6qTHUdlcadQNAg9An4qB2nFn5QS5uvm2OcLd+sRteVqA2TXzP6KlZxQmFV+waiENaWQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/sheets-sort-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-sort-ui/-/sheets-sort-ui-0.20.0.tgz", - "integrity": "sha512-tyB0yTpMrj6ZTV6TXTRtt0hAWcJ1if8sIMJBfGI2Jhx+2VzXjYn5d+SzvVYgiLNOXkBFtex/J+jLA876SGlEmQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-sort": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-table": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-table/-/sheets-table-0.20.0.tgz", - "integrity": "sha512-mZGVpQYRTApPC1qr2rRhrZJMG9fJ8U9M30y6r2ZEBRIwpZJLIo3Wpt2dfhDP7RsPEV42BgInMDiasxOpMbaaJA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-table-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-table-ui/-/sheets-table-ui-0.20.0.tgz", - "integrity": "sha512-nycmmfgfNdGxsYTqZmAxA93QsHcCImRm8dVFVWy+WLWle/1i3Ol9EfiS1Us9Tau8QDjvtm3iuzi8IPm/nfVluA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-formula-ui": "0.20.0", - "@univerjs/sheets-sort": "0.20.0", - "@univerjs/sheets-table": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-thread-comment": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-thread-comment/-/sheets-thread-comment-0.20.0.tgz", - "integrity": "sha512-SczsVh6O+ueNKScRRzFpslMJiTTowJWdaKgK51pYspNx47GvPwhLRVBW267jds0VTzVSlG24oC+JxKFFHgwgxw==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/sheets": "0.20.0", - "@univerjs/thread-comment": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-thread-comment-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-thread-comment-ui/-/sheets-thread-comment-ui-0.20.0.tgz", - "integrity": "sha512-aqB/UPJFxyJLd7FUAJbAFIP23FBBXwuMiHTjIC5UX9IS0J54NhJRgxoiAAxMXDf7nspUKo1/hI2oLyfvpD8zLQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/sheets": "0.20.0", - "@univerjs/sheets-thread-comment": "0.20.0", - "@univerjs/sheets-ui": "0.20.0", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/thread-comment-ui": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/sheets-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/sheets-ui/-/sheets-ui-0.20.0.tgz", - "integrity": "sha512-HdLBYjJQXq5QhAlpW1srUe1K9TuHFh4t+LqknFb1Ep2OQ6naHbb38bIXOPDktOQrmtwuWGyHNGJxbwC7nfGDoA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/engine-formula": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/protocol": "0.1.48", - "@univerjs/sheets": "0.20.0", - "@univerjs/telemetry": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/slides": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/slides/-/slides-0.20.0.tgz", - "integrity": "sha512-afm3MGu/sPLIP7XTHjWzWjTUs4OLjFCM89kjyUsEU67ZnZuhpoCuUd6sB+vDtA91ZHDs45d8YCyJn79eGXWfDA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/engine-render": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/slides-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/slides-ui/-/slides-ui-0.20.0.tgz", - "integrity": "sha512-4ffb/HvnGSwsJTHt/aRC2Jg5nJamvFGsDYeiSzLwiTAS2WxIGlNYvA7BI/H0mJoOhHS8WoLleaYznxXqTVpyIA==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/drawing": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/slides": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/telemetry": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/telemetry/-/telemetry-0.20.0.tgz", - "integrity": "sha512-zDwdeBl2vDnQwmbPSrMfNbef8fgzHVbnhSEItl5thvQQ7KIZr0sR7XeRXlI9VJGOGHg1WruJQ5zDdWeNQhP+Ig==", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/themes": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/themes/-/themes-0.20.0.tgz", - "integrity": "sha512-dJ9m3mY1uhyhvozK6m0qsnRF406cIAMBOAh4qBo7N1h4G9Ff9TnUTN/ebF1AQmwOoAcJkxmDeiD0Ordt1KLA8g==", - "license": "Apache-2.0", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - } - }, - "node_modules/@univerjs/thread-comment": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/thread-comment/-/thread-comment-0.20.0.tgz", - "integrity": "sha512-F4iARrow5esrb3uog1IPSKwb1WaZCCBTunNpgGKxhic4LClyZW3tx9oO0QdUaaQWBCNiY++J2jBs/ktTD+rdmg==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/thread-comment-ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/thread-comment-ui/-/thread-comment-ui-0.20.0.tgz", - "integrity": "sha512-wBGDtRXcHCW+7hN/bSnxtLggWVkTjKt245G0FUCcWzMQV2+H7Lnlt8zpsGE4kfILIbZHjzefuPpX4qlfZcwBSQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/docs-ui": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@univerjs/thread-comment": "0.20.0", - "@univerjs/ui": "0.20.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, - "node_modules/@univerjs/ui": { - "version": "0.20.0", - "resolved": "https://registry.npmjs.org/@univerjs/ui/-/ui-0.20.0.tgz", - "integrity": "sha512-XKxcH3hsYAOhL9wswyMglrHDRzCweUFetzwdlulaaztgwp5mKC7qNp1qgy6HUBb9OaB1yZYa8raBg7dN49DVvQ==", - "license": "Apache-2.0", - "dependencies": { - "@univerjs/core": "0.20.0", - "@univerjs/design": "0.20.0", - "@univerjs/engine-render": "0.20.0", - "@univerjs/icons": "^1.1.1", - "@wendellhu/redi": "1.1.1", - "localforage": "^1.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/univer" - }, - "peerDependencies": { - "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "rxjs": ">=7.0.0" - } - }, "node_modules/@upsetjs/venn.js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@upsetjs/venn.js/-/venn.js-2.0.0.tgz", @@ -6210,20 +3246,6 @@ "vue-component-meta": "3.2.6" } }, - "node_modules/@wendellhu/redi": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@wendellhu/redi/-/redi-1.1.1.tgz", - "integrity": "sha512-y2fuAgHJ2n8sI8Pe/1QtAuPQ6ZbZ9/Dn3uVQI8cctVqLZzp/0OpLM7DSMOU6vmGYXNsIQwsquR91WcxZ4jrRvA==", - "license": "MIT", - "peerDependencies": { - "react": ">=16.8.0" - }, - "peerDependenciesMeta": { - "react": { - "optional": true - } - } - }, "node_modules/abab": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", @@ -6465,18 +3487,6 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/arr-diff": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", @@ -6604,12 +3614,6 @@ "license": "MIT", "optional": true }, - "node_modules/async-lock": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz", - "integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==", - "license": "MIT" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -7307,19 +4311,6 @@ "node": ">=10" } }, - "node_modules/cjk-regex": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/cjk-regex/-/cjk-regex-3.4.0.tgz", - "integrity": "sha512-m+gbmlIP6gAG7tDvo2kpeSPAz/uh5wY5/zx10ymjdpbbiTHNTNoYnP2lCiyqtmbLxwhEdq8/lsVbsy4GTc9oUw==", - "license": "MIT", - "dependencies": { - "regexp-util": "^2.0.3", - "unicode-regex": "^4.2.0" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -7373,18 +4364,6 @@ "node": ">=0.10.0" } }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", - "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" - }, - "funding": { - "url": "https://polar.sh/cva" - } - }, "node_modules/cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", @@ -7421,16 +4400,6 @@ "@codemirror/view": "^6.0.0" } }, - "node_modules/collapse-white-space": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-2.1.0.tgz", - "integrity": "sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", @@ -7651,12 +4620,6 @@ "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", "license": "MIT" }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", - "license": "MIT" - }, "node_modules/css-line-break": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", @@ -8260,12 +5223,6 @@ "node": ">=0.10.0" } }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "license": "MIT" - }, "node_modules/decode-named-character-reference": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", @@ -8428,12 +5385,6 @@ "node": ">=8" } }, - "node_modules/detect-node-es": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", - "license": "MIT" - }, "node_modules/detective": { "version": "4.7.1", "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", @@ -8506,16 +5457,6 @@ "adm-zip": "^0.5.16" } }, - "node_modules/dom-helpers": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", - "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.8.7", - "csstype": "^3.0.2" - } - }, "node_modules/domexception": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", @@ -8890,12 +5831,6 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "license": "MIT" }, - "node_modules/fast-diff": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", - "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "license": "Apache-2.0" - }, "node_modules/fast-equals": { "version": "5.4.0", "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", @@ -9049,19 +5984,6 @@ "node": ">=0.10.0" } }, - "node_modules/franc-min": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/franc-min/-/franc-min-6.2.0.tgz", - "integrity": "sha512-1uDIEUSlUZgvJa2AKYR/dmJC66v/PvGQ9mWfI9nOr/kPpMFyvswK0gPXOwpYJYiYD008PpHLkGfG58SPjQJFxw==", - "license": "MIT", - "dependencies": { - "trigram-utils": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/fs-minipass": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", @@ -9201,15 +6123,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-nonce": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/get-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", @@ -10517,12 +7430,6 @@ "katex": "cli.js" } }, - "node_modules/kdbush": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", - "integrity": "sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==", - "license": "ISC" - }, "node_modules/khroma": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", @@ -10659,24 +7566,6 @@ "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", "license": "MIT" }, - "node_modules/localforage": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz", - "integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==", - "license": "Apache-2.0", - "dependencies": { - "lie": "3.1.1" - } - }, - "node_modules/localforage/node_modules/lie": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", - "integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==", - "license": "MIT", - "dependencies": { - "immediate": "~3.0.5" - } - }, "node_modules/lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", @@ -10723,24 +7612,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loose-envify/node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -11953,16 +8824,6 @@ "dev": true, "license": "MIT" }, - "node_modules/n-gram": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/n-gram/-/n-gram-2.0.2.tgz", - "integrity": "sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/nan": { "version": "2.26.2", "resolved": "https://registry.npmjs.org/nan/-/nan-2.26.2.tgz", @@ -12130,12 +8991,6 @@ "set-blocking": "^2.0.0" } }, - "node_modules/numfmt": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/numfmt/-/numfmt-3.2.6.tgz", - "integrity": "sha512-MXc2KP3j+2usdHTY5/ENUc2S+3BRF/cJqnR6RHeq6LBqKoIZOAQ62DQw974nnaZOencbfkmkTPyTmMnlkCpjzg==", - "license": "MIT" - }, "node_modules/nwsapi": { "version": "2.2.23", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", @@ -12157,6 +9012,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -12274,22 +9130,6 @@ "wrappy": "1" } }, - "node_modules/opentype.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz", - "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==", - "license": "MIT", - "dependencies": { - "string.prototype.codepointat": "^0.2.1", - "tiny-inflate": "^1.0.3" - }, - "bin": { - "ot": "bin/ot" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/optionator": { "version": "0.8.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", @@ -12337,24 +9177,6 @@ "node": ">=0.10.0" } }, - "node_modules/ot-json1": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/ot-json1/-/ot-json1-1.0.2.tgz", - "integrity": "sha512-IhxkqVWQqlkWULoi/Q2AdzKk0N5vQRbUMUwubFXFCPcY4TsOZjmp2YKrk0/z1TeiECPadWEK060sdFdQ3Grokg==", - "license": "ISC", - "dependencies": { - "ot-text-unicode": "4" - } - }, - "node_modules/ot-text-unicode": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-4.0.0.tgz", - "integrity": "sha512-W7ZLU8QXesY2wagYFv47zErXud3E93FGImmSGJsQnBzE+idcPPyo2u2KMilIrTwBh4pbCizy71qRjmmV6aDhcQ==", - "license": "ISC", - "dependencies": { - "unicount": "1.1" - } - }, "node_modules/output-file-sync": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.2.tgz", @@ -12657,17 +9479,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } - }, "node_modules/property-information": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", @@ -13128,12 +9939,6 @@ "node": ">=0.6" } }, - "node_modules/quickselect": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/quickselect/-/quickselect-3.0.0.tgz", - "integrity": "sha512-XdjUArbK4Bm5fLLvlm5KpTFOiOThgfWWI4axAZDWg4E/0mKdZyI9tNEfds27qCi1ze/vwTR16kvmmGhRra3c2g==", - "license": "ISC" - }, "node_modules/raf": { "version": "3.4.1", "resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz", @@ -13179,15 +9984,6 @@ "node": ">=0.10.0" } }, - "node_modules/rbush": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/rbush/-/rbush-4.0.1.tgz", - "integrity": "sha512-IP0UpfeWQujYC8Jg162rMNc01Rf0gWMMAb2Uxus/Q0qOFw4lCcq6ZnQEZwUoJqWyUGJ9th7JjwI4yIWo+uvoAQ==", - "license": "MIT", - "dependencies": { - "quickselect": "^3.0.0" - } - }, "node_modules/react": { "version": "19.2.4", "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", @@ -13220,97 +10016,6 @@ "react": "*" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "license": "MIT" - }, - "node_modules/react-remove-scroll": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", - "license": "MIT", - "dependencies": { - "react-remove-scroll-bar": "^2.3.7", - "react-style-singleton": "^2.2.3", - "tslib": "^2.1.0", - "use-callback-ref": "^1.3.3", - "use-sidecar": "^1.1.3" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-remove-scroll-bar": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", - "license": "MIT", - "dependencies": { - "react-style-singleton": "^2.2.2", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-style-singleton": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", - "license": "MIT", - "dependencies": { - "get-nonce": "^1.0.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/react-transition-group": { - "version": "4.4.5", - "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", - "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", - "license": "BSD-3-Clause", - "dependencies": { - "@babel/runtime": "^7.5.5", - "dom-helpers": "^5.0.1", - "loose-envify": "^1.4.0", - "prop-types": "^15.6.2" - }, - "peerDependencies": { - "react": ">=16.6.0", - "react-dom": ">=16.6.0" - } - }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -13679,15 +10384,6 @@ "node": ">=0.10.0" } }, - "node_modules/regexp-util": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/regexp-util/-/regexp-util-2.0.3.tgz", - "integrity": "sha512-GP6h9OgJmhAZpb3dbNbXTfRWVnGcoMhWRZv/HxgM4/qCVqs1P9ukQdYxaUhjWBSAs9oJ/uPXUUvGT1VMe0Bs0Q==", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, "node_modules/regexpu": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz", @@ -14281,16 +10977,6 @@ "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", "license": "BSD-3-Clause" }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -14581,16 +11267,6 @@ "node": ">= 0.4" } }, - "node_modules/sonner": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz", - "integrity": "sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==", - "license": "MIT", - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", - "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc" - } - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -14819,12 +11495,6 @@ "node": ">=8" } }, - "node_modules/string.prototype.codepointat": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==", - "license": "MIT" - }, "node_modules/stringify-entities": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", @@ -14924,16 +11594,6 @@ "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", "license": "MIT" }, - "node_modules/tailwind-merge": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", - "integrity": "sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" - } - }, "node_modules/tar": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", @@ -14982,12 +11642,6 @@ "license": "MIT", "optional": true }, - "node_modules/tiny-inflate": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", - "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", - "license": "MIT" - }, "node_modules/tinyexec": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", @@ -15112,20 +11766,6 @@ "punycode": "^2.1.0" } }, - "node_modules/trigram-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/trigram-utils/-/trigram-utils-2.0.1.tgz", - "integrity": "sha512-nfWIXHEaB+HdyslAfMxSqWKDdmqY9I32jS7GnqpdWQnLH89r6A5sdk3fDVYqGAZ0CrT8ovAFSAo6HRiWcWNIGQ==", - "license": "MIT", - "dependencies": { - "collapse-white-space": "^2.0.0", - "n-gram": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", @@ -15190,12 +11830,6 @@ "node": ">=6.10" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, "node_modules/tui-code-snippet": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tui-code-snippet/-/tui-code-snippet-2.3.3.tgz", @@ -15295,24 +11929,6 @@ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, - "node_modules/unicode-regex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/unicode-regex/-/unicode-regex-4.2.0.tgz", - "integrity": "sha512-fEYz7CCnvHDAdrb8OYAP7qlQCWzXBO5cHXQ3XI+HoZaBpiAwyC6b2nixMGl91yrDYEIRm7NDskgTvnLZ7mqrKQ==", - "license": "MIT", - "dependencies": { - "regexp-util": "^2.0.3" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/unicount": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicount/-/unicount-1.1.0.tgz", - "integrity": "sha512-RlwWt1ywVW4WErPGAVHw/rIuJ2+MxvTME0siJ6lk9zBhpDfExDbspe6SRlWT3qU6AucNjotPl9qAJRVjP7guCQ==", - "license": "ISC" - }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", @@ -15534,49 +12150,6 @@ "node": ">=0.10.0" } }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, "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", diff --git a/package.json b/package.json index d35a6b8..d4cf2b1 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,6 @@ "@milkdown/kit": "^7.18.0", "@milkdown/theme-nord": "^7.18.0", "@milkdown/vue": "^7.18.0", - "@univerjs/preset-docs-core": "^0.20.0", - "@univerjs/preset-sheets-core": "^0.20.0", - "@univerjs/presets": "^0.20.0", - "@univerjs/slides": "^0.20.0", - "@univerjs/slides-ui": "^0.20.0", "docx": "^9.6.0", "docx-preview": "^0.3.7", "docx2pdf-converter": "^2.1.1", diff --git a/src/AGENTS.md b/src/AGENTS.md index c3b83c2..a7f56f9 100644 --- a/src/AGENTS.md +++ b/src/AGENTS.md @@ -15,8 +15,8 @@ - 路由:router/index.js - 编辑页:views/EditorView.vue - 文档页:views/DocsView.vue -- Univer 页:views/UniverView.vue - 编辑器主组件:components/MilkdownEditor.vue +- 文件预览:components/FileContent.vue、components/OfficePreview.vue - 设置面板:components/SettingsPanel.vue - TTS 组件:components/TTSMenu.vue、components/TTSPlayer.vue - 插件层:plugins/ @@ -31,7 +31,6 @@ - / -> EditorView - /docs -> DocsView -- /univer -> UniverView ## 核心行为 diff --git a/src/components/ContextMenu.vue b/src/components/ContextMenu.vue index 81e58cc..8d301ad 100644 --- a/src/components/ContextMenu.vue +++ b/src/components/ContextMenu.vue @@ -1,5 +1,5 @@ diff --git a/src/components/DocBlockCrepe.vue b/src/components/DocBlockCrepe.vue index c3ecfcd..3d701c0 100644 --- a/src/components/DocBlockCrepe.vue +++ b/src/components/DocBlockCrepe.vue @@ -38,6 +38,7 @@ import { replaceAll } from '@milkdown/kit/utils' import { Crepe } from '@milkdown/crepe' import { editorViewCtx } from '@milkdown/kit/core' import { copilotPlugin, copilotConfigCtx, copilotGhostMark, setCopilotEnabled, clearGhostSuggestion } from '../plugins/copilotPlugin' +import { hiddenTextInputPlugin, hiddenTextNode, hiddenTextRemark, hiddenTextView } from '../plugins/hiddenTextPlugin' import { fetchSuggestion } from '../utils/api.js' const props = defineProps({ @@ -145,6 +146,10 @@ onMounted(async () => { crepe.editor.use(copilotConfigCtx) crepe.editor.use(copilotGhostMark) crepe.editor.use(copilotPlugin) + crepe.editor.use(hiddenTextRemark) + crepe.editor.use(hiddenTextNode) + crepe.editor.use(hiddenTextView) + crepe.editor.use(hiddenTextInputPlugin) await crepe.create() crepe.on((listener) => { diff --git a/src/components/FileContent.vue b/src/components/FileContent.vue index 816fcd6..a0d9a29 100644 --- a/src/components/FileContent.vue +++ b/src/components/FileContent.vue @@ -1,9 +1,11 @@ + + \ No newline at end of file diff --git a/src/components/MarkdownPreview.vue b/src/components/MarkdownPreview.vue index fbaa1b5..8703eba 100644 --- a/src/components/MarkdownPreview.vue +++ b/src/components/MarkdownPreview.vue @@ -7,6 +7,7 @@ import { computed } from 'vue' import MarkdownIt from 'markdown-it' import katex from 'katex' import 'katex/dist/katex.min.css' +import { hiddenTextMarkdownItPlugin } from '../utils/hiddenText.js' const props = defineProps({ content: { @@ -21,6 +22,8 @@ const md = new MarkdownIt({ typographer: true }) +md.use(hiddenTextMarkdownItPlugin) + // 预处理 markdown,转换 $...$ 为 ... const preprocessLatex = (text) => { // 处理 $$...$$ 块级公式 diff --git a/src/components/MilkdownEditor.vue b/src/components/MilkdownEditor.vue index 28dd24d..2930a3a 100644 --- a/src/components/MilkdownEditor.vue +++ b/src/components/MilkdownEditor.vue @@ -111,7 +111,7 @@ {{ aiButtonLabel }} - +
+
+ +
+
+
{{ t('presetTemplates') }}
+ +
+ +
+
{{ t('customTemplates') }}
+

{{ t('noTemplates') }}

+ +
+ + +
+
+ +
+ + + + +
+ @@ -1349,6 +1726,10 @@ for (const url of Array.from(objectUrls)) { z-index: 99999; } +.template-btn-wrapper { + position: relative; +} + .action-btn { width: 44px; height: 44px; @@ -1574,6 +1955,225 @@ for (const url of Array.from(objectUrls)) { background: var(--crepe-color-hover); } +.template-dropdown { + position: absolute; + top: calc(100% + 8px); + right: 0; + min-width: 260px; + max-width: min(360px, calc(100vw - 40px)); + background: var(--panel-bg); + border: 1px solid var(--panel-border); + border-radius: 12px; + box-shadow: var(--panel-shadow); + overflow: hidden; + z-index: 100000; + display: flex; + flex-direction: column; +} + +.template-dropdown-section { + padding: 8px 0; +} + +.template-dropdown-title { + padding: 0 16px 8px; + font-size: 11px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted-text); +} + +.template-dropdown-item { + display: flex; + width: 100%; + padding: 10px 16px; + border: none; + background: none; + text-align: left; + cursor: pointer; + font-size: 14px; + color: var(--app-text); +} + +.template-dropdown-item:hover { + background: var(--crepe-color-hover); +} + +.template-dropdown-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.template-dropdown-empty { + margin: 0; + padding: 0 16px 8px; + font-size: 12px; + color: var(--muted-text); +} + +.template-dropdown-footer { + padding: 10px 16px 14px; + border-top: 1px solid var(--panel-border); +} + +.template-dropdown-action { + width: 100%; + padding: 10px 12px; + border: 1px dashed var(--panel-border); + border-radius: 10px; + background: transparent; + color: var(--app-text); + cursor: pointer; +} + +.template-dropdown-action:hover { + background: var(--crepe-color-hover); + border-color: var(--btn-hover-bg); +} + +.template-dialog-overlay { + position: fixed; + inset: 0; + background: var(--overlay-bg); + display: flex; + align-items: center; + justify-content: center; + z-index: 100001; + padding: 20px; +} + +.template-dialog { + width: min(720px, calc(100vw - 40px)); + max-height: calc(100vh - 40px); + overflow: auto; + background: var(--panel-bg); + border: 1px solid var(--panel-border); + border-radius: 16px; + box-shadow: var(--panel-shadow); + padding: 20px; +} + +.template-dialog-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; + margin-bottom: 12px; +} + +.template-dialog-kicker { + margin: 0 0 4px; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--muted-text); +} + +.template-dialog-header h3 { + margin: 0; + font-size: 18px; + color: var(--app-text); +} + +.template-dialog-close { + width: 32px; + height: 32px; + border: 1px solid transparent; + border-radius: 8px; + background: transparent; + color: var(--muted-text); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; +} + +.template-dialog-close:hover { + background: var(--crepe-color-hover); + color: var(--app-text); +} + +.template-dialog-meta { + margin: 0 0 12px; + font-size: 13px; + color: var(--muted-text); +} + +.template-preview-textarea { + width: 100%; + min-height: 300px; + box-sizing: border-box; + padding: 14px 16px; + border: 1px solid var(--panel-border); + border-radius: 12px; + background: var(--crepe-color-background); + color: var(--app-text); + font-family: inherit; + line-height: 1.6; + resize: vertical; +} + +.template-form { + display: flex; + flex-direction: column; + gap: 10px; +} + +.template-form-label { + font-size: 13px; + color: var(--muted-text); +} + +.template-form-input, +.template-form-textarea { + width: 100%; + box-sizing: border-box; + padding: 12px 14px; + border: 1px solid var(--panel-border); + border-radius: 12px; + background: var(--crepe-color-background); + color: var(--app-text); + font-size: 14px; +} + +.template-form-textarea { + min-height: 300px; + resize: vertical; +} + +.template-form-input:focus, +.template-form-textarea:focus { + outline: none; + border-color: var(--focus-ring); +} + +.template-dialog-error { + margin: 0; + font-size: 13px; + color: var(--danger-text); +} + +.template-dialog-actions { + display: flex; + justify-content: flex-end; + gap: 8px; + margin-top: 16px; + flex-wrap: wrap; +} + +.dialog-btn.danger { + background: rgba(220, 38, 38, 0.08); + color: var(--danger-text); + border-color: rgba(220, 38, 38, 0.2); +} + +.dialog-btn.danger:hover { + background: rgba(220, 38, 38, 0.14); +} + .url-dialog-overlay { position: fixed; top: 0; @@ -1793,6 +2393,26 @@ for (const url of Array.from(objectUrls)) { background-color: var(--ghost-code-bg); } +.pro-block-accepted-highlight { + background: rgba(59, 130, 246, 0.16); + animation: pro-accept-fade 1s ease forwards; +} + +.pro-block-accepted-block { + background: linear-gradient(180deg, rgba(219, 234, 254, 0.72) 0%, rgba(239, 246, 255, 0.22) 100%); + border-radius: 14px; + animation: pro-accept-fade 1s ease forwards; +} + +@keyframes pro-accept-fade { + from { + background-color: rgba(59, 130, 246, 0.18); + } + to { + background-color: transparent; + } +} + .upload-progress-overlay { position: fixed; top: 0; diff --git a/src/components/OfficePreview.vue b/src/components/OfficePreview.vue new file mode 100644 index 0000000..0a85c1c --- /dev/null +++ b/src/components/OfficePreview.vue @@ -0,0 +1,245 @@ + + + + + \ No newline at end of file diff --git a/src/components/ProBlockCrepe.vue b/src/components/ProBlockCrepe.vue new file mode 100644 index 0000000..d9ad5a7 --- /dev/null +++ b/src/components/ProBlockCrepe.vue @@ -0,0 +1,334 @@ + + + + + \ No newline at end of file diff --git a/src/components/SettingsPanel.vue b/src/components/SettingsPanel.vue index 3829ecc..d2318e7 100644 --- a/src/components/SettingsPanel.vue +++ b/src/components/SettingsPanel.vue @@ -237,6 +237,21 @@ const switchView = (view) => { +
+

{{ t('proMode') || 'PRO 模式' }}

+ +
+ + +

{{ t('proModelDesc') }} {{ t('proModelEmptyHint') || '留空则使用后端默认 PRO 模型。' }}

+
+
+

{{ t('modelIntelligence') }}

diff --git a/src/components/UniverEditor.vue b/src/components/UniverEditor.vue deleted file mode 100644 index 505b2c8..0000000 --- a/src/components/UniverEditor.vue +++ /dev/null @@ -1,206 +0,0 @@ - - - - - diff --git a/src/components/UniverPreview.vue b/src/components/UniverPreview.vue deleted file mode 100644 index 9d6b0d0..0000000 --- a/src/components/UniverPreview.vue +++ /dev/null @@ -1,407 +0,0 @@ - - - - - diff --git a/src/components/UploadBlockCrepe.vue b/src/components/UploadBlockCrepe.vue new file mode 100644 index 0000000..367382c --- /dev/null +++ b/src/components/UploadBlockCrepe.vue @@ -0,0 +1,240 @@ + + + + + \ No newline at end of file diff --git a/src/main.js b/src/main.js index 7974ede..e9ff1ad 100644 --- a/src/main.js +++ b/src/main.js @@ -11,11 +11,3 @@ const app = createApp(App) app.use(createPinia()) app.use(router) app.mount('#app') - -if (import.meta.env.PROD && 'serviceWorker' in navigator && false) { - window.addEventListener('load', () => { - navigator.serviceWorker.register('/sw.js').catch(() => { - // Service worker registration failed, silently ignore - }) - }) -} diff --git a/src/plugins/hiddenTextPlugin.ts b/src/plugins/hiddenTextPlugin.ts new file mode 100644 index 0000000..f5adfd3 --- /dev/null +++ b/src/plugins/hiddenTextPlugin.ts @@ -0,0 +1,246 @@ +import { createApp, h, reactive } from 'vue' +import { Plugin, PluginKey } from '@milkdown/prose/state' +import { $node, $prose, $remark, $view } from '@milkdown/kit/utils' +import type { Node as ProseNode } from '@milkdown/prose/model' +import type { EditorView, NodeView } from '@milkdown/prose/view' +import HiddenTextCrepe from '../components/HiddenTextCrepe.vue' +import { HIDDEN_TEXT_NODE_TYPE, parseHiddenTextAt, serializeHiddenTextSyntax, splitTextWithHiddenSyntax } from '../utils/hiddenText.js' + +const HIDDEN_TEXT_INPUT_PLUGIN_KEY = new PluginKey('milkdown-hidden-text-input') +const HIDDEN_TEXT_INPUT_META = 'hidden-text-input-meta' + +function transformHiddenTextChildren(node: any) { + if (!node || !Array.isArray(node.children)) return + + const nextChildren: any[] = [] + + for (const child of node.children) { + if (child?.type === 'text' && typeof child.value === 'string') { + const segments = splitTextWithHiddenSyntax(child.value) + if (segments) { + nextChildren.push(...segments) + continue + } + } + + transformHiddenTextChildren(child) + nextChildren.push(child) + } + + node.children = nextChildren +} + +function findHiddenTextReplacements(doc: ProseNode) { + const replacements: Array<{ from: number; to: number; displayed: string; hidden: string; marks: ProseNode['marks'] }> = [] + + doc.descendants((node, pos, parent) => { + if (!node.isText || !node.text) return true + if (parent?.type.spec.code) return true + if (node.marks.some((mark) => mark.type.spec.code)) return true + + let index = 0 + while (index < node.text.length) { + const match = parseHiddenTextAt(node.text, index) + if (!match) { + index += 1 + continue + } + + replacements.push({ + from: pos + match.start, + to: pos + match.end, + displayed: match.displayed, + hidden: match.hidden, + marks: node.marks, + }) + + index = match.end + } + + return true + }) + + return replacements +} + +class HiddenTextNodeView implements NodeView { + node: ProseNode + view: EditorView + getPos: (() => number) | boolean + dom: HTMLElement + app: ReturnType | null = null + props: Record + + constructor(node: ProseNode, view: EditorView, getPos: (() => number) | boolean) { + this.node = node + this.view = view + this.getPos = getPos + this.dom = document.createElement('span') + this.dom.className = 'hidden-text-node-view' + this.dom.setAttribute('contenteditable', 'false') + this.props = reactive({ + displayed: node.attrs.displayed, + hidden: node.attrs.hidden, + expanded: node.attrs.expanded, + updateTexts: ({ displayed, hidden }: { displayed: string; hidden: string }) => { + this.updateAttrs({ displayed, hidden }) + }, + updateExpanded: (expanded: boolean) => this.updateAttrs({ expanded }), + }) + this.mount() + } + + mount() { + this.app = createApp({ + render: () => h(HiddenTextCrepe, this.props), + }) + this.app.mount(this.dom) + } + + getPosValue() { + if (typeof this.getPos === 'function') { + try { + const pos = this.getPos() + if (typeof pos === 'number') return pos + } catch { + // Fall back to DOM-based resolution below. + } + } + + try { + const pos = this.view.posAtDOM(this.dom, 0) + return typeof pos === 'number' ? pos : undefined + } catch { + return undefined + } + } + + updateAttrs(patch: Record) { + const pos = this.getPosValue() + if (pos === undefined) return + const nextAttrs = { ...this.node.attrs, ...patch } + const nextNode = this.node.type.create(nextAttrs, undefined, this.node.marks) + this.view.dispatch(this.view.state.tr.replaceWith(pos, pos + this.node.nodeSize, nextNode)) + } + + update(node: ProseNode) { + if (node.type !== this.node.type) return false + this.node = node + this.props.displayed = node.attrs.displayed + this.props.hidden = node.attrs.hidden + this.props.expanded = node.attrs.expanded + return true + } + + stopEvent(event: Event) { + const target = event.target as Node | null + return Boolean(target && this.dom.contains(target)) + } + + ignoreMutation() { + return true + } + + destroy() { + this.app?.unmount() + this.app = null + } +} + +export const hiddenTextRemark = $remark('hiddenTextRemark', () => () => { + return (tree: any) => { + transformHiddenTextChildren(tree) + } +}) + +export const hiddenTextNode = $node(HIDDEN_TEXT_NODE_TYPE, () => ({ + group: 'inline', + inline: true, + atom: true, + selectable: true, + draggable: false, + attrs: { + displayed: { default: '' }, + hidden: { default: '' }, + expanded: { default: false }, + }, + parseDOM: [ + { + tag: 'span[data-hidden-text="true"]', + getAttrs: (dom) => ({ + displayed: (dom as HTMLElement).getAttribute('data-hidden-display') || '', + hidden: (dom as HTMLElement).getAttribute('data-hidden-value') || '', + expanded: ((dom as HTMLElement).getAttribute('data-hidden-expanded') || '') === 'true', + }), + }, + ], + toDOM: (node) => [ + 'span', + { + 'data-hidden-text': 'true', + 'data-hidden-display': node.attrs.displayed, + 'data-hidden-value': node.attrs.hidden, + 'data-hidden-expanded': String(Boolean(node.attrs.expanded)), + }, + ], + parseMarkdown: { + match: (node) => node.type === HIDDEN_TEXT_NODE_TYPE, + runner: (state, node, type) => { + state.addNode(type, { + displayed: String(node.displayed || ''), + hidden: String(node.hidden || ''), + expanded: false, + }) + }, + }, + toMarkdown: { + match: (node) => node.type.name === HIDDEN_TEXT_NODE_TYPE, + runner: (state, node) => { + state.addNode('text', undefined, serializeHiddenTextSyntax(node.attrs.displayed, node.attrs.hidden)) + }, + }, + leafText: (node) => serializeHiddenTextSyntax(node.attrs.displayed, node.attrs.hidden), +})) + +export const hiddenTextView = $view(hiddenTextNode, () => { + return (node, view, getPos) => new HiddenTextNodeView(node, view, getPos) +}) + +export const hiddenTextInputPlugin = $prose(() => { + return new Plugin({ + key: HIDDEN_TEXT_INPUT_PLUGIN_KEY, + appendTransaction: (transactions, _oldState, newState) => { + if (!transactions.some((transaction) => transaction.docChanged)) return null + if (transactions.some((transaction) => transaction.getMeta(HIDDEN_TEXT_INPUT_META))) return null + + const hiddenTextType = newState.schema.nodes[HIDDEN_TEXT_NODE_TYPE] + if (!hiddenTextType) return null + + const replacements = findHiddenTextReplacements(newState.doc) + if (replacements.length === 0) return null + + let tr = newState.tr + + for (let index = replacements.length - 1; index >= 0; index -= 1) { + const replacement = replacements[index] + tr = tr.replaceWith( + replacement.from, + replacement.to, + hiddenTextType.create( + { + displayed: replacement.displayed, + hidden: replacement.hidden, + expanded: false, + }, + undefined, + replacement.marks + ) + ) + } + + if (!tr.docChanged) return null + tr.setMeta(HIDDEN_TEXT_INPUT_META, true) + return tr + }, + }) +}) \ No newline at end of file diff --git a/src/plugins/mermaidPlugin.ts b/src/plugins/mermaidPlugin.ts index 5a46679..61fd3e5 100644 --- a/src/plugins/mermaidPlugin.ts +++ b/src/plugins/mermaidPlugin.ts @@ -373,8 +373,8 @@ async function renderMermaidBlock(block: HTMLElement, token: number): Promise { const block = document.querySelector(targetSelector) @@ -382,13 +382,9 @@ function scheduleMermaidRender(token: number) { void renderMermaidBlock(block, token) return } - if (attempt >= maxAttempts) return - - if (typeof window.requestAnimationFrame === 'function') { - window.requestAnimationFrame(() => run(attempt + 1)) - } else { - window.setTimeout(() => run(attempt + 1), 16) - } + const nextDelay = retryDelays[attempt] + if (nextDelay === undefined) return + window.setTimeout(() => run(attempt + 1), nextDelay) } run(0) diff --git a/src/plugins/proBlockPlugin.ts b/src/plugins/proBlockPlugin.ts new file mode 100644 index 0000000..1512887 --- /dev/null +++ b/src/plugins/proBlockPlugin.ts @@ -0,0 +1,665 @@ +import { createApp, h, reactive } from 'vue' +import { parserCtx, serializerCtx } from '@milkdown/kit/core' +import { $ctx, $node, $prose, $remark, $view } from '@milkdown/kit/utils' +import { Plugin, PluginKey, Selection } from '@milkdown/prose/state' +import { type Node as ProseNode, Slice, type Schema } from '@milkdown/prose/model' +import { Decoration, DecorationSet, type EditorView, type NodeView } from '@milkdown/prose/view' +import ProBlockCrepe from '../components/ProBlockCrepe.vue' +import { extractDocBlockContextFromMarkdown } from '../utils/docBlock.js' +import { extractTextFromOCR, getOcrCache } from '../utils/ocrCache' +import { PRO_BLOCK_NODE_TYPE, PRO_DISPLAY_LABEL, PRO_TRIGGER_TEXT, parseProBlockSyntax, serializeProBlockSyntax } from '../utils/proBlock.js' + +const PRO_BLOCK_INPUT_PLUGIN_KEY = new PluginKey('milkdown-pro-block-input') +const PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY = new PluginKey('milkdown-pro-block-highlight') +const PRO_BLOCK_INPUT_META = 'pro-block-input-meta' +const PRO_CONTEXT_LIMIT = 32 * 1024 +const FALLBACK_BLOCK_SEPARATOR = '\n\n' +const FALLBACK_LEAF_TEXT = '\n' +const IMAGE_NODE_TYPES = new Set(['image', 'image-block', 'imageBlock']) + +interface ProBlockConfig { + fetchSuggestionStream: (payload: { + prefix: string + suffix: string + languageId: string + signal?: AbortSignal + model?: string + temperature?: number + onChunk?: (chunk: string) => void + }) => Promise + getProModel: () => string + showError: (message: string) => void + isThinkingActive: () => boolean + setThinkingActive: (value: boolean) => void +} + +interface ProRequestPayload { + prefix: string + suffix: string + languageId: string + blocked: boolean +} + +function serializeRangeToMarkdown( + doc: ProseNode, + from: number, + to: number, + schema: Schema, + serializer: (content: ProseNode) => string +): string { + if (from >= to) return '' + const fallback = doc.textBetween(from, to, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT) + if (typeof serializer !== 'function') return fallback + + const slice = doc.slice(from, to) + if (slice.content.size <= 0) return '' + + try { + const sliceDoc = schema.topNodeType.createAndFill(undefined, slice.content) + return sliceDoc ? serializer(sliceDoc) : fallback + } catch { + return fallback + } +} + +function transformProBlockChildren(node: any) { + if (!node || !Array.isArray(node.children)) return + + node.children = node.children.map((child: any) => { + const replacement = parseProParagraphNode(child) + if (replacement) return replacement + transformProBlockChildren(child) + return child + }) +} + +function parseProParagraphNode(node: any) { + if (!node || node.type !== 'paragraph' || !Array.isArray(node.children)) return null + if (node.children.length !== 1) return null + + const textNode = node.children[0] + if (!textNode || textNode.type !== 'text' || typeof textNode.value !== 'string') return null + + const parsed = parseProBlockSyntax(textNode.value) + if (!parsed) return null + + return { + type: PRO_BLOCK_NODE_TYPE, + content: parsed.content, + autoStart: false, + } +} + +function findProBlockReplacements(doc: ProseNode) { + const replacements: Array<{ from: number; to: number; content: string; autoStart: boolean }> = [] + + doc.descendants((node, pos) => { + if (node.type.name !== 'paragraph' || node.childCount !== 1) return true + + const firstChild = node.firstChild + if (!firstChild?.isText) return true + + const parsed = parseProBlockSyntax(node.textContent) + if (!parsed) return true + + replacements.push({ + from: pos, + to: pos + node.nodeSize, + content: parsed.content, + autoStart: parsed.autoStart, + }) + + return false + }) + + return replacements +} + +function buildOcrContext(doc: ProseNode) { + const lines: string[] = [] + + doc.descendants((node) => { + if (!IMAGE_NODE_TYPES.has(node.type.name)) return true + const src = typeof node.attrs?.src === 'string' ? node.attrs.src : '' + if (!src) return true + const ocrText = getOcrCache(src) + if (!ocrText) return true + + const preview = extractTextFromOCR(ocrText, 120) + if (!preview) return true + + const label = typeof node.attrs?.alt === 'string' && node.attrs.alt.trim() ? node.attrs.alt.trim() : 'image' + lines.push(`![${label}](${src}) `) + return true + }) + + return lines.join('\n') +} + +function createHighlightDecorations(doc: ProseNode, from: number, to: number) { + if (to <= from) return DecorationSet.empty + + const decorations = [ + Decoration.inline(from, to, { class: 'pro-block-accepted-highlight' }), + ] + + doc.nodesBetween(from, to, (node, pos) => { + if (!node.isBlock) return true + const nodeFrom = pos + const nodeTo = pos + node.nodeSize + if (nodeFrom >= from && nodeTo <= to) { + decorations.push(Decoration.node(nodeFrom, nodeTo, { class: 'pro-block-accepted-block' })) + } + return true + }) + + return DecorationSet.create(doc, decorations) +} + +function replaceWithParsedMarkdownSlice(tr: any, from: number, to: number, parsedDoc: ProseNode) { + if (!parsedDoc || parsedDoc.content.size <= 0) return null + + const parsedSlice = Slice.maxOpen(parsedDoc.content) + if (!parsedSlice || parsedSlice.size <= 0) return null + + tr.replaceRange(from, to, parsedSlice) + const startPos = tr.mapping.map(from, -1) + const endPos = tr.mapping.map(to, 1) + if (endPos <= startPos) return null + return { from: startPos, to: endPos } +} + +function insertProBlockNode(view: EditorView, autoStart: boolean) { + const proBlockType = view.state.schema.nodes[PRO_BLOCK_NODE_TYPE] + if (!proBlockType) return false + + const { from, to } = view.state.selection + const blockNode = proBlockType.create({ content: '', autoStart }) + const tr = view.state.tr.replaceRangeWith(from, to, blockNode) + const nextPos = Math.min(from + blockNode.nodeSize, tr.doc.content.size) + tr.setSelection(Selection.near(tr.doc.resolve(nextPos), 1)) + view.dispatch(tr.scrollIntoView()) + view.focus() + return true +} + +function replaceParagraphWithProBlock( + view: EditorView, + from: number, + to: number, + content: string, + autoStart: boolean +) { + const proBlockType = view.state.schema.nodes[PRO_BLOCK_NODE_TYPE] + if (!proBlockType) return false + + const blockNode = proBlockType.create({ + content, + autoStart: autoStart && content === '', + }) + const tr = view.state.tr.replaceWith(from, to, blockNode) + const nextPos = Math.min(from + blockNode.nodeSize, tr.doc.content.size) + tr.setMeta(PRO_BLOCK_INPUT_META, true) + tr.setSelection(Selection.near(tr.doc.resolve(nextPos), 1)) + view.dispatch(tr.scrollIntoView()) + view.focus() + return true +} + +function tryHandleProTriggerTextInput(view: EditorView, insertedText: string) { + if (!insertedText || !insertedText.includes(']')) return false + + const { state } = view + const { $from, from, to } = state.selection + const paragraph = $from.parent + if (!paragraph || paragraph.type.name !== 'paragraph') return false + if (!$from.sameParent(state.selection.$to)) return false + + const paragraphDepth = $from.depth + const paragraphStart = $from.start(paragraphDepth) + const startOffset = from - paragraphStart + const endOffset = to - paragraphStart + const nextText = `${paragraph.textContent.slice(0, startOffset)}${insertedText}${paragraph.textContent.slice(endOffset)}` + const parsed = parseProBlockSyntax(nextText) + if (!parsed?.autoStart || parsed.content) return false + + const blockFrom = $from.before(paragraphDepth) + const blockTo = blockFrom + paragraph.nodeSize + return replaceParagraphWithProBlock(view, blockFrom, blockTo, parsed.content, parsed.autoStart) +} + +function isAbortError(error: unknown) { + return Boolean(error && typeof error === 'object' && 'name' in error && (error as { name?: string }).name === 'AbortError') +} + +export const proBlockConfigCtx = $ctx({ + fetchSuggestionStream: async () => '', + getProModel: () => '', + showError: () => {}, + isThinkingActive: () => false, + setThinkingActive: () => {}, +}, 'proBlockConfig') + +class ProBlockNodeView implements NodeView { + node: ProseNode + view: EditorView + getPos: (() => number) | boolean + dom: HTMLElement + app: ReturnType | null = null + props: Record + parser: (markdown: string) => Promise + serializer: (content: ProseNode) => string + config: ProBlockConfig + abortController: AbortController | null = null + requestSeq = 0 + redoCount = 0 + destroyed = false + highlightTimer: ReturnType | null = null + + constructor( + node: ProseNode, + view: EditorView, + getPos: (() => number) | boolean, + parser: (markdown: string) => Promise, + serializer: (content: ProseNode) => string, + config: ProBlockConfig + ) { + this.node = node + this.view = view + this.getPos = getPos + this.parser = parser + this.serializer = serializer + this.config = config + this.dom = document.createElement('div') + this.dom.className = 'pro-block-node-view' + this.props = reactive({ + stage: node.attrs.content ? 'done' : 'idle', + previewContent: '', + savedContent: node.attrs.content || '', + isBusy: false, + title: PRO_DISPLAY_LABEL, + activateAction: () => { + void this.startThinking(false) + }, + discardAction: () => { + this.discardResult() + }, + redoAction: () => { + void this.startThinking(true) + }, + acceptAction: () => { + void this.acceptResult() + }, + }) + this.mount() + queueMicrotask(() => { + if (!this.destroyed) { + this.consumeAutoStart() + } + }) + } + + mount() { + this.app = createApp({ + render: () => h(ProBlockCrepe, { ...this.props }), + }) + this.app.mount(this.dom) + } + + getPosValue() { + if (typeof this.getPos === 'function') { + try { + const pos = this.getPos() + if (typeof pos === 'number') return pos + } catch { + // Ignore and fall back to DOM lookup. + } + } + + try { + const pos = this.view.posAtDOM(this.dom, 0) + return typeof pos === 'number' ? pos : undefined + } catch { + return undefined + } + } + + updateAttrs(patch: Record) { + const pos = this.getPosValue() + if (pos === undefined) return + const nextAttrs = { ...this.node.attrs, ...patch } + this.view.dispatch(this.view.state.tr.setNodeMarkup(pos, undefined, nextAttrs)) + } + + setStage(stage: 'idle' | 'thinking' | 'streaming' | 'done', previewContent = '') { + this.props.stage = stage + this.props.previewContent = previewContent + this.props.isBusy = stage === 'thinking' || stage === 'streaming' + } + + buildRequestPayload(): ProRequestPayload { + const pos = this.getPosValue() + if (pos === undefined) { + return { + prefix: '', + suffix: '', + languageId: 'markdown', + blocked: true, + } + } + + const doc = this.view.state.doc + const schema = this.view.state.schema + const prefixMarkdown = serializeRangeToMarkdown(doc, 0, pos, schema, this.serializer) + || doc.textBetween(0, pos, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT) + const suffixStart = Math.min(pos + this.node.nodeSize, doc.content.size) + const suffixMarkdown = serializeRangeToMarkdown(doc, suffixStart, doc.content.size, schema, this.serializer) + || doc.textBetween(suffixStart, doc.content.size, FALLBACK_BLOCK_SEPARATOR, FALLBACK_LEAF_TEXT) + + const ocrContext = buildOcrContext(doc) + const docContext = extractDocBlockContextFromMarkdown(`${prefixMarkdown}\n\n${suffixMarkdown}`, 1600) + const fullPrefix = [ocrContext, docContext, prefixMarkdown].filter(Boolean).join('\n\n') + + return { + prefix: fullPrefix, + suffix: suffixMarkdown, + languageId: 'markdown', + blocked: fullPrefix.length + suffixMarkdown.length > PRO_CONTEXT_LIMIT, + } + } + + consumeAutoStart() { + if (!this.node.attrs.autoStart) return + this.updateAttrs({ autoStart: false }) + void this.startThinking(false) + } + + stopThinkingLock() { + this.config.setThinkingActive(false) + } + + abortActive(reason = 'abort') { + if (!this.abortController) return + this.abortController.abort(reason) + this.abortController = null + this.stopThinkingLock() + } + + discardResult() { + this.abortActive('discard') + this.redoCount = 0 + this.props.savedContent = '' + this.setStage('idle', '') + this.updateAttrs({ content: '', autoStart: false }) + } + + async startThinking(isRedo: boolean) { + if (this.props.isBusy) return + const previousContent = this.node.attrs.content || '' + let requestSeq = this.requestSeq + let requestStarted = false + + try { + if (this.config.isThinkingActive()) { + this.config.showError('当前已有一个 PRO 思考正在进行,请先等待它完成。') + return + } + + const payload = this.buildRequestPayload() + if (payload.blocked) { + this.config.showError('PRO 模式上下文超过 32KB,无法继续思考。') + return + } + + requestSeq = this.requestSeq + 1 + this.requestSeq = requestSeq + this.abortController = new AbortController() + this.config.setThinkingActive(true) + requestStarted = true + this.setStage('thinking', '') + + const temperature = Math.min(1.2, 0.7 + (isRedo ? (this.redoCount + 1) * 0.2 : 0)) + + const result = await this.config.fetchSuggestionStream({ + ...payload, + signal: this.abortController.signal, + model: this.config.getProModel(), + temperature, + onChunk: (chunk) => { + if (this.destroyed || this.requestSeq !== requestSeq) return + const nextContent = `${this.props.previewContent || ''}${chunk}` + this.setStage('streaming', nextContent) + }, + }) + + if (this.destroyed || this.requestSeq !== requestSeq) return + + const content = String(result || this.props.previewContent || '').trim() + this.redoCount = isRedo ? this.redoCount + 1 : 0 + this.props.savedContent = content + this.setStage(content ? 'done' : 'idle', '') + this.updateAttrs({ content, autoStart: false }) + } catch (error) { + if (!isAbortError(error)) { + this.config.showError(error instanceof Error ? error.message : 'PRO 模式思考失败') + } + + this.props.savedContent = previousContent + this.setStage(previousContent ? 'done' : 'idle', '') + } finally { + if (requestStarted && this.requestSeq === requestSeq) { + this.abortController = null + this.stopThinkingLock() + } + } + } + + async acceptResult() { + if (this.props.isBusy) return + + const source = String(this.props.savedContent || this.node.attrs.content || '').trim() + if (!source) return + + const pos = this.getPosValue() + if (pos === undefined) return + + const from = pos + const to = pos + this.node.nodeSize + const tr = this.view.state.tr + let insertedRange: { from: number; to: number } | null = null + + try { + const parsedDoc = await this.parser(source) + insertedRange = replaceWithParsedMarkdownSlice(tr, from, to, parsedDoc) + } catch { + insertedRange = null + } + + if (!insertedRange) { + tr.insertText(source, from, to) + insertedRange = { from, to: from + source.length } + } + + const endPos = Math.min(insertedRange.to, tr.doc.content.size) + tr.setSelection(Selection.near(tr.doc.resolve(endPos), 1)) + tr.setMeta(PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY, { set: insertedRange }) + this.view.dispatch(tr.scrollIntoView()) + this.view.focus() + + if (this.highlightTimer) { + clearTimeout(this.highlightTimer) + } + this.highlightTimer = setTimeout(() => { + if (this.destroyed) return + this.view.dispatch(this.view.state.tr.setMeta(PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY, { clear: true })) + }, 1000) + } + + update(node: ProseNode) { + if (node.type !== this.node.type) return false + this.node = node + + if (!this.props.isBusy) { + this.props.savedContent = node.attrs.content || '' + this.props.previewContent = '' + this.props.stage = node.attrs.content ? 'done' : 'idle' + } + + return true + } + + stopEvent(event: Event) { + const target = event.target as Node | null + return Boolean(target && this.dom.contains(target)) + } + + ignoreMutation() { + return true + } + + destroy() { + this.destroyed = true + this.abortActive('destroy') + if (this.highlightTimer) { + clearTimeout(this.highlightTimer) + this.highlightTimer = null + } + this.app?.unmount() + this.app = null + } +} + +export const proBlockRemark = $remark('proBlockRemark', () => () => { + return (tree: any) => { + transformProBlockChildren(tree) + } +}) + +export const proBlockNode = $node(PRO_BLOCK_NODE_TYPE, () => ({ + group: 'block', + atom: true, + isolating: true, + selectable: true, + draggable: false, + marks: '', + attrs: { + content: { default: '' }, + autoStart: { default: false }, + }, + parseDOM: [ + { + tag: 'div[data-pro-block="true"]', + getAttrs: (dom) => ({ + content: (dom as HTMLElement).getAttribute('data-pro-content') || '', + autoStart: ((dom as HTMLElement).getAttribute('data-pro-autostart') || '') === 'true', + }), + }, + ], + toDOM: (node) => [ + 'div', + { + 'data-pro-block': 'true', + 'data-pro-content': node.attrs.content || '', + 'data-pro-autostart': String(Boolean(node.attrs.autoStart)), + }, + ], + parseMarkdown: { + match: (node) => node.type === PRO_BLOCK_NODE_TYPE, + runner: (state, node, type) => { + state.addNode(type, { + content: String(node.content || ''), + autoStart: false, + }) + }, + }, + toMarkdown: { + match: (node) => node.type.name === PRO_BLOCK_NODE_TYPE, + runner: (state, node) => { + state.addNode('paragraph', [{ + type: 'text', + value: serializeProBlockSyntax(node.attrs.content), + }]) + }, + }, + leafText: (node) => serializeProBlockSyntax(node.attrs.content), +})) + +export const proBlockView = $view(proBlockNode, (ctx) => { + const parser = ctx.get(parserCtx) + const serializer = ctx.get(serializerCtx) + const config = ctx.get(proBlockConfigCtx.key) + return (node, view, getPos) => new ProBlockNodeView(node, view, getPos, parser, serializer, config) +}) + +export const proBlockInputPlugin = $prose(() => { + return new Plugin({ + key: PRO_BLOCK_INPUT_PLUGIN_KEY, + props: { + handleTextInput: (view, _from, _to, text) => { + return tryHandleProTriggerTextInput(view, text) + }, + handleKeyDown: (view, event) => { + const pressed = (event.ctrlKey || event.metaKey) && event.shiftKey && event.key.toLowerCase() === 'p' + if (!pressed) return false + event.preventDefault() + return insertProBlockNode(view, false) + }, + }, + appendTransaction: (transactions, _oldState, newState) => { + if (!transactions.some((transaction) => transaction.docChanged)) return null + if (transactions.some((transaction) => transaction.getMeta(PRO_BLOCK_INPUT_META))) return null + + const proBlockType = newState.schema.nodes[PRO_BLOCK_NODE_TYPE] + if (!proBlockType) return null + + const replacements = findProBlockReplacements(newState.doc) + if (replacements.length === 0) return null + + let tr = newState.tr + + for (let index = replacements.length - 1; index >= 0; index -= 1) { + const replacement = replacements[index] + tr = tr.replaceWith( + replacement.from, + replacement.to, + proBlockType.create({ + content: replacement.content, + autoStart: replacement.autoStart && replacement.content === '', + }) + ) + } + + if (!tr.docChanged) return null + tr.setMeta(PRO_BLOCK_INPUT_META, true) + return tr + }, + }) +}) + +export const proBlockHighlightPlugin = $prose(() => { + return new Plugin({ + key: PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY, + state: { + init: () => DecorationSet.empty, + apply: (tr, value) => { + const meta = tr.getMeta(PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY) + if (meta?.clear) { + return DecorationSet.empty + } + if (meta?.set) { + return createHighlightDecorations(tr.doc, meta.set.from, meta.set.to) + } + return value.map(tr.mapping, tr.doc) + }, + }, + props: { + decorations: (state) => PRO_BLOCK_HIGHLIGHT_PLUGIN_KEY.getState(state), + }, + }) +}) + +export function insertProBlockAtSelection(view: EditorView, autoStart = false) { + return insertProBlockNode(view, autoStart) +} + +export { PRO_TRIGGER_TEXT } \ No newline at end of file diff --git a/src/plugins/uploadBlockPlugin.ts b/src/plugins/uploadBlockPlugin.ts new file mode 100644 index 0000000..cdb8410 --- /dev/null +++ b/src/plugins/uploadBlockPlugin.ts @@ -0,0 +1,263 @@ +import { createApp, h, reactive } from 'vue' +import { Plugin, PluginKey } from '@milkdown/prose/state' +import { $ctx, $node, $prose, $remark, $view } from '@milkdown/kit/utils' +import type { Node as ProseNode } from '@milkdown/prose/model' +import type { EditorView, NodeView } from '@milkdown/prose/view' +import UploadBlockCrepe from '../components/UploadBlockCrepe.vue' +import { + DEFAULT_UPLOAD_BLOCK_TYPES, + UPLOAD_BLOCK_NODE_TYPE, + getUploadBlockAccept, + getUploadBlockMenuOptions, + normalizeUploadBlockTypes, + parseUploadBlockSyntax, + serializeUploadBlockSyntax, +} from '../utils/uploadBlock.js' + +const UPLOAD_BLOCK_INPUT_PLUGIN_KEY = new PluginKey('milkdown-upload-block-input') +const UPLOAD_BLOCK_INPUT_META = 'upload-block-input-meta' + +interface UploadBlockConfig { + requestUpload: (payload: { file: File; allowedTypes: string[]; from: number; to: number }) => Promise +} + +export const uploadBlockConfigCtx = $ctx({ + requestUpload: async () => {}, +}, 'uploadBlockConfig') + +function transformUploadBlockChildren(node: any) { + if (!node || !Array.isArray(node.children)) return + + node.children = node.children.map((child: any) => { + const replacement = parseUploadParagraphNode(child) + if (replacement) return replacement + transformUploadBlockChildren(child) + return child + }) +} + +function parseUploadParagraphNode(node: any) { + if (!node || node.type !== 'paragraph' || !Array.isArray(node.children)) return null + if (node.children.length !== 1) return null + + const textNode = node.children[0] + if (!textNode || textNode.type !== 'text' || typeof textNode.value !== 'string') return null + + const parsed = parseUploadBlockSyntax(textNode.value) + if (!parsed) return null + + return { + type: UPLOAD_BLOCK_NODE_TYPE, + allowedTypes: parsed.allowedTypes, + } +} + +function findUploadBlockReplacements(doc: ProseNode) { + const replacements: Array<{ from: number; to: number; allowedTypes: string[] }> = [] + + doc.descendants((node, pos) => { + if (node.type.name !== 'paragraph' || node.childCount !== 1) return true + + const firstChild = node.firstChild + if (!firstChild?.isText) return true + + const parsed = parseUploadBlockSyntax(node.textContent) + if (!parsed) return true + + replacements.push({ + from: pos, + to: pos + node.nodeSize, + allowedTypes: parsed.allowedTypes, + }) + + return false + }) + + return replacements +} + +class UploadBlockNodeView implements NodeView { + node: ProseNode + view: EditorView + getPos: (() => number) | boolean + dom: HTMLElement + app: ReturnType | null = null + props: Record + requestUpload: UploadBlockConfig['requestUpload'] + + constructor(node: ProseNode, view: EditorView, getPos: (() => number) | boolean, requestUpload: UploadBlockConfig['requestUpload']) { + this.node = node + this.view = view + this.getPos = getPos + this.requestUpload = requestUpload + this.dom = document.createElement('div') + this.dom.className = 'upload-block-node-view' + + const allowedTypes = normalizeUploadBlockTypes(node.attrs.allowedTypes) + + this.props = reactive({ + allowedTypes, + accept: getUploadBlockAccept(allowedTypes), + menuOptions: getUploadBlockMenuOptions(allowedTypes), + onUploadRequest: (file: File) => this.handleUploadRequest(file), + }) + + this.mount() + } + + mount() { + this.app = createApp({ + render: () => h(UploadBlockCrepe, this.props), + }) + this.app.mount(this.dom) + } + + getPosValue() { + if (typeof this.getPos === 'function') { + try { + const pos = this.getPos() + if (typeof pos === 'number') return pos + } catch { + // Ignore and fall back to DOM lookup. + } + } + + try { + const pos = this.view.posAtDOM(this.dom, 0) + return typeof pos === 'number' ? pos : undefined + } catch { + return undefined + } + } + + async handleUploadRequest(file: File) { + const pos = this.getPosValue() + if (pos === undefined || !file) return + + await this.requestUpload({ + file, + allowedTypes: normalizeUploadBlockTypes(this.node.attrs.allowedTypes), + from: pos, + to: pos + this.node.nodeSize, + }) + } + + update(node: ProseNode) { + if (node.type !== this.node.type) return false + this.node = node + + const allowedTypes = normalizeUploadBlockTypes(node.attrs.allowedTypes) + this.props.allowedTypes = allowedTypes + this.props.accept = getUploadBlockAccept(allowedTypes) + this.props.menuOptions = getUploadBlockMenuOptions(allowedTypes) + return true + } + + stopEvent(event: Event) { + const target = event.target as Node | null + return Boolean(target && this.dom.contains(target)) + } + + ignoreMutation() { + return true + } + + destroy() { + this.app?.unmount() + this.app = null + } +} + +export const uploadBlockRemark = $remark('uploadBlockRemark', () => () => { + return (tree: any) => { + transformUploadBlockChildren(tree) + } +}) + +export const uploadBlockNode = $node(UPLOAD_BLOCK_NODE_TYPE, () => ({ + group: 'block', + atom: true, + isolating: true, + selectable: true, + draggable: false, + marks: '', + attrs: { + allowedTypes: { default: [...DEFAULT_UPLOAD_BLOCK_TYPES] }, + }, + parseDOM: [ + { + tag: 'div[data-upload-block="true"]', + getAttrs: (dom) => ({ + allowedTypes: normalizeUploadBlockTypes( + String((dom as HTMLElement).getAttribute('data-upload-types') || '') + .split(',') + .map((item) => item.trim()) + .filter(Boolean) + ), + }), + }, + ], + toDOM: (node) => [ + 'div', + { + 'data-upload-block': 'true', + 'data-upload-types': normalizeUploadBlockTypes(node.attrs.allowedTypes).join(','), + }, + ], + parseMarkdown: { + match: (node) => node.type === UPLOAD_BLOCK_NODE_TYPE, + runner: (state, node, type) => { + state.addNode(type, { + allowedTypes: normalizeUploadBlockTypes(node.allowedTypes), + }) + }, + }, + toMarkdown: { + match: (node) => node.type.name === UPLOAD_BLOCK_NODE_TYPE, + runner: (state, node) => { + state.addNode('paragraph', [{ + type: 'text', + value: serializeUploadBlockSyntax(node.attrs.allowedTypes), + }]) + }, + }, + leafText: (node) => serializeUploadBlockSyntax(node.attrs.allowedTypes), +})) + +export const uploadBlockView = $view(uploadBlockNode, (ctx) => { + const requestUpload = ctx.get(uploadBlockConfigCtx.key).requestUpload + return (node, view, getPos) => new UploadBlockNodeView(node, view, getPos, requestUpload) +}) + +export const uploadBlockInputPlugin = $prose(() => { + return new Plugin({ + key: UPLOAD_BLOCK_INPUT_PLUGIN_KEY, + appendTransaction: (transactions, _oldState, newState) => { + if (!transactions.some((transaction) => transaction.docChanged)) return null + if (transactions.some((transaction) => transaction.getMeta(UPLOAD_BLOCK_INPUT_META))) return null + + const uploadBlockType = newState.schema.nodes[UPLOAD_BLOCK_NODE_TYPE] + if (!uploadBlockType) return null + + const replacements = findUploadBlockReplacements(newState.doc) + if (replacements.length === 0) return null + + let tr = newState.tr + + for (let index = replacements.length - 1; index >= 0; index -= 1) { + const replacement = replacements[index] + tr = tr.replaceWith( + replacement.from, + replacement.to, + uploadBlockType.create({ + allowedTypes: normalizeUploadBlockTypes(replacement.allowedTypes), + }) + ) + } + + if (!tr.docChanged) return null + tr.setMeta(UPLOAD_BLOCK_INPUT_META, true) + return tr + }, + }) +}) \ No newline at end of file diff --git a/src/router/index.js b/src/router/index.js index dbb1cfa..4833b39 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -12,9 +12,8 @@ const routes = [ component: () => import('../views/DocsView.vue') }, { - path: '/univer', - name: 'Univer', - component: () => import('../views/UniverView.vue') + path: '/:pathMatch(.*)*', + redirect: '/' } ] diff --git a/src/services/officeDetection.js b/src/services/officeDetection.js index 7731730..20ddaf1 100644 --- a/src/services/officeDetection.js +++ b/src/services/officeDetection.js @@ -1,7 +1,12 @@ /** * Office 文件类型检测工具 */ -import { OfficeFormat, OfficePresetType } from './univerBridge' + +export const OfficeFormat = { + DOCX: 'docx', + XLSX: 'xlsx', + PPTX: 'pptx' +} /** * 支持的 Office 文件扩展名 @@ -73,63 +78,10 @@ export function getOfficeFormat(file) { return null } -/** - * 获取文件图标类型 - */ -export function getOfficeIcon(format) { - switch (format) { - case OfficeFormat.DOCX: - return 'doc' - case OfficeFormat.XLSX: - return 'xls' - case OfficeFormat.PPTX: - return 'ppt' - default: - return 'file' - } -} - -/** - * 获取格式显示名称 - */ -export function getFormatDisplayName(format, locale = 'zh-CN') { - const names = { - 'zh-CN': { - [OfficeFormat.DOCX]: 'Word 文档', - [OfficeFormat.XLSX]: 'Excel 表格', - [OfficeFormat.PPTX]: 'PowerPoint 演示文稿' - }, - 'en-US': { - [OfficeFormat.DOCX]: 'Word Document', - [OfficeFormat.XLSX]: 'Excel Spreadsheet', - [OfficeFormat.PPTX]: 'PowerPoint Presentation' - } - } - - return names[locale]?.[format] || format?.toUpperCase() || '未知格式' -} - -/** - * 获取对应的 Preset 类型 - */ -export function getPresetTypeByFormat(format) { - switch (format) { - case OfficeFormat.DOCX: - case OfficeFormat.PPTX: - return OfficePresetType.DOCS - case OfficeFormat.XLSX: - return OfficePresetType.SHEETS - default: - return null - } -} - export default { + OfficeFormat, isOfficeFile, getOfficeFormat, - getOfficeIcon, - getFormatDisplayName, - getPresetTypeByFormat, SUPPORTED_EXTENSIONS, MIME_TYPES } diff --git a/src/services/univerBridge.js b/src/services/univerBridge.js deleted file mode 100644 index 7153484..0000000 --- a/src/services/univerBridge.js +++ /dev/null @@ -1,302 +0,0 @@ -/** -* Univer 编辑器桥接服务 -* 封装 Univer 的初始化、加载、导出等操作 -* -* 支持三种文档格式: -* - DOCX: 使用 DocsCorePreset -* - XLSX: 使用 SheetsCorePreset -* - PPTX: 使用 DocsCorePreset + Slides 插件组合 -*/ -import { createUniver, LocaleType, merge } from '@univerjs/presets' -import { UniverDocsCorePreset } from '@univerjs/preset-docs-core' -import { UniverSheetsCorePreset } from '@univerjs/preset-sheets-core' -import { UniverSlidesPlugin } from '@univerjs/slides' -import { UniverSlidesUIPlugin } from '@univerjs/slides-ui' - -// 导入样式 -import '@univerjs/preset-docs-core/lib/index.css' -import '@univerjs/preset-sheets-core/lib/index.css' -import '@univerjs/slides-ui/lib/index.css' - -// 导入语言包 -import DocsCoreEnUS from '@univerjs/preset-docs-core/locales/en-US' -import SheetsCoreEnUS from '@univerjs/preset-sheets-core/locales/en-US' -import DocsCoreZhCN from '@univerjs/preset-docs-core/locales/zh-CN' -import SheetsCoreZhCN from '@univerjs/preset-sheets-core/locales/zh-CN' - -// Slides 语言包(使用空对象作为 fallback,因为 slides 语言包可能不存在) -const SlidesZhCN = {} -const SlidesEnUS = {} - -export const OfficeFormat = { - DOCX: 'docx', - XLSX: 'xlsx', - PPTX: 'pptx' -} - -export const OfficePresetType = { - DOCS: 'docs', - SHEETS: 'sheets', - SLIDES: 'slides' -} - -/** - * 根据文件扩展名判断 Office 格式 - */ -export function detectOfficeFormat(filename) { - const ext = filename?.toLowerCase().split('.').pop() || '' - if (ext === 'docx') return OfficeFormat.DOCX - if (ext === 'xlsx') return OfficeFormat.XLSX - if (ext === 'pptx') return OfficeFormat.PPTX - return null -} - -/** - * 根据格式获取对应的 Preset 类型 - */ -export function getPresetType(format) { - switch (format) { - case OfficeFormat.DOCX: - return OfficePresetType.DOCS - case OfficeFormat.XLSX: - return OfficePresetType.SHEETS - case OfficeFormat.PPTX: - return OfficePresetType.SLIDES - default: - return null - } -} - -/** - * 创建 Univer 实例 - */ -export async function createUniverInstance(container, options = {}) { - const { - format = OfficeFormat.DOCX, - locale = 'zh-CN', - theme = 'light' - } = options - - const localeType = locale === 'zh-CN' ? LocaleType.ZH_CN : LocaleType.EN_US - // 合并所有语言包(包括 Slides) - const locales = locale === 'zh-CN' - ? { [LocaleType.ZH_CN]: merge({}, DocsCoreZhCN, SheetsCoreZhCN, SlidesZhCN) } - : { [LocaleType.EN_US]: merge({}, DocsCoreEnUS, SheetsCoreEnUS, SlidesEnUS) } - - const presets = [] - const extraPlugins = [] - - // 根据格式添加对应的 Preset 和插件 - switch (format) { - case OfficeFormat.DOCX: - // DOCX 只需要 DocsCorePreset - presets.push(UniverDocsCorePreset({ - container, - theme: theme === 'dark' ? 'dark' : 'default' - })) - break - - case OfficeFormat.XLSX: - // XLSX 只需要 SheetsCorePreset - presets.push(UniverSheetsCorePreset({ - container, - theme: theme === 'dark' ? 'dark' : 'default' - })) - break - - case OfficeFormat.PPTX: - // PPTX 需要 DocsCorePreset + Slides 插件 - // DocsCorePreset 提供基础文档渲染能力 - presets.push(UniverDocsCorePreset({ - container, - theme: theme === 'dark' ? 'dark' : 'default' - })) - // Slides 插件提供演示文稿功能 - // 注意:必须作为 plugins 而非 presets 传入 - extraPlugins.push([UniverSlidesPlugin]) - extraPlugins.push([UniverSlidesUIPlugin]) - break - - default: - // 默认使用 Docs 作为兜底 - presets.push(UniverDocsCorePreset({ - container, - theme: theme === 'dark' ? 'dark' : 'default' - })) - } - - try { - const { univer, univerAPI } = createUniver({ - locale: localeType, - locales, - presets, - plugins: extraPlugins.length > 0 ? extraPlugins : undefined, - collaboration: false // 纯前端模式,不启用协作 - }) - - console.log(`[Univer] 初始化成功,格式: ${format}, 预设数量: ${presets.length}, 插件数量: ${extraPlugins.length}`) - return { univer, univerAPI } - } catch (error) { - console.error('[Univer] 初始化失败:', error) - throw new Error(`Univer 初始化失败: ${error.message}`) - } -} - -/** - * Univer 编辑器实例包装类 - */ -export class UniverEditorInstance { - constructor() { - this.univer = null - this.univerAPI = null - this.container = null - this.currentFormat = null - } - - /** - * 初始化编辑器 - */ - async init(container, options = {}) { - if (this.univer) { - await this.destroy() - } - - this.container = container - this.currentFormat = options.format || OfficeFormat.DOCX - - const result = await createUniverInstance(container, { - format: this.currentFormat, - ...options - }) - - this.univer = result.univer - this.univerAPI = result.univerAPI - - // 创建初始文档 - if (this.currentFormat === OfficeFormat.XLSX) { - this.univerAPI.createWorkbook({}) - } else { - this.univerAPI.createUniverDoc({}) - } - - return this - } - - /** - * 从字节数组加载文档 - */ - async loadFromBytes(bytes, format) { - if (!this.univerAPI) { - throw new Error('Univer 实例未初始化') - } - - // 注意:纯前端模式下,Univer 不支持直接从 DOCX/XLSX/PPTX 字节流加载 - // 这里需要使用快照模式或后端服务来解析 - // 当前实现为占位,实际需要配合快照格式 - console.warn('纯前端模式暂不支持从 DOCX/XLSX/PPTX 字节流加载,请使用快照模式') - return false - } - - /** - * 导出为快照数据 - */ - async exportSnapshot() { - if (!this.univerAPI) { - throw new Error('Univer 实例未初始化') - } - - const activeDoc = this.univerAPI.getActiveDocument() - const activeSheet = this.univerAPI.getActiveWorkbook() - - if (activeSheet) { - return { - type: OfficePresetType.SHEETS, - format: OfficeFormat.XLSX, - data: activeSheet.getSnapshot() - } - } - - if (activeDoc) { - return { - type: OfficePresetType.DOCS, - format: OfficeFormat.DOCX, - data: activeDoc.getSnapshot() - } - } - - return null - } - - /** - * 从快照数据导入 - */ - async importSnapshot(snapshot) { - if (!this.univerAPI || !snapshot?.data) { - throw new Error('无效的快照数据') - } - - // 快照数据可以直接用于恢复文档状态 - // 具体实现取决于 Univer API - console.log('导入快照:', snapshot.type, snapshot.format) - return true - } - - /** - * 监听文档变化 - */ - onChange(callback) { - if (!this.univerAPI) return - - // Univer API 的事件监听 - this.univerAPI.addEvent(this.univerAPI.Event.CommandExecuted, (event) => { - callback({ - type: 'command', - data: event - }) - }) - } - - /** - * 销毁实例 - */ - async destroy() { - if (this.univer) { - this.univer.dispose() - this.univer = null - this.univerAPI = null - this.container = null - this.currentFormat = null - } - } - - /** - * 获取当前格式 - */ - getFormat() { - return this.currentFormat - } - - /** - * 检查是否已初始化 - */ - isInitialized() { - return this.univer !== null && this.univerAPI !== null - } -} - -/** - * 创建 Univer 编辑器实例 - */ -export function createUniverEditor() { - return new UniverEditorInstance() -} - -export default { - createUniverInstance, - createUniverEditor, - detectOfficeFormat, - getPresetType, - OfficeFormat, - OfficePresetType, - UniverEditorInstance -} diff --git a/src/stores/office.js b/src/stores/office.js deleted file mode 100644 index 230721e..0000000 --- a/src/stores/office.js +++ /dev/null @@ -1,186 +0,0 @@ -import { defineStore } from 'pinia' -import { ref, computed } from 'vue' -import { OfficeFormat, OfficePresetType } from '../services/univerBridge' - -export const useOfficeStore = defineStore('office', () => { - // 当前文档状态 - const currentFileName = ref('') - const currentFormat = ref(null) - const currentFileSize = ref(0) - const currentBytes = ref(null) - - // 快照模式 - const isSnapshotMode = ref(true) // 默认启用快照模式 - const currentSnapshot = ref(null) - - // 编辑状态 - const isEditing = ref(false) - const hasUnsavedChanges = ref(false) - - // 视图状态 - const activeView = ref('milkdown') // 'milkdown' | 'univer' - - // 文档加载状态(新增) - const documentLoadStatus = ref('idle') // 'idle' | 'loading' | 'success' | 'error' - const documentErrorMessage = ref('') - - // 计算属性 - const hasDocument = computed(() => { - return currentFileName.value && currentFormat.value - }) - - const documentInfo = computed(() => { - if (!hasDocument.value) return null - return { - name: currentFileName.value, - format: currentFormat.value, - size: currentFileSize.value, - isSnapshot: isSnapshotMode.value, - loadStatus: documentLoadStatus.value - } - }) - - /** - * 设置当前文档 - */ - function setCurrentDocument(file, bytes) { - if (!file) { - clearCurrentDocument() - return - } - - currentFileName.value = file.name || '未命名' - currentFormat.value = getFormatFromFileName(file.name) - currentFileSize.value = file.size || 0 - currentBytes.value = bytes - hasUnsavedChanges.value = false - documentLoadStatus.value = 'idle' - documentErrorMessage.value = '' - } - - /** - * 清除当前文档 - */ - function clearCurrentDocument() { - currentFileName.value = '' - currentFormat.value = null - currentFileSize.value = 0 - currentBytes.value = null - currentSnapshot.value = null - hasUnsavedChanges.value = false - documentLoadStatus.value = 'idle' - documentErrorMessage.value = '' - } - - /** - * 设置快照数据 - */ - function setSnapshot(snapshot) { - currentSnapshot.value = snapshot - hasUnsavedChanges.value = false - } - - /** - * 标记有未保存的更改 - */ - function markAsChanged() { - hasUnsavedChanges.value = true - } - - /** - * 切换视图 - */ - function switchView(view) { - activeView.value = view - } - - /** - * 切换快照模式 - */ - function toggleSnapshotMode() { - isSnapshotMode.value = !isSnapshotMode.value - } - - // 新增:文档加载状态管理方法 - /** - * 开始加载文档 - */ - function startDocumentLoad() { - documentLoadStatus.value = 'loading' - documentErrorMessage.value = '' - } - - /** - * 文档加载成功 - */ - function completeDocumentLoad() { - documentLoadStatus.value = 'success' - documentErrorMessage.value = '' - } - - /** - * 文档加载失败 - */ - function failDocumentLoad(message) { - documentLoadStatus.value = 'error' - documentErrorMessage.value = message || '文档加载失败' - } - - /** - * 重置文档加载状态 - */ - function resetDocumentLoadStatus() { - documentLoadStatus.value = 'idle' - documentErrorMessage.value = '' - } - - return { - // 状态 - currentFileName, - currentFormat, - currentFileSize, - currentBytes, - isSnapshotMode, - currentSnapshot, - isEditing, - hasUnsavedChanges, - activeView, - documentLoadStatus, - documentErrorMessage, - - // 计算属性 - hasDocument, - documentInfo, - - // 方法 - setCurrentDocument, - clearCurrentDocument, - setSnapshot, - markAsChanged, - switchView, - toggleSnapshotMode, - startDocumentLoad, - completeDocumentLoad, - failDocumentLoad, - resetDocumentLoadStatus - } -}) - -/** - * 从文件名获取格式 - */ -function getFormatFromFileName(filename) { - const ext = filename?.toLowerCase().split('.').pop() || '' - switch (ext) { - case 'docx': - return OfficeFormat.DOCX - case 'xlsx': - return OfficeFormat.XLSX - case 'pptx': - return OfficeFormat.PPTX - default: - return null - } -} - -export default useOfficeStore diff --git a/src/stores/settings.js b/src/stores/settings.js index 32e7cc0..ba8c988 100644 --- a/src/stores/settings.js +++ b/src/stores/settings.js @@ -11,6 +11,7 @@ export const useSettingsStore = defineStore('settings', () => { // 2. Model Behavior const modelThinking = ref('low') // 'low' | 'medium' | 'high' const debounceMs = ref(1000) // 1000 - 5000 + const proModel = ref('') // 3. Privacy const privacyMode = ref(true) @@ -44,7 +45,10 @@ export const useSettingsStore = defineStore('settings', () => { // We will let the backend handle 'auto' currency if needed, or stick to auto label. const t = computed(() => { - return translations[uiLanguage.value] || translations['en'] + return { + ...translations['en'], + ...(translations[uiLanguage.value] || {}), + } }) const initialMarkdown = computed(() => { @@ -63,6 +67,7 @@ export const useSettingsStore = defineStore('settings', () => { if (data.theme) theme.value = data.theme if (data.modelThinking) modelThinking.value = data.modelThinking if (data.debounceMs) debounceMs.value = data.debounceMs + if (typeof data.proModel === 'string') proModel.value = data.proModel if (typeof data.privacyMode === 'boolean') privacyMode.value = data.privacyMode if (data.language) language.value = data.language if (data.currency) currency.value = data.currency @@ -86,6 +91,7 @@ export const useSettingsStore = defineStore('settings', () => { theme: theme.value, modelThinking: modelThinking.value, debounceMs: debounceMs.value, + proModel: proModel.value, privacyMode: privacyMode.value, language: language.value, currency: currency.value, @@ -105,6 +111,7 @@ export const useSettingsStore = defineStore('settings', () => { theme.value = 'system' modelThinking.value = 'low' debounceMs.value = 1000 + proModel.value = '' privacyMode.value = false language.value = 'auto' currency.value = 'auto' @@ -121,6 +128,7 @@ export const useSettingsStore = defineStore('settings', () => { theme, modelThinking, debounceMs, + proModel, privacyMode, language, currency, @@ -141,6 +149,7 @@ export const useSettingsStore = defineStore('settings', () => { theme, modelThinking, debounceMs, + proModel, privacyMode, language, currency, @@ -148,6 +157,7 @@ export const useSettingsStore = defineStore('settings', () => { backgroundImage, backgroundOpacity, ttsInstruct, + detectedTimezone, uiLanguage, t, initialMarkdown, diff --git a/src/stores/templates.js b/src/stores/templates.js new file mode 100644 index 0000000..95ade67 --- /dev/null +++ b/src/stores/templates.js @@ -0,0 +1,259 @@ +import { defineStore } from 'pinia' +import { ref, watch } from 'vue' + +const STORAGE_KEY = 'llm-in-text-templates' +const STORAGE_VERSION = 1 + +const PRESET_TEMPLATES = [ + { + id: 'preset-meeting-notes', + name: '会议纪要', + content: `# 会议纪要 + +- 日期: +- 时间: +- 地点: +- 参会人: + +## 议题 + +## 讨论记录 + +## 决议 + +## 待办事项 +- [ ] `, + }, + { + id: 'preset-article-outline', + name: '文章大纲', + content: `# 标题 + +> 一句话摘要 + +## 背景 + +## 结构 + +## 关键点 + +## 结论`, + }, + { + id: 'preset-project-plan', + name: '项目计划', + content: `# 项目计划 + +## 目标 + +## 范围 + +## 里程碑 + +## 风险 + +## 负责人`, + }, + { + id: 'preset-research-notes', + name: '研究笔记', + content: `# 研究笔记 + +## 问题定义 + +## 资料来源 + +## 观察 + +## 结论 + +## 下一步`, + }, +] + +const normalizeTemplateName = (value) => String(value ?? '').replace(/\s+/g, ' ').trim() + +const normalizeTemplateContent = (value) => String(value ?? '').replace(/\r\n?/g, '\n').replace(/\s+$/, '') + +const makeTemplateId = () => { + if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') { + return `custom-${crypto.randomUUID()}` + } + + return `custom-${Date.now()}-${Math.random().toString(36).slice(2, 10)}` +} + +const normalizeStoredTemplate = (template) => { + if (!template || typeof template !== 'object') return null + + const name = normalizeTemplateName(template.name) + const content = normalizeTemplateContent(template.content) + if (!name || !content.trim()) return null + + const createdAt = typeof template.createdAt === 'string' && template.createdAt ? template.createdAt : new Date().toISOString() + const updatedAt = typeof template.updatedAt === 'string' && template.updatedAt ? template.updatedAt : createdAt + + return { + id: typeof template.id === 'string' && template.id ? template.id : makeTemplateId(), + name, + content, + createdAt, + updatedAt, + } +} + +export const useTemplatesStore = defineStore('templates', () => { + const customTemplates = ref([]) + let isHydrated = false + + const loadTemplates = () => { + try { + const stored = localStorage.getItem(STORAGE_KEY) + if (!stored) return + + const parsed = JSON.parse(stored) + const rawTemplates = Array.isArray(parsed) + ? parsed + : Array.isArray(parsed?.customTemplates) + ? parsed.customTemplates + : Array.isArray(parsed?.data) + ? parsed.data + : [] + + customTemplates.value = rawTemplates + .map(normalizeStoredTemplate) + .filter(Boolean) + } catch { + customTemplates.value = [] + } + } + + const saveTemplates = () => { + if (!isHydrated) return + + try { + localStorage.setItem( + STORAGE_KEY, + JSON.stringify({ + version: STORAGE_VERSION, + customTemplates: customTemplates.value, + }) + ) + } catch { + // Ignore persistence failures in browser storage + } + } + + const isCustomTemplateNameTaken = (name, ignoreId = '') => { + const normalizedName = normalizeTemplateName(name) + if (!normalizedName) return false + + return customTemplates.value.some((template) => { + if (ignoreId && template.id === ignoreId) return false + return template.name.toLowerCase() === normalizedName.toLowerCase() + }) + } + + const buildUniqueCustomTemplateName = (baseName, ignoreId = '') => { + const normalizedBaseName = normalizeTemplateName(baseName) || 'Untitled Template' + let candidate = normalizedBaseName + let suffix = 2 + + while (isCustomTemplateNameTaken(candidate, ignoreId)) { + candidate = `${normalizedBaseName} ${suffix}` + suffix += 1 + } + + return candidate + } + + const createCustomTemplate = ({ name, content }) => { + const nextName = normalizeTemplateName(name) + const nextContent = normalizeTemplateContent(content) + + if (!nextName || !nextContent.trim()) { + return { ok: false, error: 'invalid' } + } + + if (isCustomTemplateNameTaken(nextName)) { + return { ok: false, error: 'duplicate' } + } + + const now = new Date().toISOString() + const template = { + id: makeTemplateId(), + name: nextName, + content: nextContent, + createdAt: now, + updatedAt: now, + } + + customTemplates.value = [...customTemplates.value, template] + return { ok: true, template } + } + + const updateCustomTemplate = (id, { name, content }) => { + const index = customTemplates.value.findIndex((template) => template.id === id) + if (index === -1) { + return { ok: false, error: 'not_found' } + } + + const nextName = normalizeTemplateName(name) + const nextContent = normalizeTemplateContent(content) + + if (!nextName || !nextContent.trim()) { + return { ok: false, error: 'invalid' } + } + + if (isCustomTemplateNameTaken(nextName, id)) { + return { ok: false, error: 'duplicate' } + } + + const now = new Date().toISOString() + const nextTemplates = [...customTemplates.value] + nextTemplates[index] = { + ...nextTemplates[index], + name: nextName, + content: nextContent, + updatedAt: now, + } + + customTemplates.value = nextTemplates + return { ok: true, template: nextTemplates[index] } + } + + const deleteCustomTemplate = (id) => { + const nextTemplates = customTemplates.value.filter((template) => template.id !== id) + if (nextTemplates.length === customTemplates.value.length) { + return false + } + + customTemplates.value = nextTemplates + return true + } + + const getTemplateById = (id) => { + if (!id) return null + + return PRESET_TEMPLATES.find((template) => template.id === id) + || customTemplates.value.find((template) => template.id === id) + || null + } + + watch(customTemplates, saveTemplates, { deep: true }) + + loadTemplates() + isHydrated = true + saveTemplates() + + return { + presetTemplates: PRESET_TEMPLATES, + customTemplates, + createCustomTemplate, + updateCustomTemplate, + deleteCustomTemplate, + getTemplateById, + buildUniqueCustomTemplateName, + isCustomTemplateNameTaken, + } +}) \ No newline at end of file diff --git a/src/style.css b/src/style.css index b14cd14..5878bfa 100644 --- a/src/style.css +++ b/src/style.css @@ -244,6 +244,27 @@ body { } } +.hidden-text-preview { + display: inline-flex; + align-items: center; + vertical-align: baseline; +} + +.hidden-text-preview__summary { + display: inline-flex; + align-items: center; + min-height: 1.8em; + padding: 0.08em 0.52em; + border-radius: 0.45em; + background: rgba(148, 163, 184, 0.26); + color: inherit; + white-space: pre-wrap; +} + +:root[data-theme='dark'] .hidden-text-preview__summary { + background: rgba(100, 116, 139, 0.32); +} + /* ── Mermaid diagram blocks ─────────────────────────────────────────── */ .mermaid-block { position: relative; diff --git a/src/utils/api.js b/src/utils/api.js index 519d326..ec64515 100644 --- a/src/utils/api.js +++ b/src/utils/api.js @@ -1,4 +1,4 @@ -import { API_URL, API_KEY, TTS_URL, TTS_STATUS_URL, TTS_CONFIG_URL } from './config.js' +import { API_URL, API_KEY, PRO_STREAM_URL, TTS_URL, TTS_STATUS_URL, TTS_CONFIG_URL } from './config.js' import { useSettingsStore } from '../stores/settings' function generateRequestId() { @@ -11,6 +11,9 @@ function generateRequestId() { function getCancelUrl(apiUrl) { const normalized = String(apiUrl || '').replace(/\/+$/, '') if (!normalized) return '/v1/completions/cancel' + if (/\/v1\/pro\/completions\/stream$/i.test(normalized)) { + return normalized.replace(/\/v1\/pro\/completions\/stream$/i, '/v1/completions/cancel') + } if (normalized.endsWith('/v1/completions')) { return `${normalized}/cancel` } @@ -42,6 +45,50 @@ async function sendCancelRequest(cancelUrl, requestId, reason) { } } +function createAbortError(message = 'Request aborted') { + const error = new Error(message) + error.name = 'AbortError' + return error +} + +function buildCompletionBody(settings, prefix, suffix, languageId, extra = {}) { + return { + prefix, + suffix, + languageId, + model_thinking: settings.modelThinking, + privacy_mode: settings.privacyMode, + user_preferences: { + language: settings.language, + currency: settings.currency, + timezone: settings.detectedTimezone, + }, + ...extra, + } +} + +function parseSseEvent(rawEvent) { + const lines = String(rawEvent || '').replace(/\r/g, '').split('\n') + let event = 'message' + const dataLines = [] + + for (const line of lines) { + if (!line) continue + if (line.startsWith('event:')) { + event = line.slice(6).trim() || 'message' + continue + } + if (line.startsWith('data:')) { + dataLines.push(line.slice(5).trimStart()) + } + } + + return { + event, + data: dataLines.join('\n'), + } +} + export async function fetchSuggestion(prefix, suffix, languageId, signal, apiUrl = API_URL) { let normalizedLanguageId = 'markdown' if (typeof languageId === 'string' && languageId.trim()) { @@ -77,18 +124,7 @@ export async function fetchSuggestion(prefix, suffix, languageId, signal, apiUrl 'X-API-Key': API_KEY, } - const body = { - prefix, - suffix, - languageId: normalizedLanguageId, - model_thinking: settings.modelThinking, - privacy_mode: settings.privacyMode, - user_preferences: { - language: settings.language, - currency: settings.currency, - timezone: settings.detectedTimezone, - }, - } + const body = buildCompletionBody(settings, prefix, suffix, normalizedLanguageId) const res = await fetch(apiUrl, { method: 'POST', @@ -117,6 +153,134 @@ export async function fetchSuggestion(prefix, suffix, languageId, signal, apiUrl } } +export async function fetchProSuggestionStream(payload, apiUrl = PRO_STREAM_URL) { + const { + prefix = '', + suffix = '', + languageId = 'markdown', + signal, + model = '', + temperature = 0.7, + timeoutMs = 600000, + onChunk, + } = payload || {} + + const settings = useSettingsStore() + const requestId = generateRequestId() + const cancelUrl = getCancelUrl(apiUrl) + const requestController = new AbortController() + const timeoutId = setTimeout(() => { + requestController.abort('timeout') + }, timeoutMs) + + const relayAbort = () => { + requestController.abort(signal?.reason || 'abort') + } + + const onAbort = () => { + const reason = normalizeAbortReason(requestController.signal.reason) + void sendCancelRequest(cancelUrl, requestId, reason) + } + + requestController.signal.addEventListener('abort', onAbort, { once: true }) + + if (signal) { + if (signal.aborted) { + relayAbort() + } else { + signal.addEventListener('abort', relayAbort, { once: true }) + } + } + + try { + const res = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-Request-Id': requestId, + 'X-API-Key': API_KEY, + }, + body: JSON.stringify( + buildCompletionBody(settings, prefix, suffix, String(languageId || 'markdown').trim() || 'markdown', { + model, + temperature, + }) + ), + signal: requestController.signal, + }) + + if (!res.ok) { + const errorText = await res.text() + throw new Error(`HTTP ${res.status}: ${errorText}`) + } + + if (!res.body) { + throw new Error('PRO 模式流式响应不可用') + } + + const reader = res.body.getReader() + const decoder = new TextDecoder() + let buffer = '' + let finalContent = '' + + while (true) { + const { done, value } = await reader.read() + if (done) break + + buffer += decoder.decode(value, { stream: true }) + + let boundary = buffer.indexOf('\n\n') + while (boundary >= 0) { + const chunk = buffer.slice(0, boundary) + buffer = buffer.slice(boundary + 2) + + const parsed = parseSseEvent(chunk) + if (parsed.event === 'chunk' && parsed.data) { + const data = JSON.parse(parsed.data) + const delta = String(data.delta || '') + if (delta) { + finalContent += delta + onChunk?.(delta) + } + } + + if (parsed.event === 'done' && parsed.data) { + const data = JSON.parse(parsed.data) + return String(data.content || finalContent || '') + } + + if (parsed.event === 'error' && parsed.data) { + const data = JSON.parse(parsed.data) + throw new Error(String(data.error || 'PRO 模式请求失败')) + } + + if (parsed.event === 'cancelled') { + throw createAbortError('PRO 模式请求已取消') + } + + boundary = buffer.indexOf('\n\n') + } + } + + if (requestController.signal.aborted) { + throw createAbortError('PRO 模式请求已中止') + } + + return finalContent + } catch (e) { + if (e?.name === 'AbortError') { + throw e + } + throw e + } finally { + clearTimeout(timeoutId) + requestController.signal.removeEventListener('abort', onAbort) + if (signal) { + signal.removeEventListener('abort', relayAbort) + } + } +} + export async function fetchTTS(text, instruct = '', apiUrl = TTS_URL) { const res = await fetch(apiUrl, { method: 'POST', diff --git a/src/utils/config.js b/src/utils/config.js index 3b15813..31e76ca 100644 --- a/src/utils/config.js +++ b/src/utils/config.js @@ -4,6 +4,7 @@ const DEFAULT_API_BASE_URL = import.meta.env.DEV ? '' : 'https://api.imageteach. const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || DEFAULT_API_BASE_URL export const API_URL = import.meta.env.VITE_API_URL || `${API_BASE_URL}/v1/completions` +export const PRO_STREAM_URL = import.meta.env.VITE_PRO_STREAM_URL || `${API_BASE_URL}/v1/pro/completions/stream` export const OCR_URL = import.meta.env.VITE_OCR_URL || `${API_BASE_URL}/v1/ocr` export const CONVERT_URL = import.meta.env.VITE_CONVERT_URL || `${API_BASE_URL}/v1/convert` export const EXPORT_PDF_URL = import.meta.env.VITE_EXPORT_PDF_URL || '/v1/export/pdf' diff --git a/src/utils/hiddenText.js b/src/utils/hiddenText.js new file mode 100644 index 0000000..b0dace2 --- /dev/null +++ b/src/utils/hiddenText.js @@ -0,0 +1,162 @@ +export const HIDDEN_TEXT_NODE_TYPE = 'hiddenText' + +function escapeHtml(value = '') { + return String(value) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} + +export function normalizeHiddenTextValue(value = '') { + return String(value ?? '').replace(/\r\n?/g, ' ') +} + +export function escapeHiddenTextSegment(value = '', closingChar) { + const normalized = normalizeHiddenTextValue(value) + const closingCharPattern = new RegExp(`\\${closingChar}`, 'g') + return normalized + .replace(/\\/g, '\\\\') + .replace(closingCharPattern, `\\${closingChar}`) +} + +export function serializeHiddenTextSyntax(displayed = '', hidden = '') { + const safeDisplayed = escapeHiddenTextSegment(displayed, ')') + const safeHidden = escapeHiddenTextSegment(hidden, '}') + return `(${safeDisplayed}){${safeHidden}}` +} + +function readHiddenTextSegment(text = '', start = 0, closingChar = ')') { + let index = start + let value = '' + + while (index < text.length) { + const char = text[index] + + if (char === '\\') { + const nextChar = text[index + 1] + if (nextChar === undefined) return null + value += nextChar + index += 2 + continue + } + + if (char === '\n' || char === '\r') return null + if (char === closingChar) { + return { + value, + end: index + 1, + } + } + + value += char + index += 1 + } + + return null +} + +export function parseHiddenTextAt(text = '', start = 0) { + if (text[start] !== '(') return null + + const displayed = readHiddenTextSegment(text, start + 1, ')') + if (!displayed) return null + if (text[displayed.end] !== '{') return null + + const hidden = readHiddenTextSegment(text, displayed.end + 1, '}') + if (!hidden) return null + + return { + start, + end: hidden.end, + displayed: displayed.value, + hidden: hidden.value, + raw: text.slice(start, hidden.end), + } +} + +export function extractHiddenTextMatches(text = '') { + const matches = [] + let index = 0 + + while (index < text.length) { + const match = parseHiddenTextAt(text, index) + if (match) { + matches.push(match) + index = match.end + continue + } + + index += 1 + } + + return matches +} + +export function splitTextWithHiddenSyntax(text = '') { + const matches = extractHiddenTextMatches(text) + if (matches.length === 0) return null + + const segments = [] + let cursor = 0 + + for (const match of matches) { + if (match.start > cursor) { + segments.push({ + type: 'text', + value: text.slice(cursor, match.start), + }) + } + + segments.push({ + type: HIDDEN_TEXT_NODE_TYPE, + displayed: match.displayed, + hidden: match.hidden, + }) + + cursor = match.end + } + + if (cursor < text.length) { + segments.push({ + type: 'text', + value: text.slice(cursor), + }) + } + + return segments.filter((segment) => segment.type !== 'text' || segment.value) +} + +export function renderHiddenTextPreviewHtml(displayed = '', hidden = '') { + const summary = escapeHtml(displayed || '未命名文本') + + return [ + ``, + `${summary}`, + '', + ].join('') +} + +export function hiddenTextMarkdownItPlugin(md) { + md.inline.ruler.before('emphasis', 'hidden_text', (state, silent) => { + const match = parseHiddenTextAt(state.src, state.pos) + if (!match) return false + + if (!silent) { + const token = state.push('hidden_text', '', 0) + token.meta = { + displayed: match.displayed, + hidden: match.hidden, + } + } + + state.pos = match.end + return true + }) + + md.renderer.rules.hidden_text = (tokens, index) => { + const meta = tokens[index]?.meta || {} + return renderHiddenTextPreviewHtml(meta.displayed, meta.hidden) + } +} \ No newline at end of file diff --git a/src/utils/i18n.js b/src/utils/i18n.js index a0f4d1b..3ecf659 100644 --- a/src/utils/i18n.js +++ b/src/utils/i18n.js @@ -22,6 +22,12 @@ export const translations = { mediumDesc: 'Brief analysis before suggesting', highDesc: 'Deep, step-by-step analysis (Slowest)', debounceTime: 'Debounce Time', + proMode: 'PRO Mode', + proModeThinking: 'PRO Thinking', + proModel: 'PRO Model', + proModelPlaceholder: 'e.g. qwen3:32b', + proModelDesc: 'Optional stronger model name used only by PRO mode.', + proModelEmptyHint: 'Leave empty to use the backend default PRO model.', privacyPreferences: 'Privacy & Preferences', privacyMode: 'Privacy Mode', privacyDesc: 'Prevent sending IP and preferences to the AI', @@ -50,6 +56,27 @@ export const translations = { uploading: 'Uploading files...', enableAI: 'Enable AI', disableAI: 'Disable AI', + template: 'Template', + presetTemplates: 'Preset Templates', + customTemplates: 'Custom Templates', + newTemplate: 'New Template', + previewTemplate: 'Preview Template', + applyTemplate: 'Apply Template', + copyAsTemplate: 'Copy as Custom Template', + editTemplate: 'Edit Template', + saveTemplate: 'Save Template', + templateName: 'Template Name', + templateContent: 'Template Content', + templateNamePlaceholder: 'e.g. Meeting Notes', + templateContentPlaceholder: 'Enter template content here...', + noTemplates: 'No custom templates yet', + templateNameRequired: 'Template name is required.', + templateContentRequired: 'Template content is required.', + templateNameDuplicate: 'Template name already exists.', + templateDeleteConfirm: 'Delete this template?', + templateSaved: 'Template saved.', + templateUpdated: 'Template updated.', + templateDeleted: 'Template deleted.', insertUrl: 'Insert Image from URL', insert: 'Insert', cancel: 'Cancel', @@ -114,6 +141,12 @@ export const translations = { mediumDesc: '简要分析上下文后建议', highDesc: '深度逐步分析(最慢但质量最高)', debounceTime: '防抖时间', + proMode: 'PRO模式思考', + proModeThinking: 'PRO 正在思考', + proModel: 'PRO 模型', + proModelPlaceholder: '例如 qwen3:32b', + proModelDesc: '可选。仅在 PRO 模式下使用的更强模型名称。', + proModelEmptyHint: '留空则使用后端默认 PRO 模型。', privacyPreferences: '隐私与偏好', privacyMode: '隐私模式', privacyDesc: '不向 AI 发送 IP 地址和偏好设置', @@ -142,6 +175,27 @@ export const translations = { uploading: '正在上传文件...', enableAI: '启用 AI', disableAI: '禁用 AI', + template: '模板', + presetTemplates: '预设模板', + customTemplates: '自定义模板', + newTemplate: '新建模板', + previewTemplate: '预览模板', + applyTemplate: '应用模板', + copyAsTemplate: '复制为自定义模板', + editTemplate: '编辑模板', + saveTemplate: '保存模板', + templateName: '模板名称', + templateContent: '模板内容', + templateNamePlaceholder: '例如:会议纪要', + templateContentPlaceholder: '在这里输入模板内容...', + noTemplates: '暂无自定义模板', + templateNameRequired: '请输入模板名称', + templateContentRequired: '请输入模板内容', + templateNameDuplicate: '模板名称已存在', + templateDeleteConfirm: '确认删除此模板吗?', + templateSaved: '模板已保存', + templateUpdated: '模板已更新', + templateDeleted: '模板已删除', insertUrl: '通过 URL 插入图片', insert: '插入', cancel: '取消', diff --git a/src/utils/proBlock.js b/src/utils/proBlock.js new file mode 100644 index 0000000..eb44df3 --- /dev/null +++ b/src/utils/proBlock.js @@ -0,0 +1,72 @@ +export const PRO_BLOCK_NODE_TYPE = 'pro_block' +export const PRO_TRIGGER_TEXT = '[PRO]' +export const PRO_DISPLAY_LABEL = 'PRO模式思考' + +const PRO_PENDING_PREFIX = '[Pro][{' +const PRO_PENDING_SUFFIX = '}]' +const PRO_TRIGGER_RE = /^\[pro\]$/i + +function normalizeMarkdownText(value = '') { + return String(value || '').replace(/\r\n?/g, '\n') +} + +export function escapeProBlockContent(value = '') { + return normalizeMarkdownText(value) + .replace(/\\/g, '\\\\') + .replace(/\n/g, '\\n') + .replace(/]/g, '\\]') + .replace(/}/g, '\\}') +} + +export function unescapeProBlockContent(value = '') { + const normalized = String(value || '') + let result = '' + + for (let index = 0; index < normalized.length; index += 1) { + const char = normalized[index] + if (char !== '\\' || index === normalized.length - 1) { + result += char + continue + } + + const next = normalized[index + 1] + if (next === 'n') { + result += '\n' + } else { + result += next + } + index += 1 + } + + return result +} + +export function serializeProBlockSyntax(content = '') { + const normalized = normalizeMarkdownText(content) + if (!normalized) return PRO_TRIGGER_TEXT + return `${PRO_PENDING_PREFIX}${escapeProBlockContent(normalized)}${PRO_PENDING_SUFFIX}` +} + +export function parseProBlockSyntax(value = '') { + const text = normalizeMarkdownText(value).trim() + if (!text) return null + + if (PRO_TRIGGER_RE.test(text)) { + return { + content: '', + autoStart: true, + } + } + + const prefix = text.slice(0, PRO_PENDING_PREFIX.length) + if (prefix.toLowerCase() === PRO_PENDING_PREFIX.toLowerCase() && text.endsWith(PRO_PENDING_SUFFIX)) { + return { + content: unescapeProBlockContent( + text.slice(PRO_PENDING_PREFIX.length, text.length - PRO_PENDING_SUFFIX.length) + ), + autoStart: false, + } + } + + return null +} \ No newline at end of file diff --git a/src/utils/uploadBlock.js b/src/utils/uploadBlock.js new file mode 100644 index 0000000..2181a78 --- /dev/null +++ b/src/utils/uploadBlock.js @@ -0,0 +1,220 @@ +export const UPLOAD_BLOCK_NODE_TYPE = 'upload_block' + +export const DEFAULT_UPLOAD_BLOCK_TYPES = [ + 'docx', + 'pptx', + 'pdf', + 'txt', + 'json', + 'toml', + 'yaml', + 'images', +] + +const TYPE_ALIAS = { + doc: 'docx', + docx: 'docx', + word: 'docx', + ppt: 'pptx', + pptx: 'pptx', + powerpoint: 'pptx', + pdf: 'pdf', + txt: 'txt', + text: 'txt', + plain: 'txt', + json: 'json', + toml: 'toml', + yaml: 'yaml', + yml: 'yaml', + '[images]': 'images', + image: 'images', + images: 'images', +} + +const TYPE_LABELS = { + docx: 'DOCX', + pptx: 'PPTX', + pdf: 'PDF', + txt: 'TXT', + json: 'JSON', + toml: 'TOML', + yaml: 'YAML', + images: '图片', +} + +const TYPE_ACCEPT_MAP = { + docx: [ + '.docx', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + ], + pptx: [ + '.pptx', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + ], + pdf: [ + '.pdf', + 'application/pdf', + ], + txt: [ + '.txt', + 'text/plain', + ], + json: [ + '.json', + 'application/json', + ], + toml: [ + '.toml', + 'application/toml', + 'text/toml', + ], + yaml: [ + '.yaml', + '.yml', + 'text/yaml', + 'text/x-yaml', + 'application/x-yaml', + ], + images: ['image/*'], +} + +const IMAGE_EXT_RE = /\.(png|jpe?g|gif|webp|bmp|svg|heic|heif|avif)$/i +const SYNTAX_RE = /^\{\{\{([\s\S]*?)\}\}\}$/ +const UPLOAD_TYPE_PREFIX_RE = /^upload\s+file\s+type\s*:\s*(.+)$/i + +function parseStrictUploadBlockTypes(values = []) { + const normalized = [] + + for (const value of values) { + const next = normalizeUploadBlockType(value) + if (!next) return null + if (!normalized.includes(next)) { + normalized.push(next) + } + } + + return normalized.length > 0 ? normalized : null +} + +export function normalizeUploadBlockType(value = '') { + const key = String(value || '').trim().toLowerCase() + return TYPE_ALIAS[key] || '' +} + +export function normalizeUploadBlockTypes(value) { + const source = Array.isArray(value) ? value : [] + const normalized = [] + + for (const item of source) { + const next = normalizeUploadBlockType(item) + if (!next || normalized.includes(next)) continue + normalized.push(next) + } + + return normalized.length > 0 ? normalized : [...DEFAULT_UPLOAD_BLOCK_TYPES] +} + +export function getUploadBlockMenuOptions(allowedTypes = DEFAULT_UPLOAD_BLOCK_TYPES) { + return normalizeUploadBlockTypes(allowedTypes).map((type) => ({ + value: type, + label: TYPE_LABELS[type] || type.toUpperCase(), + })) +} + +export function getUploadBlockAccept(allowedTypes = DEFAULT_UPLOAD_BLOCK_TYPES) { + const entries = new Set() + + normalizeUploadBlockTypes(allowedTypes).forEach((type) => { + const values = TYPE_ACCEPT_MAP[type] || [] + values.forEach((entry) => entries.add(entry)) + }) + + return Array.from(entries).join(',') +} + +export function getUploadBlockAcceptForType(type = '') { + const normalized = normalizeUploadBlockType(type) + if (!normalized) return '' + return (TYPE_ACCEPT_MAP[normalized] || []).join(',') +} + +function parseUploadBlockInner(raw = '') { + const inner = String(raw || '').trim() + if (!inner) { + return { allowedTypes: [...DEFAULT_UPLOAD_BLOCK_TYPES] } + } + + const matched = inner.match(UPLOAD_TYPE_PREFIX_RE) + if (!matched) return null + + const segments = matched[1] + .split(',') + .map((item) => item.trim()) + .filter(Boolean) + + if (segments.length === 0) return null + + const allowedTypes = parseStrictUploadBlockTypes(segments) + if (!allowedTypes) return null + + return allowedTypes.length > 0 ? { allowedTypes } : null +} + +export function parseUploadBlockSyntax(value = '') { + const matched = String(value || '').trim().match(SYNTAX_RE) + if (!matched) return null + return parseUploadBlockInner(matched[1]) +} + +export function serializeUploadBlockSyntax(allowedTypes = DEFAULT_UPLOAD_BLOCK_TYPES) { + const normalized = normalizeUploadBlockTypes(allowedTypes) + const isDefault = + normalized.length === DEFAULT_UPLOAD_BLOCK_TYPES.length && + normalized.every((type, index) => type === DEFAULT_UPLOAD_BLOCK_TYPES[index]) + + if (isDefault) return '{{{}}}' + + const serialized = normalized + .map((type) => (type === 'images' ? '[images]' : type)) + .join(',') + + return `{{{upload file type:${serialized}}}}` +} + +export function isUploadBlockImageFile(file) { + if (!file) return false + const name = String(file.name || '').toLowerCase() + const type = String(file.type || '').toLowerCase() + return type.startsWith('image/') || IMAGE_EXT_RE.test(name) +} + +export function getUploadBlockFileType(file) { + if (!file) return '' + if (isUploadBlockImageFile(file)) return 'images' + + const name = String(file.name || '').toLowerCase() + if (name.endsWith('.docx')) return 'docx' + if (name.endsWith('.pptx')) return 'pptx' + if (name.endsWith('.pdf')) return 'pdf' + if (name.endsWith('.json')) return 'json' + if (name.endsWith('.toml')) return 'toml' + if (name.endsWith('.yaml') || name.endsWith('.yml')) return 'yaml' + if (name.endsWith('.txt')) return 'txt' + + const mime = String(file.type || '').toLowerCase() + if (mime === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') return 'docx' + if (mime === 'application/vnd.openxmlformats-officedocument.presentationml.presentation') return 'pptx' + if (mime === 'application/pdf') return 'pdf' + if (mime === 'application/json') return 'json' + if (mime === 'application/toml' || mime === 'text/toml') return 'toml' + if (mime === 'text/yaml' || mime === 'text/x-yaml' || mime === 'application/x-yaml') return 'yaml' + if (mime === 'text/plain') return 'txt' + + return '' +} + +export function isUploadBlockTypeAllowed(file, allowedTypes = DEFAULT_UPLOAD_BLOCK_TYPES) { + const fileType = getUploadBlockFileType(file) + if (!fileType) return false + return normalizeUploadBlockTypes(allowedTypes).includes(fileType) +} \ No newline at end of file diff --git a/src/views/UniverView.vue b/src/views/UniverView.vue deleted file mode 100644 index 481df2a..0000000 --- a/src/views/UniverView.vue +++ /dev/null @@ -1,46 +0,0 @@ - - - - -