diff --git a/surfsense_backend/alembic/versions/43_add_blocknote_fields_to_documents.py b/surfsense_backend/alembic/versions/43_add_blocknote_fields_to_documents.py
new file mode 100644
index 000000000..32e7780eb
--- /dev/null
+++ b/surfsense_backend/alembic/versions/43_add_blocknote_fields_to_documents.py
@@ -0,0 +1,75 @@
+"""43_add_blocknote_fields_to_documents
+
+Revision ID: 43
+Revises: 42
+Create Date: 2025-11-30
+
+Adds fields for live document editing:
+- blocknote_document: JSONB editor state
+- content_needs_reindexing: Flag for regenerating chunks/summary
+- last_edited_at: Last edit timestamp
+"""
+
+from collections.abc import Sequence
+
+import sqlalchemy as sa
+from sqlalchemy.dialects import postgresql
+
+from alembic import op
+
+# revision identifiers, used by Alembic.
+revision: str = "43"
+down_revision: str | None = "42"
+branch_labels: str | Sequence[str] | None = None
+depends_on: str | Sequence[str] | None = None
+
+
+def upgrade() -> None:
+ """Upgrade schema - Add BlockNote fields and trigger population task."""
+
+ # Add the columns
+ op.add_column(
+ "documents",
+ sa.Column(
+ "blocknote_document", postgresql.JSONB(astext_type=sa.Text()), nullable=True
+ ),
+ )
+ op.add_column(
+ "documents",
+ sa.Column(
+ "content_needs_reindexing",
+ sa.Boolean(),
+ nullable=False,
+ server_default=sa.false(),
+ ),
+ )
+ op.add_column(
+ "documents",
+ sa.Column("last_edited_at", sa.TIMESTAMP(timezone=True), nullable=True),
+ )
+
+ # Trigger the Celery task to populate blocknote_document for existing documents
+ try:
+ from app.tasks.celery_tasks.blocknote_migration_tasks import (
+ populate_blocknote_for_documents_task,
+ )
+
+ # Queue the task to run asynchronously
+ populate_blocknote_for_documents_task.apply_async()
+ print(
+ "✓ Queued Celery task to populate blocknote_document for existing documents"
+ )
+ except Exception as e:
+ # If Celery is not available or task queueing fails, log but don't fail the migration
+ print(f"⚠ Warning: Could not queue blocknote population task: {e}")
+ print(" You can manually trigger it later with:")
+ print(
+ " celery -A app.celery_app call app.tasks.celery_tasks.blocknote_migration_tasks.populate_blocknote_for_documents_task"
+ )
+
+
+def downgrade() -> None:
+ """Downgrade schema - Remove BlockNote fields."""
+ op.drop_column("documents", "last_edited_at")
+ op.drop_column("documents", "content_needs_reindexing")
+ op.drop_column("documents", "blocknote_document")
diff --git a/surfsense_backend/app/celery_app.py b/surfsense_backend/app/celery_app.py
index 898ab9735..f7bea8cc3 100644
--- a/surfsense_backend/app/celery_app.py
+++ b/surfsense_backend/app/celery_app.py
@@ -63,6 +63,8 @@ def parse_schedule_interval(interval: str) -> dict:
"app.tasks.celery_tasks.podcast_tasks",
"app.tasks.celery_tasks.connector_tasks",
"app.tasks.celery_tasks.schedule_checker_task",
+ "app.tasks.celery_tasks.blocknote_migration_tasks",
+ "app.tasks.celery_tasks.document_reindex_tasks",
],
)
diff --git a/surfsense_backend/app/db.py b/surfsense_backend/app/db.py
index f3147a42b..20a4adc23 100644
--- a/surfsense_backend/app/db.py
+++ b/surfsense_backend/app/db.py
@@ -20,7 +20,7 @@
UniqueConstraint,
text,
)
-from sqlalchemy.dialects.postgresql import UUID
+from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, declared_attr, relationship
@@ -343,6 +343,17 @@ class Document(BaseModel, TimestampMixin):
unique_identifier_hash = Column(String, nullable=True, index=True, unique=True)
embedding = Column(Vector(config.embedding_model_instance.dimension))
+ # BlockNote live editing state (NULL when never edited)
+ blocknote_document = Column(JSONB, nullable=True)
+
+ # blocknote background reindex flag
+ content_needs_reindexing = Column(
+ Boolean, nullable=False, default=False, server_default=text("false")
+ )
+
+ # Track when blocknote document was last edited
+ last_edited_at = Column(TIMESTAMP(timezone=True), nullable=True)
+
search_space_id = Column(
Integer, ForeignKey("searchspaces.id", ondelete="CASCADE"), nullable=False
)
diff --git a/surfsense_backend/app/routes/__init__.py b/surfsense_backend/app/routes/__init__.py
index 127a8d927..4b829fe84 100644
--- a/surfsense_backend/app/routes/__init__.py
+++ b/surfsense_backend/app/routes/__init__.py
@@ -5,6 +5,7 @@
)
from .chats_routes import router as chats_router
from .documents_routes import router as documents_router
+from .editor_routes import router as editor_router
from .google_calendar_add_connector_route import (
router as google_calendar_add_connector_router,
)
@@ -23,6 +24,7 @@
router.include_router(search_spaces_router)
router.include_router(rbac_router) # RBAC routes for roles, members, invites
+router.include_router(editor_router)
router.include_router(documents_router)
router.include_router(podcasts_router)
router.include_router(chats_router)
diff --git a/surfsense_backend/app/routes/editor_routes.py b/surfsense_backend/app/routes/editor_routes.py
new file mode 100644
index 000000000..0bc7ed50b
--- /dev/null
+++ b/surfsense_backend/app/routes/editor_routes.py
@@ -0,0 +1,140 @@
+"""
+Editor routes for BlockNote document editing.
+"""
+
+from datetime import UTC, datetime
+from typing import Any
+
+from fastapi import APIRouter, Depends, HTTPException
+from sqlalchemy import select
+from sqlalchemy.ext.asyncio import AsyncSession
+
+from app.db import Document, SearchSpace, User, get_async_session
+from app.users import current_active_user
+
+router = APIRouter()
+
+
+@router.get("/documents/{document_id}/editor-content")
+async def get_editor_content(
+ document_id: int,
+ session: AsyncSession = Depends(get_async_session),
+ user: User = Depends(current_active_user),
+):
+ """
+ Get document content for editing.
+
+ Returns BlockNote JSON document. If blocknote_document is NULL,
+ attempts to generate it from chunks (lazy migration).
+ """
+ from sqlalchemy.orm import selectinload
+
+ result = await session.execute(
+ select(Document)
+ .options(selectinload(Document.chunks))
+ .join(SearchSpace)
+ .filter(Document.id == document_id, SearchSpace.user_id == user.id)
+ )
+ document = result.scalars().first()
+
+ if not document:
+ raise HTTPException(status_code=404, detail="Document not found")
+
+ # If blocknote_document exists, return it
+ if document.blocknote_document:
+ return {
+ "document_id": document.id,
+ "title": document.title,
+ "blocknote_document": document.blocknote_document,
+ "last_edited_at": document.last_edited_at.isoformat()
+ if document.last_edited_at
+ else None,
+ }
+
+ # Lazy migration: Try to generate blocknote_document from chunks
+ from app.utils.blocknote_converter import convert_markdown_to_blocknote
+
+ chunks = sorted(document.chunks, key=lambda c: c.id)
+
+ if not chunks:
+ raise HTTPException(
+ status_code=400,
+ detail="This document has no chunks and cannot be edited. Please re-upload to enable editing.",
+ )
+
+ # Reconstruct markdown from chunks
+ markdown_content = "\n\n".join(chunk.content for chunk in chunks)
+
+ if not markdown_content.strip():
+ raise HTTPException(
+ status_code=400,
+ detail="This document has empty content and cannot be edited.",
+ )
+
+ # Convert to BlockNote
+ blocknote_json = await convert_markdown_to_blocknote(markdown_content)
+
+ if not blocknote_json:
+ raise HTTPException(
+ status_code=500,
+ detail="Failed to convert document to editable format. Please try again later.",
+ )
+
+ # Save the generated blocknote_document (lazy migration)
+ document.blocknote_document = blocknote_json
+ document.content_needs_reindexing = False
+ document.last_edited_at = None
+ await session.commit()
+
+ return {
+ "document_id": document.id,
+ "title": document.title,
+ "blocknote_document": blocknote_json,
+ "last_edited_at": None,
+ }
+
+
+@router.post("/documents/{document_id}/save")
+async def save_document(
+ document_id: int,
+ data: dict[str, Any],
+ session: AsyncSession = Depends(get_async_session),
+ user: User = Depends(current_active_user),
+):
+ """
+ Save BlockNote document and trigger reindexing.
+ Called when user clicks 'Save & Exit'.
+ """
+ from app.tasks.celery_tasks.document_reindex_tasks import reindex_document_task
+
+ # Verify ownership
+ result = await session.execute(
+ select(Document)
+ .join(SearchSpace)
+ .filter(Document.id == document_id, SearchSpace.user_id == user.id)
+ )
+ document = result.scalars().first()
+
+ if not document:
+ raise HTTPException(status_code=404, detail="Document not found")
+
+ blocknote_document = data.get("blocknote_document")
+ if not blocknote_document:
+ raise HTTPException(status_code=400, detail="blocknote_document is required")
+
+ # Save BlockNote document
+ document.blocknote_document = blocknote_document
+ document.last_edited_at = datetime.now(UTC)
+ document.content_needs_reindexing = True
+
+ await session.commit()
+
+ # Queue reindex task
+ reindex_document_task.delay(document_id, str(user.id))
+
+ return {
+ "status": "saved",
+ "document_id": document_id,
+ "message": "Document saved and will be reindexed in the background",
+ "last_edited_at": document.last_edited_at.isoformat(),
+ }
diff --git a/surfsense_backend/app/tasks/celery_tasks/blocknote_migration_tasks.py b/surfsense_backend/app/tasks/celery_tasks/blocknote_migration_tasks.py
new file mode 100644
index 000000000..c945bcb04
--- /dev/null
+++ b/surfsense_backend/app/tasks/celery_tasks/blocknote_migration_tasks.py
@@ -0,0 +1,168 @@
+"""Celery tasks for populating blocknote_document for existing documents."""
+
+import logging
+
+from sqlalchemy import select
+from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
+from sqlalchemy.orm import selectinload
+from sqlalchemy.pool import NullPool
+
+from app.celery_app import celery_app
+from app.config import config
+from app.db import Document
+from app.utils.blocknote_converter import convert_markdown_to_blocknote
+
+logger = logging.getLogger(__name__)
+
+
+def get_celery_session_maker():
+ """
+ Create a new async session maker for Celery tasks.
+ This is necessary because Celery tasks run in a new event loop,
+ and the default session maker is bound to the main app's event loop.
+ """
+ engine = create_async_engine(
+ config.DATABASE_URL,
+ poolclass=NullPool,
+ echo=False,
+ )
+ return async_sessionmaker(engine, expire_on_commit=False)
+
+
+@celery_app.task(name="populate_blocknote_for_documents", bind=True)
+def populate_blocknote_for_documents_task(
+ self, document_ids: list[int] | None = None, batch_size: int = 50
+):
+ """
+ Celery task to populate blocknote_document for existing documents.
+
+ Args:
+ document_ids: Optional list of specific document IDs to process.
+ If None, processes all documents with blocknote_document IS NULL.
+ batch_size: Number of documents to process in each batch (default: 50)
+ """
+ import asyncio
+
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ try:
+ loop.run_until_complete(
+ _populate_blocknote_for_documents(document_ids, batch_size)
+ )
+ finally:
+ loop.close()
+
+
+async def _populate_blocknote_for_documents(
+ document_ids: list[int] | None = None, batch_size: int = 50
+):
+ """
+ Async function to populate blocknote_document for documents.
+
+ Args:
+ document_ids: Optional list of specific document IDs to process
+ batch_size: Number of documents to process per batch
+ """
+ async with get_celery_session_maker()() as session:
+ try:
+ # Build query for documents that need blocknote_document populated
+ query = select(Document).where(Document.blocknote_document.is_(None))
+
+ # If specific document IDs provided, filter by them
+ if document_ids:
+ query = query.where(Document.id.in_(document_ids))
+
+ # Load chunks relationship to avoid N+1 queries
+ query = query.options(selectinload(Document.chunks))
+
+ # Execute query
+ result = await session.execute(query)
+ documents = result.scalars().all()
+
+ total_documents = len(documents)
+ logger.info(f"Found {total_documents} documents to process")
+
+ if total_documents == 0:
+ logger.info("No documents to process")
+ return
+
+ # Process documents in batches
+ processed = 0
+ failed = 0
+
+ for i in range(0, total_documents, batch_size):
+ batch = documents[i : i + batch_size]
+ logger.info(
+ f"Processing batch {i // batch_size + 1}: documents {i + 1}-{min(i + batch_size, total_documents)}"
+ )
+
+ for document in batch:
+ try:
+ # Use preloaded chunks from selectinload - no need to query again
+ chunks = sorted(document.chunks, key=lambda c: c.id)
+
+ if not chunks:
+ logger.warning(
+ f"Document {document.id} ({document.title}) has no chunks, skipping"
+ )
+ failed += 1
+ continue
+
+ # Reconstruct markdown by concatenating chunk contents
+ markdown_content = "\n\n".join(
+ chunk.content for chunk in chunks
+ )
+
+ if not markdown_content or not markdown_content.strip():
+ logger.warning(
+ f"Document {document.id} ({document.title}) has empty markdown content, skipping"
+ )
+ failed += 1
+ continue
+
+ # Convert markdown to BlockNote JSON
+ blocknote_json = await convert_markdown_to_blocknote(
+ markdown_content
+ )
+
+ if not blocknote_json:
+ logger.warning(
+ f"Failed to convert markdown to BlockNote for document {document.id} ({document.title})"
+ )
+ failed += 1
+ continue
+
+ # Update document with blocknote_document (other fields already have correct defaults)
+ document.blocknote_document = blocknote_json
+
+ processed += 1
+
+ # Commit every batch_size documents to avoid long transactions
+ if processed % batch_size == 0:
+ await session.commit()
+ logger.info(
+ f"Committed batch: {processed} documents processed so far"
+ )
+
+ except Exception as e:
+ logger.error(
+ f"Error processing document {document.id} ({document.title}): {e}",
+ exc_info=True,
+ )
+ failed += 1
+ # Continue with next document instead of failing entire batch
+ continue
+
+ # Commit remaining changes in the batch
+ await session.commit()
+ logger.info(f"Completed batch {i // batch_size + 1}")
+
+ logger.info(
+ f"Migration complete: {processed} documents processed, {failed} failed"
+ )
+
+ except Exception as e:
+ await session.rollback()
+ logger.error(f"Error in blocknote migration task: {e}", exc_info=True)
+ raise
diff --git a/surfsense_backend/app/tasks/celery_tasks/document_reindex_tasks.py b/surfsense_backend/app/tasks/celery_tasks/document_reindex_tasks.py
new file mode 100644
index 000000000..8ab5309f2
--- /dev/null
+++ b/surfsense_backend/app/tasks/celery_tasks/document_reindex_tasks.py
@@ -0,0 +1,126 @@
+"""Celery tasks for reindexing edited documents."""
+
+import logging
+
+from sqlalchemy import delete, select
+from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
+from sqlalchemy.orm import selectinload
+from sqlalchemy.pool import NullPool
+
+from app.celery_app import celery_app
+from app.config import config
+from app.db import Document
+from app.services.llm_service import get_user_long_context_llm
+from app.utils.blocknote_converter import convert_blocknote_to_markdown
+from app.utils.document_converters import (
+ create_document_chunks,
+ generate_document_summary,
+)
+
+logger = logging.getLogger(__name__)
+
+
+def get_celery_session_maker():
+ """Create async session maker for Celery tasks."""
+ engine = create_async_engine(
+ config.DATABASE_URL,
+ poolclass=NullPool,
+ echo=False,
+ )
+ return async_sessionmaker(engine, expire_on_commit=False)
+
+
+@celery_app.task(name="reindex_document", bind=True)
+def reindex_document_task(self, document_id: int, user_id: str):
+ """
+ Celery task to reindex a document after editing.
+
+ Args:
+ document_id: ID of document to reindex
+ user_id: ID of user who edited the document
+ """
+ import asyncio
+
+ loop = asyncio.new_event_loop()
+ asyncio.set_event_loop(loop)
+
+ try:
+ loop.run_until_complete(_reindex_document(document_id, user_id))
+ finally:
+ loop.close()
+
+
+async def _reindex_document(document_id: int, user_id: str):
+ """Async function to reindex a document."""
+ async with get_celery_session_maker()() as session:
+ try:
+ # Get document
+ result = await session.execute(
+ select(Document)
+ .options(selectinload(Document.chunks)) # Eagerly load chunks
+ .where(Document.id == document_id)
+ )
+ document = result.scalars().first()
+
+ if not document:
+ logger.error(f"Document {document_id} not found")
+ return
+
+ if not document.blocknote_document:
+ logger.warning(f"Document {document_id} has no BlockNote content")
+ return
+
+ logger.info(f"Reindexing document {document_id} ({document.title})")
+
+ # 1. Convert BlockNote → Markdown
+ markdown_content = await convert_blocknote_to_markdown(
+ document.blocknote_document
+ )
+
+ if not markdown_content:
+ logger.error(f"Failed to convert document {document_id} to markdown")
+ return
+
+ # 2. Delete old chunks explicitly
+ from app.db import Chunk
+
+ await session.execute(delete(Chunk).where(Chunk.document_id == document_id))
+ await session.flush() # Ensure old chunks are deleted
+
+ # 3. Create new chunks
+ new_chunks = await create_document_chunks(markdown_content)
+
+ # 4. Add new chunks to session
+ for chunk in new_chunks:
+ chunk.document_id = document_id
+ session.add(chunk)
+
+ logger.info(f"Created {len(new_chunks)} chunks for document {document_id}")
+
+ # 5. Regenerate summary
+ user_llm = await get_user_long_context_llm(
+ session, user_id, document.search_space_id
+ )
+
+ document_metadata = {
+ "title": document.title,
+ "document_type": document.document_type.value,
+ }
+
+ summary_content, summary_embedding = await generate_document_summary(
+ markdown_content, user_llm, document_metadata
+ )
+
+ # 6. Update document
+ document.content = summary_content
+ document.embedding = summary_embedding
+ document.content_needs_reindexing = False
+
+ await session.commit()
+
+ logger.info(f"Successfully reindexed document {document_id}")
+
+ except Exception as e:
+ await session.rollback()
+ logger.error(f"Error reindexing document {document_id}: {e}", exc_info=True)
+ raise
diff --git a/surfsense_backend/app/tasks/document_processors/extension_processor.py b/surfsense_backend/app/tasks/document_processors/extension_processor.py
index 663093375..48e3efe27 100644
--- a/surfsense_backend/app/tasks/document_processors/extension_processor.py
+++ b/surfsense_backend/app/tasks/document_processors/extension_processor.py
@@ -145,6 +145,16 @@ async def add_extension_received_document(
# Process chunks
chunks = await create_document_chunks(content.pageContent)
+ from app.utils.blocknote_converter import convert_markdown_to_blocknote
+
+ # Convert markdown to BlockNote JSON
+ blocknote_json = await convert_markdown_to_blocknote(combined_document_string)
+ if not blocknote_json:
+ logging.warning(
+ f"Failed to convert extension document '{content.metadata.VisitedWebPageTitle}' "
+ f"to BlockNote JSON, document will not be editable"
+ )
+
# Update or create document
if existing_document:
# Update existing document
@@ -154,6 +164,7 @@ async def add_extension_received_document(
existing_document.embedding = summary_embedding
existing_document.document_metadata = content.metadata.model_dump()
existing_document.chunks = chunks
+ existing_document.blocknote_document = blocknote_json
await session.commit()
await session.refresh(existing_document)
@@ -170,6 +181,7 @@ async def add_extension_received_document(
chunks=chunks,
content_hash=content_hash,
unique_identifier_hash=unique_identifier_hash,
+ blocknote_document=blocknote_json,
)
session.add(document)
diff --git a/surfsense_backend/app/tasks/document_processors/file_processors.py b/surfsense_backend/app/tasks/document_processors/file_processors.py
index 859f6a25d..4ae04e050 100644
--- a/surfsense_backend/app/tasks/document_processors/file_processors.py
+++ b/surfsense_backend/app/tasks/document_processors/file_processors.py
@@ -100,6 +100,15 @@ async def add_received_file_document_using_unstructured(
# Process chunks
chunks = await create_document_chunks(file_in_markdown)
+ from app.utils.blocknote_converter import convert_markdown_to_blocknote
+
+ # Convert markdown to BlockNote JSON
+ blocknote_json = await convert_markdown_to_blocknote(file_in_markdown)
+ if not blocknote_json:
+ logging.warning(
+ f"Failed to convert {file_name} to BlockNote JSON, document will not be editable"
+ )
+
# Update or create document
if existing_document:
# Update existing document
@@ -112,6 +121,9 @@ async def add_received_file_document_using_unstructured(
"ETL_SERVICE": "UNSTRUCTURED",
}
existing_document.chunks = chunks
+ existing_document.blocknote_document = blocknote_json
+ existing_document.content_needs_reindexing = False
+ existing_document.last_edited_at = None
await session.commit()
await session.refresh(existing_document)
@@ -131,6 +143,9 @@ async def add_received_file_document_using_unstructured(
chunks=chunks,
content_hash=content_hash,
unique_identifier_hash=unique_identifier_hash,
+ blocknote_document=blocknote_json,
+ content_needs_reindexing=False,
+ last_edited_at=None,
)
session.add(document)
@@ -214,6 +229,15 @@ async def add_received_file_document_using_llamacloud(
# Process chunks
chunks = await create_document_chunks(file_in_markdown)
+ from app.utils.blocknote_converter import convert_markdown_to_blocknote
+
+ # Convert markdown to BlockNote JSON
+ blocknote_json = await convert_markdown_to_blocknote(file_in_markdown)
+ if not blocknote_json:
+ logging.warning(
+ f"Failed to convert {file_name} to BlockNote JSON, document will not be editable"
+ )
+
# Update or create document
if existing_document:
# Update existing document
@@ -226,6 +250,9 @@ async def add_received_file_document_using_llamacloud(
"ETL_SERVICE": "LLAMACLOUD",
}
existing_document.chunks = chunks
+ existing_document.blocknote_document = blocknote_json
+ existing_document.content_needs_reindexing = False
+ existing_document.last_edited_at = None
await session.commit()
await session.refresh(existing_document)
@@ -245,6 +272,9 @@ async def add_received_file_document_using_llamacloud(
chunks=chunks,
content_hash=content_hash,
unique_identifier_hash=unique_identifier_hash,
+ blocknote_document=blocknote_json,
+ content_needs_reindexing=False,
+ last_edited_at=None,
)
session.add(document)
@@ -353,6 +383,15 @@ async def add_received_file_document_using_docling(
# Process chunks
chunks = await create_document_chunks(file_in_markdown)
+ from app.utils.blocknote_converter import convert_markdown_to_blocknote
+
+ # Convert markdown to BlockNote JSON
+ blocknote_json = await convert_markdown_to_blocknote(file_in_markdown)
+ if not blocknote_json:
+ logging.warning(
+ f"Failed to convert {file_name} to BlockNote JSON, document will not be editable"
+ )
+
# Update or create document
if existing_document:
# Update existing document
@@ -365,6 +404,9 @@ async def add_received_file_document_using_docling(
"ETL_SERVICE": "DOCLING",
}
existing_document.chunks = chunks
+ existing_document.blocknote_document = blocknote_json
+ existing_document.content_needs_reindexing = False
+ existing_document.last_edited_at = None
await session.commit()
await session.refresh(existing_document)
@@ -384,6 +426,9 @@ async def add_received_file_document_using_docling(
chunks=chunks,
content_hash=content_hash,
unique_identifier_hash=unique_identifier_hash,
+ blocknote_document=blocknote_json,
+ content_needs_reindexing=False,
+ last_edited_at=None,
)
session.add(document)
diff --git a/surfsense_backend/app/tasks/document_processors/markdown_processor.py b/surfsense_backend/app/tasks/document_processors/markdown_processor.py
index 76215ed51..3036071c9 100644
--- a/surfsense_backend/app/tasks/document_processors/markdown_processor.py
+++ b/surfsense_backend/app/tasks/document_processors/markdown_processor.py
@@ -110,6 +110,15 @@ async def add_received_markdown_file_document(
# Process chunks
chunks = await create_document_chunks(file_in_markdown)
+ from app.utils.blocknote_converter import convert_markdown_to_blocknote
+
+ # Convert to BlockNote JSON
+ blocknote_json = await convert_markdown_to_blocknote(file_in_markdown)
+ if not blocknote_json:
+ logging.warning(
+ f"Failed to convert {file_name} to BlockNote JSON, document will not be editable"
+ )
+
# Update or create document
if existing_document:
# Update existing document
@@ -121,6 +130,7 @@ async def add_received_markdown_file_document(
"FILE_NAME": file_name,
}
existing_document.chunks = chunks
+ existing_document.blocknote_document = blocknote_json
await session.commit()
await session.refresh(existing_document)
@@ -139,6 +149,7 @@ async def add_received_markdown_file_document(
chunks=chunks,
content_hash=content_hash,
unique_identifier_hash=unique_identifier_hash,
+ blocknote_document=blocknote_json,
)
session.add(document)
diff --git a/surfsense_backend/app/tasks/document_processors/youtube_processor.py b/surfsense_backend/app/tasks/document_processors/youtube_processor.py
index c7d396974..332e775e1 100644
--- a/surfsense_backend/app/tasks/document_processors/youtube_processor.py
+++ b/surfsense_backend/app/tasks/document_processors/youtube_processor.py
@@ -291,6 +291,16 @@ async def add_youtube_video_document(
{"stage": "chunk_processing"},
)
+ from app.utils.blocknote_converter import convert_markdown_to_blocknote
+
+ # Convert transcript to BlockNote JSON
+ blocknote_json = await convert_markdown_to_blocknote(combined_document_string)
+ if not blocknote_json:
+ logging.warning(
+ f"Failed to convert YouTube video '{video_id}' to BlockNote JSON, "
+ "document will not be editable"
+ )
+
chunks = await create_document_chunks(combined_document_string)
# Update or create document
@@ -314,6 +324,7 @@ async def add_youtube_video_document(
"thumbnail": video_data.get("thumbnail_url", ""),
}
existing_document.chunks = chunks
+ existing_document.blocknote_document = blocknote_json
await session.commit()
await session.refresh(existing_document)
@@ -342,6 +353,7 @@ async def add_youtube_video_document(
search_space_id=search_space_id,
content_hash=content_hash,
unique_identifier_hash=unique_identifier_hash,
+ blocknote_document=blocknote_json,
)
session.add(document)
diff --git a/surfsense_backend/app/utils/blocknote_converter.py b/surfsense_backend/app/utils/blocknote_converter.py
new file mode 100644
index 000000000..b57a82996
--- /dev/null
+++ b/surfsense_backend/app/utils/blocknote_converter.py
@@ -0,0 +1,123 @@
+import logging
+from typing import Any
+
+import httpx
+
+from app.config import config
+
+logger = logging.getLogger(__name__)
+
+
+async def convert_markdown_to_blocknote(markdown: str) -> dict[str, Any] | None:
+ """
+ Convert markdown to BlockNote JSON via Next.js API.
+
+ Args:
+ markdown: Markdown string to convert
+
+ Returns:
+ BlockNote document as dict, or None if conversion fails
+ """
+ if not markdown or not markdown.strip():
+ logger.warning("Empty markdown provided for conversion")
+ return None
+
+ if not markdown or len(markdown) < 10:
+ logger.warning("Markdown became too short after sanitization")
+ # Return a minimal BlockNote document
+ return [
+ {
+ "type": "paragraph",
+ "content": [
+ {
+ "type": "text",
+ "text": "Document content could not be converted for editing.",
+ "styles": {},
+ }
+ ],
+ "children": [],
+ }
+ ]
+
+ async with httpx.AsyncClient() as client:
+ try:
+ response = await client.post(
+ f"{config.NEXT_FRONTEND_URL}/api/convert-to-blocknote",
+ json={"markdown": markdown},
+ timeout=30.0,
+ )
+ response.raise_for_status()
+ data = response.json()
+ blocknote_document = data.get("blocknote_document")
+
+ if blocknote_document:
+ logger.info(
+ f"Successfully converted markdown to BlockNote (original: {len(markdown)} chars, sanitized: {len(markdown)} chars)"
+ )
+ return blocknote_document
+ else:
+ logger.warning("Next.js API returned empty blocknote_document")
+ return None
+
+ except httpx.TimeoutException:
+ logger.error("Timeout converting markdown to BlockNote after 30s")
+ return None
+ except httpx.HTTPStatusError as e:
+ logger.error(
+ f"HTTP error converting markdown to BlockNote: {e.response.status_code} - {e.response.text}"
+ )
+ # Log first 1000 chars of problematic markdown for debugging
+ logger.debug(f"Problematic markdown sample: {markdown[:1000]}")
+ return None
+ except Exception as e:
+ logger.error(f"Failed to convert markdown to BlockNote: {e}", exc_info=True)
+ return None
+
+
+async def convert_blocknote_to_markdown(
+ blocknote_document: dict[str, Any] | list[dict[str, Any]],
+) -> str | None:
+ """
+ Convert BlockNote JSON to markdown via Next.js API.
+
+ Args:
+ blocknote_document: BlockNote document as dict or list of blocks
+
+ Returns:
+ Markdown string, or None if conversion fails
+ """
+ if not blocknote_document:
+ logger.warning("Empty BlockNote document provided for conversion")
+ return None
+
+ async with httpx.AsyncClient() as client:
+ try:
+ response = await client.post(
+ f"{config.NEXT_FRONTEND_URL}/api/convert-to-markdown",
+ json={"blocknote_document": blocknote_document},
+ timeout=30.0,
+ )
+ response.raise_for_status()
+ data = response.json()
+ markdown = data.get("markdown")
+
+ if markdown:
+ logger.info(
+ f"Successfully converted BlockNote to markdown ({len(markdown)} chars)"
+ )
+ return markdown
+ else:
+ logger.warning("Next.js API returned empty markdown")
+ return None
+
+ except httpx.TimeoutException:
+ logger.error("Timeout converting BlockNote to markdown after 30s")
+ return None
+ except httpx.HTTPStatusError as e:
+ logger.error(
+ f"HTTP error converting BlockNote to markdown: {e.response.status_code} - {e.response.text}"
+ )
+ return None
+ except Exception as e:
+ logger.error(f"Failed to convert BlockNote to markdown: {e}", exc_info=True)
+ return None
diff --git a/surfsense_web/app/api/convert-to-blocknote/route.ts b/surfsense_web/app/api/convert-to-blocknote/route.ts
new file mode 100644
index 000000000..e11c9cb47
--- /dev/null
+++ b/surfsense_web/app/api/convert-to-blocknote/route.ts
@@ -0,0 +1,40 @@
+import { ServerBlockNoteEditor } from "@blocknote/server-util";
+import { type NextRequest, NextResponse } from "next/server";
+
+export async function POST(request: NextRequest) {
+ try {
+ const { markdown } = await request.json();
+
+ if (!markdown || typeof markdown !== "string") {
+ return NextResponse.json({ error: "Markdown string is required" }, { status: 400 });
+ }
+
+ // Log raw markdown input before conversion
+ // console.log(`\n${"=".repeat(80)}`);
+ // console.log("RAW MARKDOWN INPUT (BEFORE CONVERSION):");
+ // console.log("=".repeat(80));
+ // console.log(markdown);
+ // console.log(`${"=".repeat(80)}\n`);
+
+ // Create server-side editor instance
+ const editor = ServerBlockNoteEditor.create();
+
+ // Convert markdown directly to BlockNote blocks
+ const blocks = await editor.tryParseMarkdownToBlocks(markdown);
+
+ if (!blocks || blocks.length === 0) {
+ throw new Error("Markdown parsing returned no blocks");
+ }
+
+ return NextResponse.json({ blocknote_document: blocks });
+ } catch (error: any) {
+ console.error("Failed to convert markdown to BlockNote:", error);
+ return NextResponse.json(
+ {
+ error: "Failed to convert markdown to BlockNote blocks",
+ details: error.message,
+ },
+ { status: 500 }
+ );
+ }
+}
diff --git a/surfsense_web/app/api/convert-to-markdown/route.ts b/surfsense_web/app/api/convert-to-markdown/route.ts
new file mode 100644
index 000000000..7005a800f
--- /dev/null
+++ b/surfsense_web/app/api/convert-to-markdown/route.ts
@@ -0,0 +1,28 @@
+import { ServerBlockNoteEditor } from "@blocknote/server-util";
+import { type NextRequest, NextResponse } from "next/server";
+
+export async function POST(request: NextRequest) {
+ try {
+ const { blocknote_document } = await request.json();
+
+ if (!blocknote_document || !Array.isArray(blocknote_document)) {
+ return NextResponse.json({ error: "BlockNote document array is required" }, { status: 400 });
+ }
+
+ // Create server-side editor instance
+ const editor = ServerBlockNoteEditor.create();
+
+ // Convert BlockNote blocks to markdown
+ const markdown = await editor.blocksToMarkdownLossy(blocknote_document);
+
+ return NextResponse.json({
+ markdown,
+ });
+ } catch (error) {
+ console.error("Failed to convert BlockNote to markdown:", error);
+ return NextResponse.json(
+ { error: "Failed to convert BlockNote blocks to markdown" },
+ { status: 500 }
+ );
+ }
+}
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx
index 20f2be15f..0483940e0 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/DocumentsTableShell.tsx
@@ -309,6 +309,7 @@ export function DocumentsTableShell({
refreshDocuments={async () => {
await onRefresh();
}}
+ searchSpaceId={searchSpaceId as string}
/>
@@ -340,6 +341,7 @@ export function DocumentsTableShell({
refreshDocuments={async () => {
await onRefresh();
}}
+ searchSpaceId={searchSpaceId as string}
/>
diff --git a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/RowActions.tsx b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/RowActions.tsx
index bd1e182d9..ea4c66228 100644
--- a/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/RowActions.tsx
+++ b/surfsense_web/app/dashboard/[search_space_id]/documents/(manage)/components/RowActions.tsx
@@ -1,6 +1,7 @@
"use client";
-import { MoreHorizontal } from "lucide-react";
+import { FileText, MoreHorizontal, Pencil, Trash2 } from "lucide-react";
+import { useRouter } from "next/navigation";
import { useState } from "react";
import { toast } from "sonner";
import { JsonMetadataViewer } from "@/components/json-metadata-viewer";
@@ -28,13 +29,16 @@ export function RowActions({
document,
deleteDocument,
refreshDocuments,
+ searchSpaceId,
}: {
document: Document;
deleteDocument: (id: number) => Promise
;
refreshDocuments: () => Promise;
+ searchSpaceId: string;
}) {
const [isOpen, setIsOpen] = useState(false);
const [isDeleting, setIsDeleting] = useState(false);
+ const router = useRouter();
const handleDelete = async () => {
setIsDeleting(true);
@@ -52,6 +56,10 @@ export function RowActions({
}
};
+ const handleEdit = () => {
+ router.push(`/dashboard/${searchSpaceId}/editor/${document.id}`);
+ };
+
return (
@@ -62,11 +70,17 @@ export function RowActions({
+
+
+ Edit Document
+
+
e.preventDefault()}>
+
View Metadata
}
@@ -81,6 +95,7 @@ export function RowActions({
setIsOpen(true);
}}
>
+
Delete
diff --git a/surfsense_web/app/dashboard/[search_space_id]/editor/[documentId]/page.tsx b/surfsense_web/app/dashboard/[search_space_id]/editor/[documentId]/page.tsx
new file mode 100644
index 000000000..544834372
--- /dev/null
+++ b/surfsense_web/app/dashboard/[search_space_id]/editor/[documentId]/page.tsx
@@ -0,0 +1,263 @@
+"use client";
+
+import { AlertCircle, FileText, Loader2, Save, X } from "lucide-react";
+import { motion } from "motion/react";
+import { useParams, useRouter } from "next/navigation";
+import { useEffect, useState } from "react";
+import { toast } from "sonner";
+import { BlockNoteEditor } from "@/components/DynamicBlockNoteEditor";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import { Separator } from "@/components/ui/separator";
+
+interface EditorContent {
+ document_id: number;
+ title: string;
+ blocknote_document: any;
+ last_edited_at: string | null;
+}
+
+export default function EditorPage() {
+ const params = useParams();
+ const router = useRouter();
+ const documentId = params.documentId as string;
+
+ const [document, setDocument] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [saving, setSaving] = useState(false);
+ const [editorContent, setEditorContent] = useState(null);
+ const [error, setError] = useState(null);
+ const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
+
+ // Get auth token
+ const token =
+ typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") : null;
+
+ // Fetch document content - DIRECT CALL TO FASTAPI
+ useEffect(() => {
+ async function fetchDocument() {
+ if (!token) {
+ console.error("No auth token found");
+ setError("Please login to access the editor");
+ setLoading(false);
+ return;
+ }
+
+ try {
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/${documentId}/editor-content`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ );
+
+ if (!response.ok) {
+ const errorData = await response
+ .json()
+ .catch(() => ({ detail: "Failed to fetch document" }));
+ throw new Error(errorData.detail || "Failed to fetch document");
+ }
+
+ const data = await response.json();
+
+ // Check if blocknote_document exists
+ if (!data.blocknote_document) {
+ setError(
+ "This document does not have BlockNote content. Please re-upload the document to enable editing."
+ );
+ setLoading(false);
+ return;
+ }
+
+ setDocument(data);
+ setEditorContent(data.blocknote_document);
+ setError(null);
+ } catch (error) {
+ console.error("Error fetching document:", error);
+ setError(
+ error instanceof Error ? error.message : "Failed to fetch document. Please try again."
+ );
+ } finally {
+ setLoading(false);
+ }
+ }
+
+ if (documentId && token) {
+ fetchDocument();
+ }
+ }, [documentId, token]);
+
+ // Track changes to mark as unsaved
+ useEffect(() => {
+ if (editorContent && document) {
+ setHasUnsavedChanges(true);
+ }
+ }, [editorContent, document]);
+
+ // TODO: Maybe add Auto-save every 30 seconds - DIRECT CALL TO FASTAPI
+
+ // Save and exit - DIRECT CALL TO FASTAPI
+ const handleSave = async () => {
+ if (!token) {
+ toast.error("Please login to save");
+ return;
+ }
+
+ if (!editorContent) {
+ toast.error("No content to save");
+ return;
+ }
+
+ setSaving(true);
+ try {
+ // Save blocknote_document and trigger reindexing in background
+ const response = await fetch(
+ `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/${documentId}/save`,
+ {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({ blocknote_document: editorContent }),
+ }
+ );
+
+ if (!response.ok) {
+ const errorData = await response
+ .json()
+ .catch(() => ({ detail: "Failed to save document" }));
+ throw new Error(errorData.detail || "Failed to save document");
+ }
+
+ setHasUnsavedChanges(false);
+ toast.success("Document saved! Reindexing in background...");
+
+ // Small delay before redirect to show success message
+ setTimeout(() => {
+ router.push(`/dashboard/${params.search_space_id}/documents`);
+ }, 500);
+ } catch (error) {
+ console.error("Error saving document:", error);
+ toast.error(
+ error instanceof Error ? error.message : "Failed to save document. Please try again."
+ );
+ } finally {
+ setSaving(false);
+ }
+ };
+
+ const handleCancel = () => {
+ if (hasUnsavedChanges) {
+ if (confirm("You have unsaved changes. Are you sure you want to leave?")) {
+ router.back();
+ }
+ } else {
+ router.back();
+ }
+ };
+
+ if (loading) {
+ return (
+
+
+
+
+ Loading editor...
+
+
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
+
+
+ {error}
+
+
+
+
+
+
+
+ );
+ }
+
+ if (!document) {
+ return (
+
+
+
+
+ Document not found
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Toolbar */}
+
+
+
+
+
{document.title}
+ {hasUnsavedChanges &&
Unsaved changes
}
+
+
+
+
+
+
+
+
+
+ {/* Editor Container */}
+
+
+ );
+}
diff --git a/surfsense_web/components/BlockNoteEditor.tsx b/surfsense_web/components/BlockNoteEditor.tsx
new file mode 100644
index 000000000..8064a0dc4
--- /dev/null
+++ b/surfsense_web/components/BlockNoteEditor.tsx
@@ -0,0 +1,68 @@
+"use client";
+
+import { useTheme } from "next-themes";
+import { useEffect, useMemo, useRef } from "react";
+import "@blocknote/core/fonts/inter.css";
+import "@blocknote/mantine/style.css";
+import { BlockNoteView } from "@blocknote/mantine";
+import { useCreateBlockNote } from "@blocknote/react";
+
+interface BlockNoteEditorProps {
+ initialContent?: any;
+ onChange?: (content: any) => void;
+}
+
+export default function BlockNoteEditor({ initialContent, onChange }: BlockNoteEditorProps) {
+ const { resolvedTheme } = useTheme();
+
+ // Track the initial content to prevent re-initialization
+ const initialContentRef = useRef(null);
+ const isInitializedRef = useRef(false);
+
+ // Creates a new editor instance - only use initialContent on first render
+ const editor = useCreateBlockNote({
+ initialContent: initialContentRef.current === null ? initialContent || undefined : undefined,
+ });
+
+ // Store initial content on first render only
+ useEffect(() => {
+ if (initialContent && initialContentRef.current === null) {
+ initialContentRef.current = initialContent;
+ isInitializedRef.current = true;
+ }
+ }, [initialContent]);
+
+ // Call onChange when document changes (but don't update from props)
+ useEffect(() => {
+ if (!onChange || !editor || !isInitializedRef.current) return;
+
+ const handleChange = () => {
+ onChange(editor.document);
+ };
+
+ // Subscribe to document changes
+ const unsubscribe = editor.onChange(handleChange);
+
+ return () => {
+ unsubscribe();
+ };
+ }, [editor, onChange]);
+
+ // Determine theme for BlockNote with custom dark mode background
+ const blockNoteTheme = useMemo(() => {
+ if (resolvedTheme === "dark") {
+ // Custom dark theme - only override editor background, let BlockNote handle the rest
+ return {
+ colors: {
+ editor: {
+ background: "#0A0A0A", // Custom dark background
+ },
+ },
+ };
+ }
+ return "light" as const;
+ }, [resolvedTheme]);
+
+ // Renders the editor instance
+ return ;
+}
diff --git a/surfsense_web/components/DynamicBlockNoteEditor.tsx b/surfsense_web/components/DynamicBlockNoteEditor.tsx
new file mode 100644
index 000000000..60fc6b11c
--- /dev/null
+++ b/surfsense_web/components/DynamicBlockNoteEditor.tsx
@@ -0,0 +1,6 @@
+"use client";
+
+import dynamic from "next/dynamic";
+
+// Dynamically import BlockNote editor with SSR disabled
+export const BlockNoteEditor = dynamic(() => import("./BlockNoteEditor"), { ssr: false });
diff --git a/surfsense_web/components/dashboard-breadcrumb.tsx b/surfsense_web/components/dashboard-breadcrumb.tsx
index 3e2e5199e..a05368eba 100644
--- a/surfsense_web/components/dashboard-breadcrumb.tsx
+++ b/surfsense_web/components/dashboard-breadcrumb.tsx
@@ -3,7 +3,7 @@
import { useAtomValue } from "jotai";
import { usePathname } from "next/navigation";
import { useTranslations } from "next-intl";
-import React, { useEffect } from "react";
+import React, { useEffect, useState } from "react";
import { activeChatAtom } from "@/atoms/chats/chat-query.atoms";
import {
Breadcrumb,
@@ -34,6 +34,41 @@ export function DashboardBreadcrumb() {
autoFetch: !!searchSpaceId,
});
+ // State to store document title for editor breadcrumb
+ const [documentTitle, setDocumentTitle] = useState(null);
+
+ // Fetch document title when on editor page
+ useEffect(() => {
+ if (segments[2] === "editor" && segments[3] && searchSpaceId) {
+ const documentId = segments[3];
+ const token =
+ typeof window !== "undefined" ? localStorage.getItem("surfsense_bearer_token") : null;
+
+ if (token) {
+ fetch(
+ `${process.env.NEXT_PUBLIC_FASTAPI_BACKEND_URL}/api/v1/documents/${documentId}/editor-content`,
+ {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ },
+ }
+ )
+ .then((res) => res.json())
+ .then((data) => {
+ if (data.title) {
+ setDocumentTitle(data.title);
+ }
+ })
+ .catch(() => {
+ // If fetch fails, just use the document ID
+ setDocumentTitle(null);
+ });
+ }
+ } else {
+ setDocumentTitle(null);
+ }
+ }, [segments, searchSpaceId]);
+
// Parse the pathname to create breadcrumb items
const generateBreadcrumbs = (path: string): BreadcrumbItemInterface[] => {
const segments = path.split("/").filter(Boolean);
@@ -66,6 +101,7 @@ export function DashboardBreadcrumb() {
logs: t("logs"),
chats: t("chats"),
settings: t("settings"),
+ editor: t("editor"),
};
sectionLabel = sectionLabels[section] || sectionLabel;
@@ -73,7 +109,21 @@ export function DashboardBreadcrumb() {
// Handle sub-sections
if (segments[3]) {
const subSection = segments[3];
- let subSectionLabel = subSection.charAt(0).toUpperCase() + subSection.slice(1);
+
+ // Handle editor sub-sections (document ID)
+ if (section === "editor") {
+ const documentLabel = documentTitle || subSection;
+ breadcrumbs.push({
+ label: t("documents"),
+ href: `/dashboard/${segments[1]}/documents`,
+ });
+ breadcrumbs.push({
+ label: sectionLabel,
+ href: `/dashboard/${segments[1]}/documents`,
+ });
+ breadcrumbs.push({ label: documentLabel });
+ return breadcrumbs;
+ }
// Handle sources sub-sections
if (section === "sources") {
@@ -81,7 +131,7 @@ export function DashboardBreadcrumb() {
add: "Add Sources",
};
- const sourceLabel = sourceLabels[subSection] || subSectionLabel;
+ const sourceLabel = sourceLabels[subSection] || subSection;
breadcrumbs.push({
label: "Sources",
href: `/dashboard/${segments[1]}/sources`,
@@ -98,7 +148,7 @@ export function DashboardBreadcrumb() {
webpage: t("add_webpages"),
};
- const documentLabel = documentLabels[subSection] || subSectionLabel;
+ const documentLabel = documentLabels[subSection] || subSection;
breadcrumbs.push({
label: t("documents"),
href: `/dashboard/${segments[1]}/documents`,
@@ -159,7 +209,7 @@ export function DashboardBreadcrumb() {
manage: t("manage_connectors"),
};
- const connectorLabel = connectorLabels[subSection] || subSectionLabel;
+ const connectorLabel = connectorLabels[subSection] || subSection;
breadcrumbs.push({
label: t("connectors"),
href: `/dashboard/${segments[1]}/connectors`,
@@ -169,6 +219,7 @@ export function DashboardBreadcrumb() {
}
// Handle other sub-sections
+ let subSectionLabel = subSection.charAt(0).toUpperCase() + subSection.slice(1);
const subSectionLabels: Record = {
upload: t("upload_documents"),
youtube: t("add_youtube"),
diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json
index 45663859d..e4b46ad58 100644
--- a/surfsense_web/messages/en.json
+++ b/surfsense_web/messages/en.json
@@ -621,6 +621,7 @@
"documents": "Documents",
"connectors": "Connectors",
"podcasts": "Podcasts",
+ "editor": "Editor",
"logs": "Logs",
"chats": "Chats",
"settings": "Settings",
diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json
index 809b654a5..f1ec9a6fb 100644
--- a/surfsense_web/messages/zh.json
+++ b/surfsense_web/messages/zh.json
@@ -621,6 +621,7 @@
"documents": "文档",
"connectors": "连接器",
"podcasts": "播客",
+ "editor": "编辑器",
"logs": "日志",
"chats": "聊天",
"settings": "设置",
diff --git a/surfsense_web/next.config.ts b/surfsense_web/next.config.ts
index aca3e2d37..da58fa10c 100644
--- a/surfsense_web/next.config.ts
+++ b/surfsense_web/next.config.ts
@@ -7,6 +7,8 @@ const withNextIntl = createNextIntlPlugin("./i18n/request.ts");
const nextConfig: NextConfig = {
output: "standalone",
+ // Disable StrictMode for BlockNote compatibility with React 19/Next 15
+ reactStrictMode: false,
typescript: {
ignoreBuildErrors: true,
},
@@ -21,6 +23,17 @@ const nextConfig: NextConfig = {
},
],
},
+ // Mark BlockNote server packages as external
+ serverExternalPackages: ["@blocknote/server-util"],
+
+ // Configure webpack to handle blocknote packages
+ webpack: (config, { isServer }) => {
+ if (isServer) {
+ // Don't bundle these packages on the server
+ config.externals = [...(config.externals || []), "@blocknote/server-util"];
+ }
+ return config;
+ },
};
// Wrap the config with MDX and next-intl plugins
diff --git a/surfsense_web/package.json b/surfsense_web/package.json
index a9de1d069..7f97111b9 100644
--- a/surfsense_web/package.json
+++ b/surfsense_web/package.json
@@ -22,6 +22,10 @@
},
"dependencies": {
"@ai-sdk/react": "^1.2.12",
+ "@blocknote/core": "^0.42.3",
+ "@blocknote/mantine": "^0.42.3",
+ "@blocknote/react": "^0.42.3",
+ "@blocknote/server-util": "^0.42.3",
"@hookform/resolvers": "^4.1.3",
"@llamaindex/chat-ui": "^0.5.17",
"@next/third-parties": "^15.5.6",
diff --git a/surfsense_web/pnpm-lock.yaml b/surfsense_web/pnpm-lock.yaml
index bee0c6c56..285099816 100644
--- a/surfsense_web/pnpm-lock.yaml
+++ b/surfsense_web/pnpm-lock.yaml
@@ -11,6 +11,18 @@ importers:
'@ai-sdk/react':
specifier: ^1.2.12
version: 1.2.12(react@19.1.0)(zod@3.25.76)
+ '@blocknote/core':
+ specifier: ^0.42.3
+ version: 0.42.3(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(highlight.js@11.11.1)
+ '@blocknote/mantine':
+ specifier: ^0.42.3
+ version: 0.42.3(@floating-ui/dom@1.7.4)(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.3.9(react@19.1.0))(@mantine/utils@6.0.22(react@19.1.0))(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(highlight.js@11.11.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@blocknote/react':
+ specifier: ^0.42.3
+ version: 0.42.3(@floating-ui/dom@1.7.4)(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(highlight.js@11.11.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@blocknote/server-util':
+ specifier: ^0.42.3
+ version: 0.42.3(@floating-ui/dom@1.7.4)(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(highlight.js@11.11.1)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@hookform/resolvers':
specifier: ^4.1.3
version: 4.1.3(react-hook-form@7.61.1(react@19.1.0))
@@ -136,7 +148,7 @@ importers:
version: 15.6.6(@types/react@19.1.8)(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
fumadocs-mdx:
specifier: ^11.7.1
- version: 11.7.1(acorn@8.14.0)(fumadocs-core@15.6.6(@types/react@19.1.8)(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
+ version: 11.7.1(acorn@8.15.0)(fumadocs-core@15.6.6(@types/react@19.1.8)(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0)
fumadocs-ui:
specifier: ^15.6.6
version: 15.6.6(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tailwindcss@4.1.11)
@@ -310,6 +322,9 @@ packages:
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'}
+ '@asamuzakjp/css-color@3.2.0':
+ resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==}
+
'@babel/runtime@7.26.9':
resolution: {integrity: sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==}
engines: {node: '>=6.9.0'}
@@ -367,6 +382,35 @@ packages:
cpu: [x64]
os: [win32]
+ '@blocknote/core@0.42.3':
+ resolution: {integrity: sha512-wtZki6Gok5Ac9Ek6QTQztcDymstEQgVCisJwiUZTWXh8CD4UKfnIxM7C9+6eEnZMmQ8GNTvRf1HXFl+E4N78VA==}
+ peerDependencies:
+ '@hocuspocus/provider': ^2.15.2
+ peerDependenciesMeta:
+ '@hocuspocus/provider':
+ optional: true
+
+ '@blocknote/mantine@0.42.3':
+ resolution: {integrity: sha512-xzLweZG1KfFoOp/aSHTXE10IrfEHnhDlP0C2Qt2eNO2IHHa7l8XZJpIGhCoVMsn0yylm91OSynNfTO7JkZZi8w==}
+ peerDependencies:
+ '@mantine/core': ^8.3.4
+ '@mantine/hooks': ^8.3.4
+ '@mantine/utils': ^6.0.22
+ react: ^18.0 || ^19.0 || >= 19.0.0-rc
+ react-dom: ^18.0 || ^19.0 || >= 19.0.0-rc
+
+ '@blocknote/react@0.42.3':
+ resolution: {integrity: sha512-YnrQ1uyezDbaxYcFstWOJ2r8BMxqwwEc7QAhrEjCMEyBAiOxSCPnrM4/GE2mOgCS0Xa9wIp2LDoPQP2Syv+2EA==}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || >= 19.0.0-rc
+ react-dom: ^18.0 || ^19.0 || >= 19.0.0-rc
+
+ '@blocknote/server-util@0.42.3':
+ resolution: {integrity: sha512-M+jtKeC2aHOYBp6GQ0YR19iv0/0f1HElrrnKwlaSPbwR6bw6tg+yb3yQkaJJioLTpd2X2Z/RwcEvxSJGnlZ81w==}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || >= 19.0.0-rc
+ react-dom: ^18.0 || ^19.0 || >= 19.0.0-rc
+
'@codemirror/autocomplete@6.18.6':
resolution: {integrity: sha512-PHHBXFomUs5DF+9tCOM/UoW6XQ4R44lLNNhRaW9PKPTU0D7lIjRg3ElxaJnTwsl/oHiR93WSXDBrekhoUGCPtg==}
@@ -472,6 +516,34 @@ packages:
react: ^16.8.0 || ^17 || ^18 || ^19
react-dom: ^16.8.0 || ^17 || ^18 || ^19
+ '@csstools/color-helpers@5.1.0':
+ resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==}
+ engines: {node: '>=18'}
+
+ '@csstools/css-calc@2.1.4':
+ resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-color-parser@3.1.0':
+ resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-parser-algorithms': ^3.0.5
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5':
+ resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ '@csstools/css-tokenizer': ^3.0.4
+
+ '@csstools/css-tokenizer@3.0.4':
+ resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==}
+ engines: {node: '>=18'}
+
'@date-fns/tz@1.2.0':
resolution: {integrity: sha512-LBrd7MiJZ9McsOgxqWX7AaxrDjcFVjWH/tIKJd7pnR7McaslGYOP1QmmiBXdJH/H/yLCT+rcQ7FaPBUxRGUtrg==}
@@ -481,6 +553,9 @@ packages:
'@emnapi/runtime@1.7.0':
resolution: {integrity: sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q==}
+ '@emoji-mart/data@1.2.1':
+ resolution: {integrity: sha512-no2pQMWiBy6gpBEiqGeU77/bFejDqUTRY7KX+0+iur13op3bqUsXdnwoZs6Xb1zbv0gAj5VvS1PWoUUckSr5Dw==}
+
'@esbuild-kit/core-utils@3.3.2':
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
deprecated: 'Merged into tsx: https://tsx.is'
@@ -821,12 +896,18 @@ packages:
'@floating-ui/core@1.7.2':
resolution: {integrity: sha512-wNB5ooIKHQc+Kui96jE/n69rHFWAVoxn5CAzL1Xdd8FG03cgY3MLO+GF9U3W737fYDSgPWA6MReKhBQBop6Pcw==}
+ '@floating-ui/core@1.7.3':
+ resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
+
'@floating-ui/dom@1.6.13':
resolution: {integrity: sha512-umqzocjDgNRGTuO7Q8CU32dkHkECqI8ZdMZ5Swb6QAM0t5rnlrN3lGo1hdpscRd3WS8T6DKYK4ephgIH9iRh3w==}
'@floating-ui/dom@1.7.2':
resolution: {integrity: sha512-7cfaOQuCS27HD7DX+6ib2OrnW+b4ZBwDNnCcT0uTyidcmyWb03FnQqJybDBoCnpdxwBSfA94UAYlRCt7mV+TbA==}
+ '@floating-ui/dom@1.7.4':
+ resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
+
'@floating-ui/react-dom@2.1.2':
resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==}
peerDependencies:
@@ -839,12 +920,24 @@ packages:
react: '>=16.8.0'
react-dom: '>=16.8.0'
+ '@floating-ui/react-dom@2.1.6':
+ resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==}
+ peerDependencies:
+ react: '>=16.8.0'
+ react-dom: '>=16.8.0'
+
'@floating-ui/react@0.27.13':
resolution: {integrity: sha512-Qmj6t9TjgWAvbygNEu1hj4dbHI9CY0ziCMIJrmYoDIn9TUAH5lRmiIeZmRd4c6QEZkzdoH7jNnoNyoY1AIESiA==}
peerDependencies:
react: '>=17.0.0'
react-dom: '>=17.0.0'
+ '@floating-ui/react@0.27.16':
+ resolution: {integrity: sha512-9O8N4SeG2z++TSM8QA/KTeKFBVCNEz/AGS7gWPJf6KFRzmRWixFRnCnkPHRDwSVZW6QPDO6uT0P2SpWNKCc9/g==}
+ peerDependencies:
+ react: '>=17.0.0'
+ react-dom: '>=17.0.0'
+
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
@@ -1196,6 +1289,23 @@ packages:
'@types/react':
optional: true
+ '@mantine/core@8.3.9':
+ resolution: {integrity: sha512-ivj0Crn5N521cI2eWZBsBGckg0ZYRqfOJz5vbbvYmfj65bp0EdsyqZuOxXzIcn2aUScQhskfvzyhV5XIUv81PQ==}
+ peerDependencies:
+ '@mantine/hooks': 8.3.9
+ react: ^18.x || ^19.x
+ react-dom: ^18.x || ^19.x
+
+ '@mantine/hooks@8.3.9':
+ resolution: {integrity: sha512-Dfz7W0+K1cq4Gb1WFQCZn8tsMXkLH6MV409wZR/ToqsxdNDUMJ/xxbfnwEXWEZjXNJd1wDETHgc+cZG2lTe3Xw==}
+ peerDependencies:
+ react: ^18.x || ^19.x
+
+ '@mantine/utils@6.0.22':
+ resolution: {integrity: sha512-RSKlNZvxhMCkOFZ6slbYvZYbWjHUM+PxDQnupIOxIdsTZQQjx/BFfrfJ7kQFOP+g7MtpOds8weAetEs5obwMOQ==}
+ peerDependencies:
+ react: '>=16.8.0'
+
'@marijn/find-cluster-break@1.0.2':
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
@@ -2236,6 +2346,9 @@ packages:
peerDependencies:
react: '>=16.8'
+ '@remirror/core-constants@3.0.0':
+ resolution: {integrity: sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==}
+
'@rtsao/scc@1.1.0':
resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==}
@@ -2263,6 +2376,9 @@ packages:
'@shikijs/transformers@3.8.1':
resolution: {integrity: sha512-nmTyFfBrhJk6HJi118jes0wuWdfKXeVUq1Nq+hm8h6wbk1KUfvtg+LY/uDfxZD2VDItHO3QoINIs3NtoKBmgxw==}
+ '@shikijs/types@3.13.0':
+ resolution: {integrity: sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==}
+
'@shikijs/types@3.8.1':
resolution: {integrity: sha512-5C39Q8/8r1I26suLh+5TPk1DTrbY/kn3IdWA5HdizR0FhlhD05zx5nKCqhzSfDHH3p4S0ZefxWd77DLV+8FhGg==}
@@ -2410,6 +2526,100 @@ packages:
resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==}
engines: {node: '>=12'}
+ '@tiptap/core@3.11.0':
+ resolution: {integrity: sha512-kmS7ZVpHm1EMnW1Wmft9H5ZLM7E0G0NGBx+aGEHGDcNxZBXD2ZUa76CuWjIhOGpwsPbELp684ZdpF2JWoNi4Dg==}
+ peerDependencies:
+ '@tiptap/pm': ^3.11.0
+
+ '@tiptap/extension-bold@3.11.0':
+ resolution: {integrity: sha512-V/c3XYO09Le9GlBGq1MK4c97Fffi0GADQTbZ+LFoi65nUrAwutn5wYnXBcEyWQI6RmFWVDJTieamqtc4j9teyw==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+
+ '@tiptap/extension-bubble-menu@3.11.0':
+ resolution: {integrity: sha512-P3j9lQ+EZ5Zg/isJzLpCPX7bp7WUBmz8GPs/HPlyMyN2su8LqXntITBZr8IP1JNBlB/wR83k/W0XqdC57mG7cA==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+ '@tiptap/pm': ^3.11.0
+
+ '@tiptap/extension-code@3.11.0':
+ resolution: {integrity: sha512-5OpR5O4bveHe1KG9CJsto86NgkuerYq3OLY78vzh9uFCLdv7xgXA2aZYJfRMhbZ7hKsR7hHg1etBJUCk+TKsMg==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+
+ '@tiptap/extension-floating-menu@3.11.0':
+ resolution: {integrity: sha512-nEHdWZHEJYX1II1oJQ4aeZ8O/Kss4BRbYFXQFGIvPelCfCYEATpUJh3aq3767ARSq40bOWyu+Dcd4SCW0We6Sw==}
+ peerDependencies:
+ '@floating-ui/dom': ^1.0.0
+ '@tiptap/core': ^3.11.0
+ '@tiptap/pm': ^3.11.0
+
+ '@tiptap/extension-gapcursor@3.11.0':
+ resolution: {integrity: sha512-lXGEZiYX7k/pEFr8BgDE91vqjLTwuf+qhHLTgIpfhbt562nShLPIDj9Vzu3xrR4fwUAMiUNiLyaeInb8j3I4kg==}
+ peerDependencies:
+ '@tiptap/extensions': ^3.11.0
+
+ '@tiptap/extension-history@3.11.0':
+ resolution: {integrity: sha512-Q/kuNDCoeH2dZ2P+OqEKnRW047SkrngNq+vSrwQlAKO8osO/eAS7aLzn1NELzE5jLvzOKqUda43bSTKsBeTh+w==}
+ peerDependencies:
+ '@tiptap/extensions': ^3.11.0
+
+ '@tiptap/extension-horizontal-rule@3.11.0':
+ resolution: {integrity: sha512-FugFHZG+oiMBV6k42hn9NOA4wRNc2b9UeEIMR+XwEMpWJInV4VwSwDvu8JClgkDo8z7FEnker9e51DZ00CLWqg==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+ '@tiptap/pm': ^3.11.0
+
+ '@tiptap/extension-italic@3.11.0':
+ resolution: {integrity: sha512-WP6wL2b//8bLVdeUCWOpYA7nUStvrAMMD0nRn0F9CEW+l7vH6El2PZFhHmJ9uqXo5MnyugBpARiwgxfoAlef5w==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+
+ '@tiptap/extension-link@3.11.0':
+ resolution: {integrity: sha512-RoUkGqowVMKLE76KktNOGhzNMyKtwrSDRqeYCe1ODPuOMZvDGexOE8cIuA4A1ODkgN6ji9qE/9Sf8uhpZdH39Q==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+ '@tiptap/pm': ^3.11.0
+
+ '@tiptap/extension-paragraph@3.11.0':
+ resolution: {integrity: sha512-hxgjZOXOqstRTWv+QjWJjK23rD5qzIV9ePlhX3imLeq/MgX0aU9VBDaG5SGKbSjaBNQnpLw6+sABJi3CDP6Z5A==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+
+ '@tiptap/extension-strike@3.11.0':
+ resolution: {integrity: sha512-XVP/WMYLrqLBfUsGPu2H9MrOUZLhGUaxtZ3hSRffDi/lsw53x/coZ9eO0FxOB9R7z2ksHWmticIs+0YnKt9LNQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+
+ '@tiptap/extension-text@3.11.0':
+ resolution: {integrity: sha512-ELAYm2BuChzZOqDG9B0k3W6zqM4pwNvXkam28KgHGiT2y7Ni68Rb+NXp16uVR+5zR6hkqnQ/BmJSKzAW59MXpA==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+
+ '@tiptap/extension-underline@3.11.0':
+ resolution: {integrity: sha512-D3PsS/84RlQKFjd5eerMIUioC0mNh4yy1RRV/WbXx6ugu+6T+0hT42gNk9Ap8pDsVQZCk0SHfDyBEUFC2KOwKw==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+
+ '@tiptap/extensions@3.11.0':
+ resolution: {integrity: sha512-g43beA73ZMLezez1st9LEwYrRHZ0FLzlsSlOZKk7sdmtHLmuqWHf4oyb0XAHol1HZIdGv104rYaGNgmQXr1ecQ==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+ '@tiptap/pm': ^3.11.0
+
+ '@tiptap/pm@3.11.0':
+ resolution: {integrity: sha512-plCQDLCZIOc92cizB8NNhBRN0szvYR3cx9i5IXo6v9Xsgcun8KHNcJkesc2AyeqdIs0BtOJZaqQ9adHThz8UDw==}
+
+ '@tiptap/react@3.11.0':
+ resolution: {integrity: sha512-SDGei/2DjwmhzsxIQNr6dkB6NxLgXZjQ6hF36NfDm4937r5NLrWrNk5tCsoDQiKZ0DHEzuJ6yZM5C7I7LZLB6w==}
+ peerDependencies:
+ '@tiptap/core': ^3.11.0
+ '@tiptap/pm': ^3.11.0
+ '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0
+ '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0
+ react: ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
+
'@types/canvas-confetti@1.9.0':
resolution: {integrity: sha512-aBGj/dULrimR1XDZLtG9JwxX1b4HPRF6CX9Yfwh3NvstZEm1ZL7RBnel4keCPSqs1ANRu1u2Aoz9R+VmtjYuTg==}
@@ -2440,12 +2650,21 @@ packages:
'@types/katex@0.16.7':
resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==}
+ '@types/linkify-it@5.0.0':
+ resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
+
+ '@types/markdown-it@14.1.2':
+ resolution: {integrity: sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==}
+
'@types/mdast@3.0.15':
resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==}
'@types/mdast@4.0.4':
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
+ '@types/mdurl@2.0.0':
+ resolution: {integrity: sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==}
+
'@types/mdx@2.0.13':
resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==}
@@ -2478,6 +2697,9 @@ packages:
'@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
+ '@types/use-sync-external-store@0.0.6':
+ resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==}
+
'@typescript-eslint/eslint-plugin@8.25.0':
resolution: {integrity: sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
@@ -2580,6 +2802,10 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ agent-base@7.1.4:
+ resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
+ engines: {node: '>= 14'}
+
ai@4.3.19:
resolution: {integrity: sha512-dIE2bfNpqHN3r6IINp9znguYdhIOheKW2LDigAMrgt/upT3B8eBGPSCblENvaZGoq+hxaN9fSMzjWpbqloP+7Q==}
engines: {node: '>=18'}
@@ -2661,6 +2887,9 @@ packages:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
+ asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+
attr-accept@2.2.5:
resolution: {integrity: sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==}
engines: {node: '>=4'}
@@ -2829,6 +3058,10 @@ packages:
color-name@1.1.4:
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+
comma-separated-tokens@1.0.8:
resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==}
@@ -2868,6 +3101,10 @@ packages:
engines: {node: '>=4'}
hasBin: true
+ cssstyle@4.6.0:
+ resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==}
+ engines: {node: '>=18'}
+
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
@@ -2878,6 +3115,10 @@ packages:
damerau-levenshtein@1.0.8:
resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==}
+ data-urls@5.0.0:
+ resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==}
+ engines: {node: '>=18'}
+
data-view-buffer@1.0.2:
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
engines: {node: '>= 0.4'}
@@ -2938,6 +3179,10 @@ packages:
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
engines: {node: '>= 0.4'}
+ delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -3090,6 +3335,9 @@ packages:
react: ^18.0.0
react-dom: ^18.0.0
+ emoji-mart@5.6.0:
+ resolution: {integrity: sha512-eJp3QRe79pjwa+duv+n7+5YsNhRcMl812EcFVwrnRvYKoNPoQb5qxU8DG6Bgwji0akHdp6D4Ln6tYLG58MFSow==}
+
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
@@ -3430,6 +3678,10 @@ packages:
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
engines: {node: '>= 0.4'}
+ form-data@4.0.5:
+ resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==}
+ engines: {node: '>= 6'}
+
format@0.2.2:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'}
@@ -3614,6 +3866,12 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
+ hast-util-embedded@3.0.0:
+ resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==}
+
+ hast-util-format@1.1.0:
+ resolution: {integrity: sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==}
+
hast-util-from-dom@5.0.1:
resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==}
@@ -3626,15 +3884,27 @@ packages:
hast-util-from-parse5@8.0.3:
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
+ hast-util-has-property@3.0.0:
+ resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==}
+
+ hast-util-is-body-ok-link@3.0.1:
+ resolution: {integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==}
+
hast-util-is-element@3.0.0:
resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
+ hast-util-minify-whitespace@1.0.1:
+ resolution: {integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==}
+
hast-util-parse-selector@2.2.5:
resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==}
hast-util-parse-selector@4.0.0:
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
+ hast-util-phrasing@3.0.1:
+ resolution: {integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==}
+
hast-util-raw@9.1.0:
resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==}
@@ -3650,6 +3920,9 @@ packages:
hast-util-to-jsx-runtime@2.3.6:
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
+ hast-util-to-mdast@10.1.2:
+ resolution: {integrity: sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==}
+
hast-util-to-parse5@8.0.0:
resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==}
@@ -3681,12 +3954,31 @@ packages:
highlightjs-vue@1.0.0:
resolution: {integrity: sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==}
+ html-encoding-sniffer@4.0.0:
+ resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==}
+ engines: {node: '>=18'}
+
html-url-attributes@3.0.1:
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
+ html-whitespace-sensitive-tag-names@3.0.1:
+ resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==}
+
+ http-proxy-agent@7.0.2:
+ resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
+ engines: {node: '>= 14'}
+
+ https-proxy-agent@7.0.6:
+ resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
+ engines: {node: '>= 14'}
+
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@@ -3824,6 +4116,9 @@ packages:
resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
engines: {node: '>=12'}
+ is-potential-custom-element-name@1.0.1:
+ resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==}
+
is-regex@1.2.1:
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
engines: {node: '>= 0.4'}
@@ -3915,6 +4210,15 @@ packages:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
+ jsdom@25.0.1:
+ resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==}
+ engines: {node: '>=18'}
+ peerDependencies:
+ canvas: ^2.11.2
+ peerDependenciesMeta:
+ canvas:
+ optional: true
+
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
@@ -4034,6 +4338,12 @@ packages:
resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==}
engines: {node: '>= 12.0.0'}
+ linkify-it@5.0.0:
+ resolution: {integrity: sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==}
+
+ linkifyjs@4.3.2:
+ resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
+
locate-path@6.0.0:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
@@ -4069,6 +4379,9 @@ packages:
lowlight@1.20.0:
resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==}
+ lru-cache@10.4.3:
+ resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
+
lru-cache@11.1.0:
resolution: {integrity: sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==}
engines: {node: 20 || >=22}
@@ -4100,6 +4413,10 @@ packages:
resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==}
engines: {node: '>=16'}
+ markdown-it@14.1.0:
+ resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
+ hasBin: true
+
markdown-table@3.0.4:
resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==}
@@ -4203,6 +4520,9 @@ packages:
mdast-util-to-string@4.0.0:
resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+ mdurl@2.0.0:
+ resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
+
memoize-one@5.2.1:
resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==}
@@ -4420,10 +4740,18 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
mime-db@1.54.0:
resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
engines: {node: '>= 0.6'}
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
@@ -4559,6 +4887,9 @@ packages:
number-flow@0.5.8:
resolution: {integrity: sha512-FPr1DumWyGi5Nucoug14bC6xEz70A1TnhgSHhKyfqjgji2SOTz+iLJxKtv37N5JyJbteGYCm6NQ9p1O4KZ7iiA==}
+ nwsapi@2.2.22:
+ resolution: {integrity: sha512-ujSMe1OWVn55euT1ihwCI1ZcAaAU3nxUiDwfDQldc51ZXaB9m2AyOn6/jh1BLe2t/G8xd6uKG1UBF2aZJeg2SQ==}
+
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -4604,6 +4935,9 @@ packages:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
+ orderedmap@2.1.1:
+ resolution: {integrity: sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==}
+
outvariant@1.4.0:
resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==}
@@ -4775,9 +5109,106 @@ packages:
property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
+ prosemirror-changeset@2.3.1:
+ resolution: {integrity: sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==}
+
+ prosemirror-collab@1.3.1:
+ resolution: {integrity: sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==}
+
+ prosemirror-commands@1.7.1:
+ resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==}
+
+ prosemirror-dropcursor@1.8.2:
+ resolution: {integrity: sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==}
+
+ prosemirror-gapcursor@1.4.0:
+ resolution: {integrity: sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==}
+
+ prosemirror-highlight@0.13.0:
+ resolution: {integrity: sha512-GIC2VCTUnukNdsEGLQWWOVpYPl/7/KrVp4xs7XMB48/4rhUrHK8hp8TEog4Irmv+2kmjx24RLnaisGOCP6U8jw==}
+ peerDependencies:
+ '@shikijs/types': ^1.29.2 || ^2.0.0 || ^3.0.0
+ '@types/hast': ^3.0.0
+ highlight.js: ^11.9.0
+ lowlight: ^3.1.0
+ prosemirror-model: ^1.19.3
+ prosemirror-state: ^1.4.3
+ prosemirror-transform: ^1.8.0
+ prosemirror-view: ^1.32.4
+ refractor: ^5.0.0
+ sugar-high: ^0.6.1 || ^0.7.0 || ^0.8.0 || ^0.9.0
+ peerDependenciesMeta:
+ '@shikijs/types':
+ optional: true
+ '@types/hast':
+ optional: true
+ highlight.js:
+ optional: true
+ lowlight:
+ optional: true
+ prosemirror-model:
+ optional: true
+ prosemirror-state:
+ optional: true
+ prosemirror-transform:
+ optional: true
+ prosemirror-view:
+ optional: true
+ refractor:
+ optional: true
+ sugar-high:
+ optional: true
+
+ prosemirror-history@1.5.0:
+ resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==}
+
+ prosemirror-inputrules@1.5.1:
+ resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==}
+
+ prosemirror-keymap@1.2.3:
+ resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==}
+
+ prosemirror-markdown@1.13.2:
+ resolution: {integrity: sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==}
+
+ prosemirror-menu@1.2.5:
+ resolution: {integrity: sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==}
+
+ prosemirror-model@1.25.4:
+ resolution: {integrity: sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==}
+
+ prosemirror-schema-basic@1.2.4:
+ resolution: {integrity: sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==}
+
+ prosemirror-schema-list@1.5.1:
+ resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==}
+
+ prosemirror-state@1.4.4:
+ resolution: {integrity: sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==}
+
+ prosemirror-tables@1.8.1:
+ resolution: {integrity: sha512-DAgDoUYHCcc6tOGpLVPSU1k84kCUWTWnfWX3UDy2Delv4ryH0KqTD6RBI6k4yi9j9I8gl3j8MkPpRD/vWPZbug==}
+
+ prosemirror-trailing-node@3.0.0:
+ resolution: {integrity: sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==}
+ peerDependencies:
+ prosemirror-model: ^1.22.1
+ prosemirror-state: ^1.4.2
+ prosemirror-view: ^1.33.8
+
+ prosemirror-transform@1.10.5:
+ resolution: {integrity: sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==}
+
+ prosemirror-view@1.41.3:
+ resolution: {integrity: sha512-SqMiYMUQNNBP9kfPhLO8WXEk/fon47vc52FQsUiJzTBuyjKgEcoAwMyF04eQ4WZ2ArMn7+ReypYL60aKngbACQ==}
+
pump@3.0.3:
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
+ punycode.js@2.3.1:
+ resolution: {integrity: sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==}
+ engines: {node: '>=6'}
+
punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
@@ -4834,6 +5265,11 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17 || ^18 || ^19
+ react-icons@5.5.0:
+ resolution: {integrity: sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==}
+ peerDependencies:
+ react: '*'
+
react-intersection-observer@9.5.1:
resolution: {integrity: sha512-YwcNF/4WsMAG1rLVDQHSbpdEW9vDaIl4QW88d+vqeXNUewFV4AJDQB14oHpAJ3rRCnKRmwu3nqfwwYe6wioNIg==}
peerDependencies:
@@ -4881,6 +5317,12 @@ packages:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-number-format@5.4.4:
+ resolution: {integrity: sha512-wOmoNZoOpvMminhifQYiYSTCLUDOiUbBunrMrMjA+dV52sY+vck1S4UhR6PkgnoCquvvMSeJjErXZ4qSaWCliA==}
+ peerDependencies:
+ react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
react-pdf@9.2.1:
resolution: {integrity: sha512-AJt0lAIkItWEZRA5d/mO+Om4nPCuTiQ0saA+qItO967DTjmGjnhmF+Bi2tL286mOTfBlF5CyLzJ35KTMaDoH+A==}
peerDependencies:
@@ -4958,6 +5400,12 @@ packages:
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-textarea-autosize@8.5.9:
+ resolution: {integrity: sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
react-window@1.8.9:
resolution: {integrity: sha512-+Eqx/fj1Aa5WnhRfj9dJg4VYATGwIUP2ItwItiJ6zboKWA6EX3lYDAXfGF2hyNqplEprhbtjbipiADEcwQ823Q==}
engines: {node: '>8.0.0'}
@@ -5017,18 +5465,33 @@ packages:
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
engines: {node: '>= 0.4'}
+ rehype-format@5.0.1:
+ resolution: {integrity: sha512-zvmVru9uB0josBVpr946OR8ui7nJEdzZobwLOOqHb/OOD88W0Vk2SqLwoVOj0fM6IPCCO6TaV9CvQvJMWwukFQ==}
+
rehype-katex@7.0.1:
resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==}
+ rehype-minify-whitespace@6.0.2:
+ resolution: {integrity: sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==}
+
+ rehype-parse@9.0.1:
+ resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==}
+
rehype-raw@7.0.0:
resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
rehype-recma@1.0.0:
resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==}
+ rehype-remark@10.0.1:
+ resolution: {integrity: sha512-EmDndlb5NVwXGfUa4c9GPK+lXeItTilLhE6ADSaQuHr4JUlKw9MidzGzx4HpqZrNCt6vnHmEifXQiiA+CEnjYQ==}
+
rehype-sanitize@6.0.0:
resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==}
+ rehype-stringify@10.0.1:
+ resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==}
+
remark-code-import@1.2.0:
resolution: {integrity: sha512-fgwLruqlZbVOIhCJFjY+JDwPZhA4/eK3InJzN8Ox8UDdtudpG212JwtRj6la+lAzJU7JmSEyewZSukVZdknt3Q==}
engines: {node: '>= 12'}
@@ -5083,9 +5546,18 @@ packages:
resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ rope-sequence@1.3.4:
+ resolution: {integrity: sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==}
+
rough-notation@0.5.1:
resolution: {integrity: sha512-ITHofTzm13cWFVfoGsh/4c/k2Mg8geKgBCwex71UZLnNuw403tCRjYPQ68jSAd37DMbZIePXPjDgY0XdZi9HPw==}
+ rrweb-cssom@0.7.1:
+ resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==}
+
+ rrweb-cssom@0.8.0:
+ resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==}
+
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -5108,6 +5580,13 @@ packages:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
+ safer-buffer@2.1.2:
+ resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+
+ saxes@6.0.0:
+ resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==}
+ engines: {node: '>=v12.22.7'}
+
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
@@ -5306,6 +5785,9 @@ packages:
peerDependencies:
react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ symbol-tree@3.2.4:
+ resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+
tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
@@ -5362,6 +5844,13 @@ packages:
resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
engines: {node: '>=12.0.0'}
+ tldts-core@6.1.86:
+ resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==}
+
+ tldts@6.1.86:
+ resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==}
+ hasBin: true
+
to-gatsby-remark-plugin@0.1.0:
resolution: {integrity: sha512-blmhJ/gIrytWnWLgPSRCkhCPeki6UBK2daa3k9mGahN7GjwHu8KrS7F70MvwlsG7IE794JLgwAdCbi4hU4faFQ==}
@@ -5372,12 +5861,23 @@ packages:
to-vfile@6.1.0:
resolution: {integrity: sha512-BxX8EkCxOAZe+D/ToHdDsJcVI4HqQfmw0tCkp31zf3dNP/XWIAjU4CmeuSwsSoOzOTqHPOL0KUzyZqJplkD0Qw==}
+ tough-cookie@5.1.2:
+ resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==}
+ engines: {node: '>=16'}
+
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+ tr46@5.1.1:
+ resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+ engines: {node: '>=18'}
+
trim-lines@3.0.1:
resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==}
+ trim-trailing-lines@2.1.0:
+ resolution: {integrity: sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==}
+
trough@2.2.0:
resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
@@ -5408,6 +5908,10 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
+ type-fest@4.41.0:
+ resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==}
+ engines: {node: '>=16'}
+
type@2.7.3:
resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
@@ -5436,6 +5940,9 @@ packages:
resolution: {integrity: sha512-z6PJ8Lml+v3ichVojCiB8toQJBuwR42ySM4ezjXIqXK3M0HczmKQ3LF4rhU55PfD99KEEXQG6yb7iOMyvYuHew==}
hasBin: true
+ uc.micro@2.1.0:
+ resolution: {integrity: sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==}
+
unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
@@ -5565,6 +6072,10 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
+ uuid@8.3.2:
+ resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
+ hasBin: true
+
uvu@0.5.6:
resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==}
engines: {node: '>=8'}
@@ -5600,6 +6111,10 @@ packages:
w3c-keyname@2.2.8:
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
+ w3c-xmlserializer@5.0.0:
+ resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==}
+ engines: {node: '>=18'}
+
warning@4.0.3:
resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
@@ -5609,6 +6124,22 @@ packages:
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+ webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
+ whatwg-encoding@3.1.1:
+ resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
+ engines: {node: '>=18'}
+
+ whatwg-mimetype@4.0.0:
+ resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
+ engines: {node: '>=18'}
+
+ whatwg-url@14.2.0:
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+ engines: {node: '>=18'}
+
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
@@ -5640,10 +6171,45 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ ws@8.18.3:
+ resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
+ xml-name-validator@5.0.0:
+ resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
+ engines: {node: '>=18'}
+
+ xmlchars@2.2.0:
+ resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
+
xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
+ y-prosemirror@1.3.7:
+ resolution: {integrity: sha512-NpM99WSdD4Fx4if5xOMDpPtU3oAmTSjlzh5U4353ABbRHl1HtAFUx6HlebLZfyFxXN9jzKMDkVbcRjqOZVkYQg==}
+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+ peerDependencies:
+ prosemirror-model: ^1.7.1
+ prosemirror-state: ^1.2.3
+ prosemirror-view: ^1.9.10
+ y-protocols: ^1.0.1
+ yjs: ^13.5.38
+
+ y-protocols@1.0.6:
+ resolution: {integrity: sha512-vHRF2L6iT3rwj1jub/K5tYcTT/mEYDUppgNPXwp8fmLpui9f7Yeq3OEtTLVF012j39QnV+KEQpNqoN7CWU7Y9Q==}
+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+ peerDependencies:
+ yjs: ^13.0.0
+
yallist@5.0.0:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
@@ -5707,6 +6273,14 @@ snapshots:
'@jridgewell/gen-mapping': 0.3.8
'@jridgewell/trace-mapping': 0.3.25
+ '@asamuzakjp/css-color@3.2.0':
+ dependencies:
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+ lru-cache: 10.4.3
+
'@babel/runtime@7.26.9':
dependencies:
regenerator-runtime: 0.14.1
@@ -5746,6 +6320,136 @@ snapshots:
'@biomejs/cli-win32-x64@2.1.2':
optional: true
+ '@blocknote/core@0.42.3(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(highlight.js@11.11.1)':
+ dependencies:
+ '@emoji-mart/data': 1.2.1
+ '@shikijs/types': 3.13.0
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+ '@tiptap/extension-bold': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))
+ '@tiptap/extension-code': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))
+ '@tiptap/extension-gapcursor': 3.11.0(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))
+ '@tiptap/extension-history': 3.11.0(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))
+ '@tiptap/extension-horizontal-rule': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)
+ '@tiptap/extension-italic': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))
+ '@tiptap/extension-link': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)
+ '@tiptap/extension-paragraph': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))
+ '@tiptap/extension-strike': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))
+ '@tiptap/extension-text': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))
+ '@tiptap/extension-underline': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))
+ '@tiptap/pm': 3.11.0
+ emoji-mart: 5.6.0
+ fast-deep-equal: 3.1.3
+ hast-util-from-dom: 5.0.1
+ prosemirror-dropcursor: 1.8.2
+ prosemirror-highlight: 0.13.0(@shikijs/types@3.13.0)(@types/hast@3.0.4)(highlight.js@11.11.1)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-transform@1.10.5)(prosemirror-view@1.41.3)
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-tables: 1.8.1
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+ rehype-format: 5.0.1
+ rehype-parse: 9.0.1
+ rehype-remark: 10.0.1
+ rehype-stringify: 10.0.1
+ remark-gfm: 4.0.1
+ remark-parse: 11.0.0
+ remark-rehype: 11.1.2
+ remark-stringify: 11.0.0
+ unified: 11.0.5
+ unist-util-visit: 5.0.0
+ uuid: 8.3.2
+ y-prosemirror: 1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)
+ y-protocols: 1.0.6(yjs@13.6.27)
+ yjs: 13.6.27
+ transitivePeerDependencies:
+ - '@tiptap/extensions'
+ - '@types/hast'
+ - highlight.js
+ - lowlight
+ - refractor
+ - sugar-high
+ - supports-color
+
+ '@blocknote/mantine@0.42.3(@floating-ui/dom@1.7.4)(@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@mantine/hooks@8.3.9(react@19.1.0))(@mantine/utils@6.0.22(react@19.1.0))(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(highlight.js@11.11.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@blocknote/core': 0.42.3(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(highlight.js@11.11.1)
+ '@blocknote/react': 0.42.3(@floating-ui/dom@1.7.4)(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(highlight.js@11.11.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@mantine/core': 8.3.9(@mantine/hooks@8.3.9(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@mantine/hooks': 8.3.9(react@19.1.0)
+ '@mantine/utils': 6.0.22(react@19.1.0)
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-icons: 5.5.0(react@19.1.0)
+ transitivePeerDependencies:
+ - '@floating-ui/dom'
+ - '@hocuspocus/provider'
+ - '@tiptap/extensions'
+ - '@types/hast'
+ - '@types/react'
+ - '@types/react-dom'
+ - highlight.js
+ - lowlight
+ - refractor
+ - sugar-high
+ - supports-color
+
+ '@blocknote/react@0.42.3(@floating-ui/dom@1.7.4)(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(highlight.js@11.11.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@blocknote/core': 0.42.3(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(highlight.js@11.11.1)
+ '@emoji-mart/data': 1.2.1
+ '@floating-ui/react': 0.27.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+ '@tiptap/pm': 3.11.0
+ '@tiptap/react': 3.11.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ emoji-mart: 5.6.0
+ lodash.merge: 4.6.2
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-icons: 5.5.0(react@19.1.0)
+ transitivePeerDependencies:
+ - '@floating-ui/dom'
+ - '@hocuspocus/provider'
+ - '@tiptap/extensions'
+ - '@types/hast'
+ - '@types/react'
+ - '@types/react-dom'
+ - highlight.js
+ - lowlight
+ - refractor
+ - sugar-high
+ - supports-color
+
+ '@blocknote/server-util@0.42.3(@floating-ui/dom@1.7.4)(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(highlight.js@11.11.1)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@blocknote/core': 0.42.3(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(highlight.js@11.11.1)
+ '@blocknote/react': 0.42.3(@floating-ui/dom@1.7.4)(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))(@types/hast@3.0.4)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(highlight.js@11.11.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+ '@tiptap/pm': 3.11.0
+ jsdom: 25.0.1
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ y-prosemirror: 1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27)
+ y-protocols: 1.0.6(yjs@13.6.27)
+ yjs: 13.6.27
+ transitivePeerDependencies:
+ - '@floating-ui/dom'
+ - '@hocuspocus/provider'
+ - '@tiptap/extensions'
+ - '@types/hast'
+ - '@types/react'
+ - '@types/react-dom'
+ - bufferutil
+ - canvas
+ - highlight.js
+ - lowlight
+ - prosemirror-model
+ - prosemirror-state
+ - prosemirror-view
+ - refractor
+ - sugar-high
+ - supports-color
+ - utf-8-validate
+
'@codemirror/autocomplete@6.18.6':
dependencies:
'@codemirror/language': 6.11.2
@@ -6038,6 +6742,26 @@ snapshots:
react-dom: 19.1.0(react@19.1.0)
react-is: 17.0.2
+ '@csstools/color-helpers@5.1.0': {}
+
+ '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/color-helpers': 5.1.0
+ '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4)
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)':
+ dependencies:
+ '@csstools/css-tokenizer': 3.0.4
+
+ '@csstools/css-tokenizer@3.0.4': {}
+
'@date-fns/tz@1.2.0': {}
'@drizzle-team/brocli@0.10.2': {}
@@ -6047,6 +6771,8 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@emoji-mart/data@1.2.1': {}
+
'@esbuild-kit/core-utils@3.3.2':
dependencies:
esbuild: 0.18.20
@@ -6253,6 +6979,10 @@ snapshots:
dependencies:
'@floating-ui/utils': 0.2.10
+ '@floating-ui/core@1.7.3':
+ dependencies:
+ '@floating-ui/utils': 0.2.10
+
'@floating-ui/dom@1.6.13':
dependencies:
'@floating-ui/core': 1.6.9
@@ -6263,6 +6993,11 @@ snapshots:
'@floating-ui/core': 1.7.2
'@floating-ui/utils': 0.2.10
+ '@floating-ui/dom@1.7.4':
+ dependencies:
+ '@floating-ui/core': 1.7.3
+ '@floating-ui/utils': 0.2.10
+
'@floating-ui/react-dom@2.1.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@floating-ui/dom': 1.6.13
@@ -6275,6 +7010,12 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
+ '@floating-ui/react-dom@2.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
'@floating-ui/react@0.27.13(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
dependencies:
'@floating-ui/react-dom': 2.1.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
@@ -6283,6 +7024,14 @@ snapshots:
react-dom: 19.1.0(react@19.1.0)
tabbable: 6.2.0
+ '@floating-ui/react@0.27.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/react-dom': 2.1.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@floating-ui/utils': 0.2.10
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ tabbable: 6.2.0
+
'@floating-ui/utils@0.2.10': {}
'@floating-ui/utils@0.2.9': {}
@@ -6766,9 +7515,31 @@ snapshots:
optionalDependencies:
'@types/react': 19.1.8
+ '@mantine/core@8.3.9(@mantine/hooks@8.3.9(react@19.1.0))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@floating-ui/react': 0.27.16(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ '@mantine/hooks': 8.3.9(react@19.1.0)
+ clsx: 2.1.1
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ react-number-format: 5.4.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
+ react-remove-scroll: 2.7.1(@types/react@19.1.8)(react@19.1.0)
+ react-textarea-autosize: 8.5.9(@types/react@19.1.8)(react@19.1.0)
+ type-fest: 4.41.0
+ transitivePeerDependencies:
+ - '@types/react'
+
+ '@mantine/hooks@8.3.9(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+
+ '@mantine/utils@6.0.22(react@19.1.0)':
+ dependencies:
+ react: 19.1.0
+
'@marijn/find-cluster-break@1.0.2': {}
- '@mdx-js/mdx@3.1.0(acorn@8.14.0)':
+ '@mdx-js/mdx@3.1.0(acorn@8.15.0)':
dependencies:
'@types/estree': 1.0.8
'@types/estree-jsx': 1.0.5
@@ -6782,7 +7553,7 @@ snapshots:
hast-util-to-jsx-runtime: 2.3.6
markdown-extensions: 2.0.0
recma-build-jsx: 1.0.0
- recma-jsx: 1.0.0(acorn@8.14.0)
+ recma-jsx: 1.0.0(acorn@8.15.0)
recma-stringify: 1.0.0
rehype-recma: 1.0.0
remark-mdx: 3.1.0
@@ -7915,6 +8686,8 @@ snapshots:
dependencies:
react: 19.1.0
+ '@remirror/core-constants@3.0.0': {}
+
'@rtsao/scc@1.1.0': {}
'@rushstack/eslint-patch@1.10.5': {}
@@ -7959,6 +8732,11 @@ snapshots:
'@shikijs/core': 3.8.1
'@shikijs/types': 3.8.1
+ '@shikijs/types@3.13.0':
+ dependencies:
+ '@shikijs/vscode-textmate': 10.0.2
+ '@types/hast': 3.0.4
+
'@shikijs/types@3.8.1':
dependencies:
'@shikijs/vscode-textmate': 10.0.2
@@ -8086,6 +8864,114 @@ snapshots:
'@tanstack/table-core@8.21.3': {}
+ '@tiptap/core@3.11.0(@tiptap/pm@3.11.0)':
+ dependencies:
+ '@tiptap/pm': 3.11.0
+
+ '@tiptap/extension-bold@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+
+ '@tiptap/extension-bubble-menu@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+ '@tiptap/pm': 3.11.0
+ optional: true
+
+ '@tiptap/extension-code@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+
+ '@tiptap/extension-floating-menu@3.11.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)':
+ dependencies:
+ '@floating-ui/dom': 1.7.4
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+ '@tiptap/pm': 3.11.0
+ optional: true
+
+ '@tiptap/extension-gapcursor@3.11.0(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))':
+ dependencies:
+ '@tiptap/extensions': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)
+
+ '@tiptap/extension-history@3.11.0(@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0))':
+ dependencies:
+ '@tiptap/extensions': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)
+
+ '@tiptap/extension-horizontal-rule@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+ '@tiptap/pm': 3.11.0
+
+ '@tiptap/extension-italic@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+
+ '@tiptap/extension-link@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+ '@tiptap/pm': 3.11.0
+ linkifyjs: 4.3.2
+
+ '@tiptap/extension-paragraph@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+
+ '@tiptap/extension-strike@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+
+ '@tiptap/extension-text@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+
+ '@tiptap/extension-underline@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+
+ '@tiptap/extensions@3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+ '@tiptap/pm': 3.11.0
+
+ '@tiptap/pm@3.11.0':
+ dependencies:
+ prosemirror-changeset: 2.3.1
+ prosemirror-collab: 1.3.1
+ prosemirror-commands: 1.7.1
+ prosemirror-dropcursor: 1.8.2
+ prosemirror-gapcursor: 1.4.0
+ prosemirror-history: 1.5.0
+ prosemirror-inputrules: 1.5.1
+ prosemirror-keymap: 1.2.3
+ prosemirror-markdown: 1.13.2
+ prosemirror-menu: 1.2.5
+ prosemirror-model: 1.25.4
+ prosemirror-schema-basic: 1.2.4
+ prosemirror-schema-list: 1.5.1
+ prosemirror-state: 1.4.4
+ prosemirror-tables: 1.8.1
+ prosemirror-trailing-node: 3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+
+ '@tiptap/react@3.11.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
+ dependencies:
+ '@tiptap/core': 3.11.0(@tiptap/pm@3.11.0)
+ '@tiptap/pm': 3.11.0
+ '@types/react': 19.1.8
+ '@types/react-dom': 19.1.6(@types/react@19.1.8)
+ '@types/use-sync-external-store': 0.0.6
+ fast-deep-equal: 3.1.3
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+ use-sync-external-store: 1.5.0(react@19.1.0)
+ optionalDependencies:
+ '@tiptap/extension-bubble-menu': 3.11.0(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)
+ '@tiptap/extension-floating-menu': 3.11.0(@floating-ui/dom@1.7.4)(@tiptap/core@3.11.0(@tiptap/pm@3.11.0))(@tiptap/pm@3.11.0)
+ transitivePeerDependencies:
+ - '@floating-ui/dom'
+
'@types/canvas-confetti@1.9.0': {}
'@types/debug@4.1.12':
@@ -8114,6 +9000,13 @@ snapshots:
'@types/katex@0.16.7': {}
+ '@types/linkify-it@5.0.0': {}
+
+ '@types/markdown-it@14.1.2':
+ dependencies:
+ '@types/linkify-it': 5.0.0
+ '@types/mdurl': 2.0.0
+
'@types/mdast@3.0.15':
dependencies:
'@types/unist': 2.0.11
@@ -8122,6 +9015,8 @@ snapshots:
dependencies:
'@types/unist': 3.0.3
+ '@types/mdurl@2.0.0': {}
+
'@types/mdx@2.0.13': {}
'@types/ms@2.1.0': {}
@@ -8154,6 +9049,8 @@ snapshots:
'@types/unist@3.0.3': {}
+ '@types/use-sync-external-store@0.0.6': {}
+
'@typescript-eslint/eslint-plugin@8.25.0(@typescript-eslint/parser@8.25.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)':
dependencies:
'@eslint-community/regexpp': 4.12.1
@@ -8291,6 +9188,8 @@ snapshots:
acorn@8.15.0: {}
+ agent-base@7.1.4: {}
+
ai@4.3.19(react@19.1.0)(zod@3.25.76):
dependencies:
'@ai-sdk/provider': 1.1.3
@@ -8398,6 +9297,8 @@ snapshots:
async-function@1.0.0: {}
+ asynckit@0.4.0: {}
+
attr-accept@2.2.5: {}
available-typed-arrays@1.0.7:
@@ -8567,6 +9468,10 @@ snapshots:
color-name@1.1.4: {}
+ combined-stream@1.0.8:
+ dependencies:
+ delayed-stream: 1.0.0
+
comma-separated-tokens@1.0.8: {}
comma-separated-tokens@2.0.3: {}
@@ -8599,6 +9504,11 @@ snapshots:
cssesc@3.0.0: {}
+ cssstyle@4.6.0:
+ dependencies:
+ '@asamuzakjp/css-color': 3.2.0
+ rrweb-cssom: 0.8.0
+
csstype@3.1.3: {}
d@1.0.2:
@@ -8608,6 +9518,11 @@ snapshots:
damerau-levenshtein@1.0.8: {}
+ data-urls@5.0.0:
+ dependencies:
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+
data-view-buffer@1.0.2:
dependencies:
call-bound: 1.0.3
@@ -8666,6 +9581,8 @@ snapshots:
has-property-descriptors: 1.0.2
object-keys: 1.1.1
+ delayed-stream@1.0.0: {}
+
dequal@2.0.3: {}
detect-libc@2.0.3: {}
@@ -8740,6 +9657,8 @@ snapshots:
- '@types/react'
- '@types/react-dom'
+ emoji-mart@5.6.0: {}
+
emoji-regex@9.2.2: {}
end-of-stream@1.4.5:
@@ -9314,6 +10233,14 @@ snapshots:
dependencies:
is-callable: 1.2.7
+ form-data@4.0.5:
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ es-set-tostringtag: 2.1.0
+ hasown: 2.0.2
+ mime-types: 2.1.35
+
format@0.2.2: {}
framer-motion@12.23.22(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
@@ -9358,9 +10285,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
- fumadocs-mdx@11.7.1(acorn@8.14.0)(fumadocs-core@15.6.6(@types/react@19.1.8)(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0):
+ fumadocs-mdx@11.7.1(acorn@8.15.0)(fumadocs-core@15.6.6(@types/react@19.1.8)(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(next@15.5.6(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react@19.1.0):
dependencies:
- '@mdx-js/mdx': 3.1.0(acorn@8.14.0)
+ '@mdx-js/mdx': 3.1.0(acorn@8.15.0)
'@standard-schema/spec': 1.0.0
chokidar: 4.0.3
esbuild: 0.25.8
@@ -9510,6 +10437,21 @@ snapshots:
dependencies:
function-bind: 1.1.2
+ hast-util-embedded@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-is-element: 3.0.0
+
+ hast-util-format@1.1.0:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-embedded: 3.0.0
+ hast-util-minify-whitespace: 1.0.1
+ hast-util-phrasing: 3.0.1
+ hast-util-whitespace: 3.0.0
+ html-whitespace-sensitive-tag-names: 3.0.1
+ unist-util-visit-parents: 6.0.1
+
hast-util-from-dom@5.0.1:
dependencies:
'@types/hast': 3.0.4
@@ -9543,16 +10485,40 @@ snapshots:
vfile-location: 5.0.3
web-namespaces: 2.0.1
+ hast-util-has-property@3.0.0:
+ dependencies:
+ '@types/hast': 3.0.4
+
+ hast-util-is-body-ok-link@3.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+
hast-util-is-element@3.0.0:
dependencies:
'@types/hast': 3.0.4
+ hast-util-minify-whitespace@1.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-embedded: 3.0.0
+ hast-util-is-element: 3.0.0
+ hast-util-whitespace: 3.0.0
+ unist-util-is: 6.0.0
+
hast-util-parse-selector@2.2.5: {}
hast-util-parse-selector@4.0.0:
dependencies:
'@types/hast': 3.0.4
+ hast-util-phrasing@3.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-embedded: 3.0.0
+ hast-util-has-property: 3.0.0
+ hast-util-is-body-ok-link: 3.0.1
+ hast-util-is-element: 3.0.0
+
hast-util-raw@9.1.0:
dependencies:
'@types/hast': 3.0.4
@@ -9630,6 +10596,23 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ hast-util-to-mdast@10.1.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ '@ungap/structured-clone': 1.3.0
+ hast-util-phrasing: 3.0.1
+ hast-util-to-html: 9.0.5
+ hast-util-to-text: 4.0.2
+ hast-util-whitespace: 3.0.0
+ mdast-util-phrasing: 4.1.0
+ mdast-util-to-hast: 13.2.0
+ mdast-util-to-string: 4.0.0
+ rehype-minify-whitespace: 6.0.2
+ trim-trailing-lines: 2.1.0
+ unist-util-position: 5.0.0
+ unist-util-visit: 5.0.0
+
hast-util-to-parse5@8.0.0:
dependencies:
'@types/hast': 3.0.4
@@ -9679,10 +10662,34 @@ snapshots:
highlightjs-vue@1.0.0: {}
+ html-encoding-sniffer@4.0.0:
+ dependencies:
+ whatwg-encoding: 3.1.1
+
html-url-attributes@3.0.1: {}
html-void-elements@3.0.0: {}
+ html-whitespace-sensitive-tag-names@3.0.1: {}
+
+ http-proxy-agent@7.0.2:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.0
+ transitivePeerDependencies:
+ - supports-color
+
+ https-proxy-agent@7.0.6:
+ dependencies:
+ agent-base: 7.1.4
+ debug: 4.4.0
+ transitivePeerDependencies:
+ - supports-color
+
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+
ieee754@1.2.1: {}
ignore@5.3.2: {}
@@ -9817,6 +10824,8 @@ snapshots:
is-plain-obj@4.1.0: {}
+ is-potential-custom-element-name@1.0.1: {}
+
is-regex@1.2.1:
dependencies:
call-bound: 1.0.3
@@ -9892,6 +10901,34 @@ snapshots:
dependencies:
argparse: 2.0.1
+ jsdom@25.0.1:
+ dependencies:
+ cssstyle: 4.6.0
+ data-urls: 5.0.0
+ decimal.js: 10.6.0
+ form-data: 4.0.5
+ html-encoding-sniffer: 4.0.0
+ http-proxy-agent: 7.0.2
+ https-proxy-agent: 7.0.6
+ is-potential-custom-element-name: 1.0.1
+ nwsapi: 2.2.22
+ parse5: 7.2.1
+ rrweb-cssom: 0.7.1
+ saxes: 6.0.0
+ symbol-tree: 3.2.4
+ tough-cookie: 5.1.2
+ w3c-xmlserializer: 5.0.0
+ webidl-conversions: 7.0.0
+ whatwg-encoding: 3.1.1
+ whatwg-mimetype: 4.0.0
+ whatwg-url: 14.2.0
+ ws: 8.18.3
+ xml-name-validator: 5.0.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
@@ -9989,6 +11026,12 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.30.1
lightningcss-win32-x64-msvc: 1.30.1
+ linkify-it@5.0.0:
+ dependencies:
+ uc.micro: 2.1.0
+
+ linkifyjs@4.3.2: {}
+
locate-path@6.0.0:
dependencies:
p-locate: 5.0.0
@@ -10018,6 +11061,8 @@ snapshots:
fault: 1.0.4
highlight.js: 10.7.3
+ lru-cache@10.4.3: {}
+
lru-cache@11.1.0: {}
lucide-react@0.453.0(react@19.1.0):
@@ -10040,6 +11085,15 @@ snapshots:
markdown-extensions@2.0.0: {}
+ markdown-it@14.1.0:
+ dependencies:
+ argparse: 2.0.1
+ entities: 4.5.0
+ linkify-it: 5.0.0
+ mdurl: 2.0.0
+ punycode.js: 2.3.1
+ uc.micro: 2.1.0
+
markdown-table@3.0.4: {}
math-intrinsics@1.1.0: {}
@@ -10343,6 +11397,8 @@ snapshots:
dependencies:
'@types/mdast': 4.0.4
+ mdurl@2.0.0: {}
+
memoize-one@5.2.1: {}
merge-refs@1.3.0(@types/react@19.1.8):
@@ -10838,8 +11894,14 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.52.0: {}
+
mime-db@1.54.0: {}
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
mimic-response@3.1.0:
optional: true
@@ -10950,6 +12012,8 @@ snapshots:
dependencies:
esm-env: 1.2.2
+ nwsapi@2.2.22: {}
+
object-assign@4.1.1: {}
object-inspect@1.13.4: {}
@@ -11013,6 +12077,8 @@ snapshots:
type-check: 0.4.0
word-wrap: 1.2.5
+ orderedmap@2.1.1: {}
+
outvariant@1.4.0: {}
own-keys@1.0.1:
@@ -11191,12 +12257,127 @@ snapshots:
property-information@7.1.0: {}
+ prosemirror-changeset@2.3.1:
+ dependencies:
+ prosemirror-transform: 1.10.5
+
+ prosemirror-collab@1.3.1:
+ dependencies:
+ prosemirror-state: 1.4.4
+
+ prosemirror-commands@1.7.1:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ prosemirror-dropcursor@1.8.2:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+
+ prosemirror-gapcursor@1.4.0:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.3
+
+ prosemirror-highlight@0.13.0(@shikijs/types@3.13.0)(@types/hast@3.0.4)(highlight.js@11.11.1)(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-transform@1.10.5)(prosemirror-view@1.41.3):
+ optionalDependencies:
+ '@shikijs/types': 3.13.0
+ '@types/hast': 3.0.4
+ highlight.js: 11.11.1
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+
+ prosemirror-history@1.5.0:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+ rope-sequence: 1.3.4
+
+ prosemirror-inputrules@1.5.1:
+ dependencies:
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ prosemirror-keymap@1.2.3:
+ dependencies:
+ prosemirror-state: 1.4.4
+ w3c-keyname: 2.2.8
+
+ prosemirror-markdown@1.13.2:
+ dependencies:
+ '@types/markdown-it': 14.1.2
+ markdown-it: 14.1.0
+ prosemirror-model: 1.25.4
+
+ prosemirror-menu@1.2.5:
+ dependencies:
+ crelt: 1.0.6
+ prosemirror-commands: 1.7.1
+ prosemirror-history: 1.5.0
+ prosemirror-state: 1.4.4
+
+ prosemirror-model@1.25.4:
+ dependencies:
+ orderedmap: 2.1.1
+
+ prosemirror-schema-basic@1.2.4:
+ dependencies:
+ prosemirror-model: 1.25.4
+
+ prosemirror-schema-list@1.5.1:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
+ prosemirror-state@1.4.4:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+
+ prosemirror-tables@1.8.1:
+ dependencies:
+ prosemirror-keymap: 1.2.3
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+ prosemirror-view: 1.41.3
+
+ prosemirror-trailing-node@3.0.0(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3):
+ dependencies:
+ '@remirror/core-constants': 3.0.0
+ escape-string-regexp: 4.0.0
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.3
+
+ prosemirror-transform@1.10.5:
+ dependencies:
+ prosemirror-model: 1.25.4
+
+ prosemirror-view@1.41.3:
+ dependencies:
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-transform: 1.10.5
+
pump@3.0.3:
dependencies:
end-of-stream: 1.4.5
once: 1.4.0
optional: true
+ punycode.js@2.3.1: {}
+
punycode@2.3.1: {}
pure-color@1.3.0: {}
@@ -11257,6 +12438,10 @@ snapshots:
dependencies:
react: 19.1.0
+ react-icons@5.5.0(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+
react-intersection-observer@9.5.1(react@19.1.0):
dependencies:
react: 19.1.0
@@ -11330,6 +12515,11 @@ snapshots:
react: 19.1.0
react-dom: 19.1.0(react@19.1.0)
+ react-number-format@5.4.4(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
+ dependencies:
+ react: 19.1.0
+ react-dom: 19.1.0(react@19.1.0)
+
react-pdf@9.2.1(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
clsx: 2.1.1
@@ -11419,6 +12609,15 @@ snapshots:
transitivePeerDependencies:
- '@types/react'
+ react-textarea-autosize@8.5.9(@types/react@19.1.8)(react@19.1.0):
+ dependencies:
+ '@babel/runtime': 7.26.9
+ react: 19.1.0
+ use-composed-ref: 1.4.0(@types/react@19.1.8)(react@19.1.0)
+ use-latest: 1.3.0(@types/react@19.1.8)(react@19.1.0)
+ transitivePeerDependencies:
+ - '@types/react'
+
react-window@1.8.9(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
dependencies:
'@babel/runtime': 7.26.9
@@ -11447,9 +12646,9 @@ snapshots:
estree-util-build-jsx: 3.0.1
vfile: 6.0.3
- recma-jsx@1.0.0(acorn@8.14.0):
+ recma-jsx@1.0.0(acorn@8.15.0):
dependencies:
- acorn-jsx: 5.3.2(acorn@8.14.0)
+ acorn-jsx: 5.3.2(acorn@8.15.0)
estree-util-to-js: 2.0.0
recma-parse: 1.0.0
recma-stringify: 1.0.0
@@ -11509,6 +12708,11 @@ snapshots:
gopd: 1.2.0
set-function-name: 2.0.2
+ rehype-format@5.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-format: 1.1.0
+
rehype-katex@7.0.1:
dependencies:
'@types/hast': 3.0.4
@@ -11519,6 +12723,17 @@ snapshots:
unist-util-visit-parents: 6.0.1
vfile: 6.0.3
+ rehype-minify-whitespace@6.0.2:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-minify-whitespace: 1.0.1
+
+ rehype-parse@9.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-from-html: 2.0.3
+ unified: 11.0.5
+
rehype-raw@7.0.0:
dependencies:
'@types/hast': 3.0.4
@@ -11533,11 +12748,25 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ rehype-remark@10.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ '@types/mdast': 4.0.4
+ hast-util-to-mdast: 10.1.2
+ unified: 11.0.5
+ vfile: 6.0.3
+
rehype-sanitize@6.0.0:
dependencies:
'@types/hast': 3.0.4
hast-util-sanitize: 5.0.2
+ rehype-stringify@10.0.1:
+ dependencies:
+ '@types/hast': 3.0.4
+ hast-util-to-html: 9.0.5
+ unified: 11.0.5
+
remark-code-import@1.2.0:
dependencies:
strip-indent: 4.0.0
@@ -11643,8 +12872,14 @@ snapshots:
reusify@1.1.0: {}
+ rope-sequence@1.3.4: {}
+
rough-notation@0.5.1: {}
+ rrweb-cssom@0.7.1: {}
+
+ rrweb-cssom@0.8.0: {}
+
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -11675,6 +12910,12 @@ snapshots:
es-errors: 1.3.0
is-regex: 1.2.1
+ safer-buffer@2.1.2: {}
+
+ saxes@6.0.0:
+ dependencies:
+ xmlchars: 2.2.0
+
scheduler@0.26.0: {}
scroll-into-view-if-needed@3.1.0:
@@ -11936,6 +13177,8 @@ snapshots:
react: 19.1.0
use-sync-external-store: 1.4.0(react@19.1.0)
+ symbol-tree@3.2.4: {}
+
tabbable@6.2.0: {}
tailwind-merge@2.6.0: {}
@@ -11996,6 +13239,12 @@ snapshots:
fdir: 6.4.6(picomatch@4.0.3)
picomatch: 4.0.3
+ tldts-core@6.1.86: {}
+
+ tldts@6.1.86:
+ dependencies:
+ tldts-core: 6.1.86
+
to-gatsby-remark-plugin@0.1.0:
dependencies:
to-vfile: 6.1.0
@@ -12009,10 +13258,20 @@ snapshots:
is-buffer: 2.0.5
vfile: 4.2.1
+ tough-cookie@5.1.2:
+ dependencies:
+ tldts: 6.1.86
+
tr46@0.0.3: {}
+ tr46@5.1.1:
+ dependencies:
+ punycode: 2.3.1
+
trim-lines@3.0.1: {}
+ trim-trailing-lines@2.1.0: {}
+
trough@2.2.0: {}
ts-api-utils@2.0.1(typescript@5.8.3):
@@ -12046,6 +13305,8 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
+ type-fest@4.41.0: {}
+
type@2.7.3: {}
typed-array-buffer@1.0.3:
@@ -12085,6 +13346,8 @@ snapshots:
ua-parser-js@1.0.40: {}
+ uc.micro@2.1.0: {}
+
unbox-primitive@1.1.0:
dependencies:
call-bound: 1.0.3
@@ -12238,6 +13501,8 @@ snapshots:
util-deprecate@1.0.2: {}
+ uuid@8.3.2: {}
+
uvu@0.5.6:
dependencies:
dequal: 2.0.3
@@ -12295,6 +13560,10 @@ snapshots:
w3c-keyname@2.2.8: {}
+ w3c-xmlserializer@5.0.0:
+ dependencies:
+ xml-name-validator: 5.0.0
+
warning@4.0.3:
dependencies:
loose-envify: 1.4.0
@@ -12303,6 +13572,19 @@ snapshots:
webidl-conversions@3.0.1: {}
+ webidl-conversions@7.0.0: {}
+
+ whatwg-encoding@3.1.1:
+ dependencies:
+ iconv-lite: 0.6.3
+
+ whatwg-mimetype@4.0.0: {}
+
+ whatwg-url@14.2.0:
+ dependencies:
+ tr46: 5.1.1
+ webidl-conversions: 7.0.0
+
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
@@ -12357,8 +13639,28 @@ snapshots:
wrappy@1.0.2:
optional: true
+ ws@8.18.3: {}
+
+ xml-name-validator@5.0.0: {}
+
+ xmlchars@2.2.0: {}
+
xtend@4.0.2: {}
+ y-prosemirror@1.3.7(prosemirror-model@1.25.4)(prosemirror-state@1.4.4)(prosemirror-view@1.41.3)(y-protocols@1.0.6(yjs@13.6.27))(yjs@13.6.27):
+ dependencies:
+ lib0: 0.2.114
+ prosemirror-model: 1.25.4
+ prosemirror-state: 1.4.4
+ prosemirror-view: 1.41.3
+ y-protocols: 1.0.6(yjs@13.6.27)
+ yjs: 13.6.27
+
+ y-protocols@1.0.6(yjs@13.6.27):
+ dependencies:
+ lib0: 0.2.114
+ yjs: 13.6.27
+
yallist@5.0.0: {}
yjs@13.6.27: